22

在 Python 类中,当我使用__setattr__它时,它优先于此类(或任何基类)中定义的属性。考虑以下代码:

class Test(object):
    def get_x(self):
        x = self._x
        print "getting x: %s" % x
        return x
    def set_x(self, val):
        print "setting x: %s" % val
        self._x = val
    x = property(get_x, set_x)
    def __getattr__(self, a):
        print "getting attr %s" % a
        return -1
    def __setattr__(self, a, v):
        print "setting attr %s" % a

当我创建类并尝试设置x时,__setattr__调用而不是set_x

>>> test = Test()
>>> test.x = 2
setting attr x
>>> print test.x
getting attr x_
getting x: -1
-1

我想要实现的是,__setattr__只有在没有相关属性(即test.x = 2应该调用)时才调用实际代码set_x。我知道我可以通过手动检查 if ais " x " is来轻松实现这一点__setattr__,但这会使设计变得糟糕。有没有更聪明的方法来确保__setattr__类和所有基类中定义的每个属性的正确行为?

4

4 回答 4

35

Python 用于属性的搜索顺序如下所示:

  1. __getattribute____setattr__
  2. 数据描述符,如property
  3. 来自对象的实例变量__dict__(设置属性时,搜索到此结束)
  4. 非数据描述符(如方法)和其他类变量
  5. __getattr__

因为__setattr__它是排在第一位的,如果你有一个,你需要让它变得聪明,除非希望它处理你班级的所有属性设置。它可以通过以下两种方式中的任何一种变得智能:使其仅处理特定的集合属性,或使其处理某些属性集之外的所有属性。对于您不希望它处理的那些,请致电super().__setattr__.

对于您的示例类,处理“除'x'之外的所有属性”可能是最简单的:

def __setattr__(self, name, value):
    if name == "x":
        super(Test, self).__setattr__(name, value)
    else:
        print "setting attr %s" % name
于 2013-04-01T20:21:46.947 回答
9

这不是一个防弹解决方案,但是,就像您建议的那样,您可以setattr通过尝试property从类的属性(getattr在类对象上使用)访问对象来检查是否正在编辑属性。

class Test(object):

    def get_x(self):
        x = self._x
        print "getting x: %s" % x
        return x
    def set_x(self, val):
        print "setting x: %s" % val
        self._x = val
    x = property(get_x, set_x)

    @property  # no fset
    def y(self):
        print "getting y: 99"
        return 99

    def __getattr__(self, a):
        print "getting attr %s" % a
        return -1
    def __setattr__(self, a, v):
        propobj = getattr(self.__class__, a, None)
        if isinstance(propobj, property):
            print "setting attr %s using property's fset" % a
            if propobj.fset is None:
                raise AttributeError("can't set attribute")
            propobj.fset(self, v)
        else:
            print "setting attr %s" % a
            super(Test, self).__setattr__(a, v)


test = Test()
test.x = 2
print test.x
#test.y = 88  # raises AttributeError: can't set attribute
print test.y
test.z = 3
print test.z

编辑:替换self.__dict__[a] = vsuper(Test, self).__setattr__(a, v),如@Blckknght 的回答所示

于 2013-04-01T20:20:26.837 回答
2

AFAIK,没有干净的方法可以做到这一点。这里的问题来自 和之间的不对称。前者仅在通过正常方式的属性失败时才调用,而后者则无条件调用。由于没有通用的方法会失败,我不知道是否有办法可以改变这种行为。 __getattr____setattr____setattr__

最终,我相信获得你想要的行为的唯一方法是将你的属性的 set_ 操作折叠到你的__setattr__函数中——如果你这样做,你不妨折叠 getter 的行为__getattr__来拥有它都可以从 1 个地方维护。

于 2013-04-01T19:54:28.970 回答
0

我多次遇到这个问题,我的首选解决方案如下:

  • 对于每个属性,像你一样[property]定义get_[property]和函数,但不使用装饰器set_[property]
  • 修改__getattr____setattr__以便他们检查这些功能是否存在,并在可用时使用它们。

这是一个最小的例子:

class SmartSetter(object):

  def __init__(self,name):
    self.set_name(name)

  def get_name(self):
    #return the name property
    return self._name

  def set_name(self,value):
    #set the name property
    self._name = value

  def __getattr__(self,key):
    #first condition is to avoid recursive calling of this function by
    #__setattr__ when setting a previously undefined class attribute.
    if not key.startswith('get_') and hasattr(self,'get_'+key):
      return getattr(self,'get_'+key)()
    #implement your __getattr__ magic here...
    raise AttributeError("no such attribute: %s" % key)

  def __setattr__(self,key,value):
    try:
      return getattr(self,'set_'+key)(value)
    except AttributeError:
      #implement your __setattr__ magic here...
      return super(SmartSetter,self).__setattr__(key,value)

if __name__ == '__main__':
  smart_setter = SmartSetter("Bob the Builder")

  print smart_setter.name
  #Will print "Bob the Builder"

  smart_setter.name = "Spongebob Squarepants"

  print smart_setter.name
  #Will print "Spongebob Squarepants"

这种方法的优点是它保留了所有属性的正常行为,除了那些具有“getter”和“setter”方法的属性,并且在添加或删除属性时不需要对__getattr__and函数进行任何修改。__setattr__

于 2014-03-22T22:56:21.470 回答