特性(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)