property實作有__get__、__set__及__delete__,所以是一種data descriptor。其具備有簡潔的語法能方便使用,而不用煩惱descriptor的實作細節。
property的signature如下:
property(fget=None, fset=None, fdel=None, doc=None) -> property
可以將其想為一個class,並接受四個選擇性參數。以下我會以prop來稱呼由property建立的property instance。
fget控制instance.prop時的行為。fset控制instance.prop = value時的行為。fdel控制del instance.prop時的行為。doc為prop的說明文件。如果沒有指定doc,而prop裡有fget時,會將doc設為fget中的__doc__。prop後,想要新增fget、fset或fdel時,我們不mutate prop,而是透過property.getter、property.setter或property.deleter來新建一個prop返回。# 01為property的基本型態。
# 01
class MyClass:
def __init__(self, x):
self.x = x
@property
def x(self):
"""docstrings from fget"""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
if __name__ == '__main__':
my_inst = MyClass(1)
首先我們將property裝飾於function x上,而此function x內具有當使用my_inst.x語法時的行為。此裝飾相當於x = property(x):
function x作為property的第一個參數fget,並返回prop,且命名為x。prop x內已有fget及doc,但還未有fset及fdel。接著將x.setter裝飾於另一個function x上,而此function x內具有當使用my_inst.x = value語法時的行為。此裝飾相當於x = x.setter(x):
prop x的setter接收這個function x。setter中,將會生成一個新的prop,以第一步的fget作為fget及doc作為doc,再加上剛剛接收的function x作為fset。prop,且命名為x。現在的prop x內已有fget、fset及doc,但還未有fdel。最後將x.deleter裝飾於另一個function x上,而此function x內具有當使用del my_inst.x語法時的行為。此裝飾相當於x = x.deleter(x):
prop x的deleter接收這個function x。deleter中,將會生成一個新的prop,以上一步的fget作為fget、doc作為doc及fset作為fset,再加上剛剛接收的function x作為fdel。prop,且命名為x。現在的prop x內已完整擁有fget、fset、fdel及doc。乍看之下,# 01只是生成了my_inst,還沒有任何與prop x互動。但仔細看看__init__,my_inst已經透過property這個介面呼叫了prop x的fset來進行self.x = x(self就是my_inst)。
@x.setter與@x.deleter裝飾的function必須與@property所裝飾的function名一致,即x。如果使用不同名字,使用上會變得很困難,且容易出錯(註1)。
# 01是基本型態,但視您的程式需要,您可能需要先建立一個prop,然後再視情況加入fget、fset、fdel或doc,如# 01a。
# 01a
class MyClass:
def __init__(self, x):
self.x = x
prop = property()
@prop.getter
def x(self):
"""docstrings from fget"""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
又或者您可以建立好fget、fset、fdel或doc,再一次生成prop,如# 01b。
# 01b
class MyClass:
def __init__(self, x):
self.x = x
def get_x(self):
return self._x
def set_x(self, value):
self._x = value
def del_x(self):
del self._x
x = property(fget=get_x,
fset=set_x,
fdel=del_x,
doc="""docstrings""")
property適用時機instance中有一個變數,不想直接被存取,而希望使用者透過給定的接口(getter、setter與deleter)來操作這個變數。由於使用property來存取變數,與存取一般變數的語法是相同的,所以我們寫code時,可以不用一開始就決定哪些變數要使用property,等到code的中後段再來判斷,而所有的interface並不需要因此修改。function。但有些時候,這個變數更像是instance attribute,並且大多會使用快取的機制時,也是一個適合使用property的時機。註1:
@x.setter相當於set_x = x.setter(set_x),意思是使用prop x的fget及doc加上現在的set_x建立一個新prop返回,並命名為set_x。現在MyClass有兩個prop:
prop x,擁有fget及doc。prop set_x,擁有fget、fset及doc。@x.deleter相當於del_x = x.deleter(del_x),意思是使用prop x的fget及doc加上現在的del_x建立一個新prop返回,並命名為del_x。現在MyClass有三個prop:
prop x,擁有fget及doc。prop set_x,擁有fget、fset及doc。prop del_x,擁有fget、fdel及doc。儘管我們還是可以使用my_inst.x的語法,但:
my_inst.x = value必須改成my_inst.set_x = value。del my_inst.x必須改成del my_inst.delx。# 101
class MyClass:
def __init__(self, x):
self.set_x = x # not self.x = x
@property
def x(self):
"""docstrings from fget"""
return self._x
@x.setter
def set_x(self, value):
self._x = value
@x.deleter
def del_x(self):
del self._x
if __name__ == '__main__':
my_inst = MyClass(1)
for name, prop in (('x', MyClass.x),
('set_x', MyClass.set_x),
('del_x', MyClass.del_x)):
print(f'prop {name=}: ')
print(f'type of prop {name} is {type(prop)}')
print(f'fget={prop.fget}')
print(f'fset={prop.fset}')
print(f'fdel={prop.fdel}')
print(f'doc={prop.__doc__}\n')
prop name='x':
type of prop x is <class 'property'>
fget=<function MyClass.x at 0x00000142E5AC4EA0>
fset=None
fdel=None
doc=docstrings from fget
prop name='set_x':
type of prop set_x is <class 'property'>
fget=<function MyClass.x at 0x00000142E5AC4EA0>
fset=<function MyClass.set_x at 0x00000142E5AC4E00>
fdel=None
doc=docstrings from fget
prop name='del_x':
type of prop del_x is <class 'property'>
fget=<function MyClass.x at 0x00000142E5AC4EA0>
fset=None
fdel=<function MyClass.del_x at 0x00000142E5AC6340>
doc=docstrings from fget