类属性和__setattr__

时间:2013-04-01 19:42:11

标签: python

在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。我知道我可以通过手动检查a是否是“ x ”是__setattr__来轻松实现这一点,但这样做会很糟糕。是否有更聪明的方法来确保__setattr__中对于类和所有基类中定义的每个属性的正确行为?

4 个答案:

答案 0 :(得分:24)

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

  1. __getattribute____setattr__
  2. 数据描述符,例如property
  3. 来自对象__dict__的实例变量(设置属性时,搜索结束于此处)
  4. 非数据描述符(如方法)和其他类变量
  5. __getattr__
  6. 由于__setattr__排在第一位,如果您有一个,则需要将其设为智能,除非希望它处理您班级的所有属性设置。它可以通过以下两种方式实现智能:使其仅处理特定的集合属性,或使其处理之外的所有属性集。对于您不希望它处理的那些,请致电super().__setattr__

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

    def __setattr__(self, name, value):
        if name == "x":
            super(Test, self).__setattr__(name, value)
        else:
            print "setting attr %s" % name
    

答案 1 :(得分:6)

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

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] = v,如@ Blckknght的回答所示

答案 2 :(得分:2)

AFAIK,没有干净的方法来做到这一点。这里的问题来自__getattr____setattr__之间的asymmetry。只有当正常方式的属性失败时才调用前者,但后者被无条件地调用。由于__setattr__没有通用的方法会失败,我不知道是否有办法可以改变这种行为。

最终,我认为获得所需行为的唯一方法是将属性的set_ action折叠到__setattr__函数中 - 如果你这样做,你也可以折叠getter进入__getattr__的行为,可以从1个地方维护它。

答案 3 :(得分: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__和{{1添加或删除属性时的函数。