Python之@property詳解及底層實現介紹
轉自:https://blog.csdn.net/weixin_42681866/article/details/83376484
前文
Python內建有三大裝飾器:@staticmethod(靜態方法)、@classmethod(類方法)、@property(描述符),其中靜態方法就是定義在類裡的函式,並沒有非要定義的必要;類方法則是在呼叫類屬性、傳遞類物件時使用;而@property則是一個非常好用的語法糖。@property最大的好處就是在類中把一個方法變成屬性呼叫,起到既能檢查屬性,還能用屬性的方式來訪問該屬性的作用。
@property應用
讓我們先看下@property的應用,其功能1是可定義只讀屬性,也就是真正意義上的私有屬性(屬性前雙下劃線的私有屬性也是可以訪問的,具體參照這篇文章:
class Person(object): def __init__(self, name, age=18): self.name = name self.__age = 18 @property def age(self): return self.__age xm = Person('xiaoming') # 定義一個人名小明 print(xm.age) # 結果為18 xm.age = -4 # 報錯無法給年齡賦值 print(xm.age)
結果如下:
18
Traceback (most recent call last):
xm.age = 18
AttributeError: can't set attribute
在python中定義只讀屬性非@property莫屬,如果細心留意大部分原始碼,都跑不了@property的身影。而定義只讀屬性也很簡單:以需要定義的屬性為方法名(上例age屬性定義為方法),其上裝飾內建裝飾器@property就ok了。
@property真正強大的是可以限制屬性的定義。往往我們定義類,希望其中的屬性必須符合實際,但因為在__ init __裡定義的屬性可以隨意的修改,導致很難實現。如我想實現Person類,規定每個人(即建立的例項)的年齡必須大於18歲,正常實現的話,則必須將屬性age設為只讀屬性,然後通過方法來賦值,程式碼如下:
class Person(object):
def __init__(self, name, age):
self.name = name
self.__age = 18
@property
def age(self):
return self.__age
def set_age(self, age): # 定義函式來給self.__age賦值
if age < 18:
print('年齡必須大於18歲')
return
self.__age = age
return self.__age
xm = Person('xiaoming', 20)
print(xm.age)
print('----------')
xm.set_age(10)
print(xm.age)
print('----------')
xm.set_age(20)
print(xm.age)
為了便於區分結果,增加了分隔符,結果如下:
18
----------
年齡必須大於18歲
18
----------
20
可以看到,通過方法的確能限制輸入,但是不是每個人都記得方法名,有什麼簡化的方法呢?@property就能很輕鬆辦到,修改程式碼如下:
class Person(object):
def __init__(self, name, age):
self.name = name
self.__age = 18
@property
def age(self):
return self.__age
@age.setter
def age(self, age):
if age < 18:
print('年齡必須大於18歲')
return
self.__age = age
return self.__age
xm = Person('xiaoming', 20)
print(xm.age)
print('----------')
xm.age = 10
print(xm.age)
print('----------')
xm.age = 20
print(xm.age)
結果和上圖一致。兩段程式碼變化的內容:將set_age修改為age,並且在上方加入裝飾器@age.setter。這就是@property定義可訪問屬性的語法,即仍舊以屬性名為方法名,並在方法名上增加@屬性.setter就行了。
@property實現原理
在開頭說過,@property是個描述符(decorator),實際上他本身是類,不信的話,可以將上述程式碼修改如下:
class Person(object):
def __init__(self, name, age):
self.name = name
self.__age = 18
def get_age(self): # 恢復用方法名來獲取以及定義
return self.__age
def set_age(self, age):
if age < 18:
print('年齡必須大於18歲')
return
self.__age = age
return self.__age
age = property(get_age, set_age) # 增加property類
上述程式碼的執行結果和前面一致,將@property裝飾的屬性方法再次修改回定義方法名,然後再類的最下方,定義:屬性=property(get,set,del),這個格式是固定的,通過原始碼可以知道原因,property部分原始碼如下:
class property(object):
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
"""
Property attribute.
fget
function to be used for getting an attribute value
fset
function to be used for setting an attribute value
fdel
function to be used for del'ing an attribute
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
"""
pass
def __set__(self, *args, **kwargs): # real signature unknown
"""
Set an attribute of instance to value.
"""
pass
def __get__(self, *args, **kwargs): # real signature unknown
"""
Return an attribute of instance, which is of type owner.
"""
pass
def __delete__(self, *args, **kwargs): # real signature unknown
"""
Delete an attribute of instance.
"""
pass
為了不讓篇幅過長,同時影響到閱讀效果,這邊截取了部分,可以看到程式碼註釋裡寫了官方的@property用法,同時如果是用類來實現的話,必須得按__ init __ 的引數裡順序fget/fset/fdel依次填入get、set、del方法,因為都是預設引數,所以都可以省略不寫。
介紹下python的描述符,定義:如果一個類裡定義了__ set __ 、__ get __ 、__ delete __三個方法之一,同時給另一個類的屬性賦值為例項,那麼該類可以稱之為描述符。因為描述符的使用目前就python,所以瞭解下就行了。
總結
關於@property的介紹到此為止,@property裝飾器就是一個語法糖(語法糖指那些沒有給計算機語言新增新功能,而只是對人類來說更“甜蜜”的語法。 語法糖往往給程式設計師提供了更實用的編碼方式,有益於更好的編碼風格,更易讀。)。