簡體   English   中英

類屬性和__setattr__

[英]Class properties and __setattr__

在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__為類和所有基類中定義的每個屬性的正確行為?

Python用於屬性的搜索順序如下:

  1. __getattribute____setattr__
  2. 數據描述符,如property
  3. 來自對象__dict__實例變量(設置屬性時,搜索結束於此處)
  4. 非數據描述符(如方法)和其他類變量
  5. __getattr__

由於__setattr____setattr__在第一位的,如果你有一個,你需要讓它變得聰明,除非希望它能處理你班級的所有屬性設置。 它可以通過以下兩種方式實現智能:使其僅處理特定的集合屬性,或使其處理一些屬性集之外的所有屬性。 對於你不希望它處理的那些,調用super().__setattr__

對於您的示例類,處理“除'x'之外的所有屬性”可能是最簡單的:

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

這不是防彈的解決方案,但是,像你的建議,你可以檢查一個屬性被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] = v with super(Test, self).__setattr__(a, v) ,見@ Blckknght的回答

AFAIK,沒有干凈的方法來做到這一點。 這里的問題源於__getattr____setattr__之間的不對稱性。 只有當正常方式的屬性失敗時才調用前者,但后者被無條件地調用。 由於__setattr__沒有通用的方法會失敗,我不知道是否有辦法可以改變這種行為。

最后,我認為獲得所需行為的唯一方法是將屬性的__setattr__動作折疊到__setattr__函數中 - 如果你這樣做,你也可以將getter的行為折疊成__getattr__到從1個地方可以維護它。

我多次遇到這個問題,我的首選解決方案如下:

  • 對於每個屬性[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__函數進行任何修改。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM