1. 程式人生 > >Python的特性(property) (轉)

Python的特性(property) (轉)

來看 AR 重新定義 ica 支持 麻煩 有效 ret make

來自:http://www.cnblogs.com/blackmatrix/p/5646778.html

特性(property)

特性是對類的一個特定屬性進行攔截,在操作這個屬性時,執行特定的函數,對屬性的操作進行攔截。

特性的實現

特性使用property類來實現,也可以使用property裝飾器實現,二者本質是一樣的。

property類的__init__函數接收4個參數,來實現屬性的獲取、賦值、刪除及文檔。

技術分享圖片
    def __init__(self, fget=None, fset=None, fdel=None, doc=None): # known special case of property.__init__
        """
        property(fget=None, fset=None, fdel=None, doc=None) -> property attribute
        
        fget is a function to be used for getting an attribute value, and likewise
        fset is a function for setting, and fdel a function for del‘ing, an
        attribute.  Typical use is to define a managed attribute x:
        
        class C(object):
            def getx(self): return self._x
            def setx(self, value): self._x = value
            def delx(self): del self._x
            x = property(getx, setx, delx, "I‘m the ‘x‘ property.")
        
        Decorators make defining new properties or modifying existing ones easy:
        
        class C(object):
            @property
            def x(self):
                "I am the ‘x‘ property."
                return self._x
            @x.setter
            def x(self, value):
                self._x = value
            @x.deleter
            def x(self):
                del self._x
        
        # (copied from class doc)
        """
        pass
技術分享圖片

從代碼上看,4個參數都不是必須的,如果沒有傳入對應的操作函數,則取默認值None,則對應的操作不受支持,試圖調用默認值None時,會引發異常。

測試代碼:

技術分享圖片
class Person(object):

    def __init__(self, age):
        self._age = age

    # @property 裝飾器等同於 age = property(fget=age)
    @property
    def age(self):
        return self._age


if __name__ == ‘__main__‘:

    jack = Person(22)
    jack.age = 32
技術分享圖片

因為缺少setter的函數方法,所以試圖給age賦值時,會引發異常。

AttributeError: can‘t set attribute

所以我們把setter方法補充完整,setter裝飾器的寫法是剛剛被property的裝飾器所裝飾的函數的名稱,再加上setter屬性

比如下面例子中,裝飾的是 def age(self): .... 這個方法,那麽對應的setter裝飾器,就應該是 @age.setter

技術分享圖片
class Person(object):

    def __init__(self, age):
        self._age = age
        self._name = ‘lilei‘

    # @property 裝飾器等同於 age = property(fget=age)
    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        print(‘person property age setter‘)
        self._age = value * 2
技術分享圖片

一開始可能不太容易理解,age明明是一個實例方法,內部也沒有setter這個屬性,為什麽就變成了一個裝飾器,還有setter這個屬性了?

實際上,age()方法上增加@property裝飾器,等同於age = property(fget=age),將age賦值為property的實例。

所以,被裝飾後的age,已經不是這個實例方法age了,而是property的實例age。

可以將age的type打印出來看看,會得到<class ‘property‘>,說明age已經不是當初那個age了,他們只是同名而已。

可以再做個測試,把裝飾器的寫法修改下:

@property
def age(self):
    pass

修改為

age = property(fget=age)

不影響代碼的執行,所以@age.setter裝飾器就很好理解了,因為age是property類的實例,property類有setter的方法,age擁有property類的setter方法,所以可以使用@age.setter裝飾器。其他的setter、getter、deleter也是同理。

特性的繼承

類的實例和子類都會繼承類的特性,測試代碼:

技術分享圖片
class Person(object):
  def __init__(self, age): self._age = age @property def age(self): return self._age @age.setter def age(self, value): self._age = value * 2


class Man(Person): pass if __name__ == ‘__main__‘: tom = Man(22) print(tom.age) tom.age = 23 print(tom.age)
技術分享圖片

在age的setter方法中,將age的值乘以2,從運行結果上看,子類及其實例都是繼承property的

22
46

特性只對實例方法有效,對於靜態方法、類方法都無法使用

在子類中重寫父類的property

如果想在子類中,重寫父類的property,實際上要分為兩種情況:一種是完全重寫父類的property,一種是只想重寫父類property的某些方法,比如說setter。

完全重寫父類的property最為簡單,在子類中重新定義一個同名的getter函數,再加上@propety裝飾器即可。

技術分享圖片
class Student(Person):

    # 如果在子類重新定義一個property,會完全覆蓋掉父類的同名的property,包括裏面的方法
    # 所以沒有定義setter方法,age變為只讀,無法賦值
    # 實際上,這種方式是在子類重新創建了一個名為age的property對象,覆蓋掉了父類名為age的property
    # age = property(fget=..., fset=None)
    @property
    def age(self):
        print(‘student property age getter‘)
        return self._age
技術分享圖片

可以這麽理解,上面相當於在子類創建了一個property的同名實例:age,根據類繼承的原則,子類的屬性會覆蓋掉父類的屬性,所以這個時候調用子類實例的age,只會執行子類的getter方法,同時,因為setter和deleter方法沒有定義,無法進行對應的操作。

如果只想修改父類propery的某個方法,會稍微麻煩點。

在使用propery裝飾器的時候,需要先指定父類的名稱,property實例的名稱,最後指定需要修改的方法。

如下面示例代碼的 @Person.age.getter。因為age本身是property的類實例,所以@Person.age.getter 這個裝飾器,相當於找到父類Person的屬性age,在找到age的getter方法,並用被裝飾的函數,覆蓋掉父類的getter方法函數。

同樣,setter方法和deleter的方法也是同理。

技術分享圖片
class Teacher(Person):

    # 只有這種方式才能正確的覆蓋掉父類的getter方法
    @Person.age.getter
    def age(self):
        print(‘teacher property age getter‘)
        return self._age

    # 同樣, 這樣才能正確的覆蓋掉父類特性的setter方法
    # property是個類,@Person.age.setter,這個操作相當於找到Person下,這個age(property實例)的方法fset
    # 並用我們在子類的方法將其覆蓋。
    @Person.age.setter
    def age(self, value):
        print(‘teacher property age setter‘)
        self._age = value
技術分享圖片

上面的方法,其實有個缺陷:我們必須清楚的知道,需要重寫的propery所屬的父類。

這對於單繼承通常的是沒有問題的,但是對於多繼承就會存在問題:比如繼承樹中存在多個同名的property那麽到底應該繼承哪個property就會產生疑惑。

比較合理的解決辦法是,如student這個類一樣,徹底重寫property的實例,再使用super()方法去調用父類的方法。

技術分享圖片
class Girl(Person):

    @property
    def age(self):
        print(‘girl property age getter‘)
        return super().age

    @age.setter
    def age(self, value):
        print(‘girl property age setter‘)
        super(Girl, Girl).age.__set__(self, value)
技術分享圖片

Python的特性(property) (轉)