2013-04-01 47 views
14

在Python类中,当我使用__setattr__时,它优先于此类(或任何基类)中定义的属性。请看下面的代码:类属性和__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__中针对类和所有基类中定义的每个属性的正确行为?

+0

您可以使用描述符:http://docs.python.org/2/howto/descriptor.html –

+0

注意:使用'@ properety'装饰器使属性更清晰:http://docs.python.org/2 /library/functions.html#property - 你的问题是如何定义属性函数在子类中定义的? – ninMonkey

+0

“@ property”装饰器等于'x = property(...)',并且使用它不会改变行为。我没有在示例中使用它,因为我希望能够明确引用'set_x' setter。 –

回答

24

搜索顺序是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 
+0

其实我想避免为'x'设置特殊处理程序,因为我可能会添加一些其他属性,例如在将来的基类中,我不想在这里更新它。然而,'super(Test,self).__ setattr __(name,value)'似乎是通用的,我可能会做相反的事情:使'__setattr__'完成它为某些名称所做的事情,并调用'super'。 –

+1

'__setattr__'第一个有什么启示。 –

2

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

最终,我相信获得所需行为的唯一方法是将属性的set_动作折叠到您的__setattr__函数中 - 如果您这样做,那么您可能会折叠得到者__getattr__从1个地方都可以维护。

+0

其实我正在使用Boost Python编写C++绑定,它为我创建属性。不过,我需要'__setattr__'来解决一些额外的非标准问题。 –

+0

我对Boost一无所知,但是我一直在和这个人搏斗,这是我想出的结论。最终,'__setattr__'胜过你的描述符......(虽然我希望我错了,而其他人给出了更好的答案)。 – mgilson

+0

我想到了一些搜索类和属性描述符的基础字典的方法,但我不知道如何以通用的方式来完成。 –

6

这不是防弹的解决方案,但是,像你的建议,你可以检查一个属性被setattr试图访问property对象,从类的属性ED(类对象上使用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的答案,因为看到

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____setattr__函数进行任何修改。