2013-04-01 44 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__函數進行任何修改。