W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
你想給某個(gè)實(shí)例attribute增加除訪問與修改之外的其他處理邏輯,比如類型檢查或合法性驗(yàn)證。
自定義某個(gè)屬性的一種簡單方法是將它定義為一個(gè)property。例如,下面的代碼定義了一個(gè)property,增加對(duì)一個(gè)屬性簡單的類型檢查:
class Person:
def __init__(self, first_name):
self.first_name = first_name
# Getter function
@property
def first_name(self):
return self._first_name
# Setter function
@first_name.setter
def first_name(self, value):
if not isinstance(value, str):
raise TypeError('Expected a string')
self._first_name = value
# Deleter function (optional)
@first_name.deleter
def first_name(self):
raise AttributeError("Can't delete attribute")
上述代碼中有三個(gè)相關(guān)聯(lián)的方法,這三個(gè)方法的名字都必須一樣。第一個(gè)方法是一個(gè) getter
函數(shù),它使得 first_name
成為一個(gè)屬性。其他兩個(gè)方法給 first_name
屬性添加了 setter
和 deleter
函數(shù)。需要強(qiáng)調(diào)的是只有在 first_name
屬性被創(chuàng)建后,后面的兩個(gè)裝飾器 @first_name.setter
和 @first_name.deleter
才能被定義。
property的一個(gè)關(guān)鍵特征是它看上去跟普通的attribute沒什么兩樣,但是訪問它的時(shí)候會(huì)自動(dòng)觸發(fā) getter
、setter
和 deleter
方法。例如:
>>> a = Person('Guido')
>>> a.first_name # Calls the getter
'Guido'
>>> a.first_name = 42 # Calls the setter
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "prop.py", line 14, in first_name
raise TypeError('Expected a string')
TypeError: Expected a string
>>> del a.first_name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't delete attribute
>>>
在實(shí)現(xiàn)一個(gè)property的時(shí)候,底層數(shù)據(jù)(如果有的話)仍然需要存儲(chǔ)在某個(gè)地方。因此,在get和set方法中,你會(huì)看到對(duì) _first_name``屬性的操作,這也是實(shí)際數(shù)據(jù)保存的地方。另外,你可能還會(huì)問為什么 ``__init__()
方法中設(shè)置了 self.first_name
而不是 self._first_name
。在這個(gè)例子中,我們創(chuàng)建一個(gè)property的目的就是在設(shè)置attribute的時(shí)候進(jìn)行檢查。因此,你可能想在初始化的時(shí)候也進(jìn)行這種類型檢查。通過設(shè)置 self.first_name
,自動(dòng)調(diào)用 setter
方法,這個(gè)方法里面會(huì)進(jìn)行參數(shù)的檢查,否則就是直接訪問 self._first_name
了。
還能在已存在的get和set方法基礎(chǔ)上定義property。例如:
class Person:
def __init__(self, first_name):
self.set_first_name(first_name)
# Getter function
def get_first_name(self):
return self._first_name
# Setter function
def set_first_name(self, value):
if not isinstance(value, str):
raise TypeError('Expected a string')
self._first_name = value
# Deleter function (optional)
def del_first_name(self):
raise AttributeError("Can't delete attribute")
# Make a property from existing get/set methods
name = property(get_first_name, set_first_name, del_first_name)
一個(gè)property屬性其實(shí)就是一系列相關(guān)綁定方法的集合。如果你去查看擁有property的類,就會(huì)發(fā)現(xiàn)property本身的fget、fset和fdel屬性就是類里面的普通方法。比如:
>>> Person.first_name.fget
<function Person.first_name at 0x1006a60e0>
>>> Person.first_name.fset
<function Person.first_name at 0x1006a6170>
>>> Person.first_name.fdel
<function Person.first_name at 0x1006a62e0>
>>>
通常來講,你不會(huì)直接取調(diào)用fget或者fset,它們會(huì)在訪問property的時(shí)候自動(dòng)被觸發(fā)。
只有當(dāng)你確實(shí)需要對(duì)attribute執(zhí)行其他額外的操作的時(shí)候才應(yīng)該使用到property。有時(shí)候一些從其他編程語言(比如Java)過來的程序員總認(rèn)為所有訪問都應(yīng)該通過getter和setter,所以他們認(rèn)為代碼應(yīng)該像下面這樣寫:
class Person:
def __init__(self, first_name):
self.first_name = name
@property
def first_name(self):
return self._first_name
@first_name.setter
def first_name(self, value):
self._first_name = value
不要寫這種沒有做任何其他額外操作的property。首先,它會(huì)讓你的代碼變得很臃腫,并且還會(huì)迷惑閱讀者。其次,它還會(huì)讓你的程序運(yùn)行起來變慢很多。最后,這樣的設(shè)計(jì)并沒有帶來任何的好處。特別是當(dāng)你以后想給普通attribute訪問添加額外的處理邏輯的時(shí)候,你可以將它變成一個(gè)property而無需改變原來的代碼。因?yàn)樵L問attribute的代碼還是保持原樣。
Properties還是一種定義動(dòng)態(tài)計(jì)算attribute的方法。這種類型的attributes并不會(huì)被實(shí)際的存儲(chǔ),而是在需要的時(shí)候計(jì)算出來。比如:
import math
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return math.pi * self.radius ** 2
@property
def diameter(self):
return self.radius ** 2
@property
def perimeter(self):
return 2 * math.pi * self.radius
在這里,我們通過使用properties,將所有的訪問接口形式統(tǒng)一起來,對(duì)半徑、直徑、周長和面積的訪問都是通過屬性訪問,就跟訪問簡單的attribute是一樣的。如果不這樣做的話,那么就要在代碼中混合使用簡單屬性訪問和方法調(diào)用。下面是使用的實(shí)例:
>>> c = Circle(4.0)
>>> c.radius
4.0
>>> c.area # Notice lack of ()
50.26548245743669
>>> c.perimeter # Notice lack of ()
25.132741228718345
>>>
盡管properties可以實(shí)現(xiàn)優(yōu)雅的編程接口,但有些時(shí)候你還是會(huì)想直接使用getter和setter函數(shù)。例如:
>>> p = Person('Guido')
>>> p.get_first_name()
'Guido'
>>> p.set_first_name('Larry')
>>>
這種情況的出現(xiàn)通常是因?yàn)镻ython代碼被集成到一個(gè)大型基礎(chǔ)平臺(tái)架構(gòu)或程序中。例如,有可能是一個(gè)Python類準(zhǔn)備加入到一個(gè)基于遠(yuǎn)程過程調(diào)用的大型分布式系統(tǒng)中。這種情況下,直接使用get/set方法(普通方法調(diào)用)而不是property或許會(huì)更容易兼容。
最后一點(diǎn),不要像下面這樣寫有大量重復(fù)代碼的property定義:
class Person:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
@property
def first_name(self):
return self._first_name
@first_name.setter
def first_name(self, value):
if not isinstance(value, str):
raise TypeError('Expected a string')
self._first_name = value
# Repeated property code, but for a different name (bad!)
@property
def last_name(self):
return self._last_name
@last_name.setter
def last_name(self, value):
if not isinstance(value, str):
raise TypeError('Expected a string')
self._last_name = value
重復(fù)代碼會(huì)導(dǎo)致臃腫、易出錯(cuò)和丑陋的程序。好消息是,通過使用裝飾器或閉包,有很多種更好的方法來完成同樣的事情??梢詤⒖?.9和9.21小節(jié)的內(nèi)容。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: