Python @property裝飾器全解析
在 Python 中,@property 是一個(gè)非常常用、也很優(yōu)雅的裝飾器,用來(lái)把“方法”偽裝成“屬性”。它最常見(jiàn)的用途是:對(duì)屬性的訪問(wèn)進(jìn)行控制,同時(shí)保持簡(jiǎn)潔的屬性訪問(wèn)語(yǔ)法。
下面從幾個(gè)方面幫你系統(tǒng)介紹一下 @property:
一、為什么需要@property?
假設(shè)你有一個(gè)類(lèi),直接暴露公共屬性:
class Person:
def __init__(self, name, age):
self.name = name # 公共屬性
self.age = age # 公共屬性一開(kāi)始你只是簡(jiǎn)單存?zhèn)€年齡,后來(lái)你發(fā)現(xiàn):
- 想對(duì)
age做合法性檢查(不能是負(fù)數(shù)) - 想在設(shè)置年齡時(shí)做一些額外處理(比如同步更新別的數(shù)據(jù))
如果屬性是直接暴露的:
p = Person("Tom", 20)
p.age = -5 # 沒(méi)有任何阻止
你可以改成用方法:
class Person:
def __init__(self, name, age):
self._age = None
self.set_age(age)
def get_age(self):
return self._age
def set_age(self, value):
if value < 0:
raise ValueError("年齡不能為負(fù)數(shù)")
self._age = value但這樣使用時(shí)會(huì)變成:
p = Person("Tom", 20)
p.set_age(30)
print(p.get_age())
寫(xiě)起來(lái)不如屬性直觀。并且如果項(xiàng)目已經(jīng)有很多地方寫(xiě)了 p.age,現(xiàn)在改成 p.get_age() / p.set_age() 會(huì)造成兼容性問(wèn)題。
@property 的作用就是:
在“不改變外部訪問(wèn)方式”的前提下,為屬性增加 getter/setter 等邏輯。
二、@property的基本用法(只讀屬性)
最簡(jiǎn)單的用法是將一個(gè)方法變成“只讀屬性”:
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self): # getter
return self._radius使用時(shí):
c = Circle(10) print(c.radius) # 像訪問(wèn)屬性一樣調(diào)用,實(shí)際調(diào)用的是 radius() 方法 # 輸出:10 c.radius = 20 # 這里會(huì)報(bào)錯(cuò),因?yàn)闆](méi)有定義 setter # AttributeError: can't set attribute
此時(shí) radius 就是一個(gè)只讀屬性:可以獲取,不能直接賦值。
三、定義可讀可寫(xiě)屬性:@xxx.setter
常見(jiàn)場(chǎng)景是既要讀,也要寫(xiě),并在寫(xiě)入時(shí)做檢查或處理:
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""半徑屬性:讀取時(shí)從 _radius 取值"""
return self._radius
@radius.setter
def radius(self, value):
"""設(shè)置半徑時(shí)進(jìn)行校驗(yàn)"""
if value <= 0:
raise ValueError("半徑必須為正數(shù)")
self._radius = value使用:
c = Circle(10) print(c.radius) # 10 c.radius = 5 # 實(shí)際調(diào)用 radius.setter print(c.radius) # 5 c.radius = -1 # ValueError: 半徑必須為正數(shù)
要點(diǎn):
@radius.setter中的名字必須和@property的函數(shù)名完全一致。- setter 只定義“寫(xiě)邏輯”,讀邏輯仍然在
@property的函數(shù)里。
四、帶有刪除邏輯的屬性:@xxx.deleter
有時(shí)你希望通過(guò) del obj.attr 來(lái)觸發(fā)一些自定義行為,可以用 @xxx.deleter:
class Demo:
def __init__(self, value):
self._value = value
@property
def value(self):
print("獲取 value")
return self._value
@value.setter
def value(self, v):
print("設(shè)置 value")
self._value = v
@value.deleter
def value(self):
print("刪除 value")
del self._value使用:
d = Demo(10) print(d.value) # 獲取 value -> 10 d.value = 20 # 設(shè)置 value del d.value # 刪除 value
五、計(jì)算屬性:不存儲(chǔ)結(jié)果,只在訪問(wèn)時(shí)計(jì)算
@property 不一定要綁定一個(gè)內(nèi)部存儲(chǔ)字段,也可以每次訪問(wèn)時(shí)動(dòng)態(tài)計(jì)算:
import math
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, r):
if r <= 0:
raise ValueError("半徑必須為正數(shù)")
self._radius = r
@property
def area(self):
"""面積屬性:只讀,實(shí)時(shí)計(jì)算"""
return math.pi * self._radius ** 2使用:
c = Circle(10) print(c.area) # 314.159... c.radius = 5 print(c.area) # 會(huì)自動(dòng)變成半徑為 5 時(shí)的面積
area 不需要額外存儲(chǔ)字段 _area,也不需要在每次修改半徑時(shí)手動(dòng)更新面積,訪問(wèn)時(shí)自動(dòng)按最新半徑計(jì)算。
六、命名習(xí)慣:前有下劃線的“內(nèi)部屬性”
常見(jiàn)寫(xiě)法:
- 公共接口屬性:
obj.x - 內(nèi)部真實(shí)存儲(chǔ)字段:
obj._x
class Person:
def __init__(self, age):
self._age = None
self.age = age # 這里會(huì)走 setter
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value < 0:
raise ValueError("年齡不能為負(fù)數(shù)")
self._age = value初學(xué)者常見(jiàn)錯(cuò)誤是 setter 里面寫(xiě)成 self.age = value:
@age.setter
def age(self, value):
self.age = value # 錯(cuò)誤!會(huì)導(dǎo)致無(wú)限遞歸
正確做法是操作“內(nèi)部字段”例如 self._age,避免調(diào)用自己。
七、@property的優(yōu)點(diǎn)總結(jié)
保持接口穩(wěn)定
一開(kāi)始可以簡(jiǎn)單寫(xiě)成公有屬性:
class Person:
def __init__(self, age):
self.age = age
以后如果需要加校驗(yàn),只要改成:
class Person:
def __init__(self, age):
self._age = None
self.age = age
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value < 0:
raise ValueError("年齡不能為負(fù)數(shù)")
self._age = value外部代碼依然寫(xiě) p.age,不需要改任何調(diào)用。
語(yǔ)法簡(jiǎn)潔、可讀性好
相比 obj.get_x() / obj.set_x(),obj.x 更符合屬性的直覺(jué)。
延遲計(jì)算 / 動(dòng)態(tài)計(jì)算
對(duì)一些計(jì)算開(kāi)銷(xiāo)不大的數(shù)據(jù),用 @property 動(dòng)態(tài)計(jì)算非常方便。
如果計(jì)算很昂貴,還可以結(jié)合緩存(例如自己實(shí)現(xiàn)緩存,或用 functools.cached_property)。
方便加入調(diào)試日志、權(quán)限控制等邏輯
例如在 setter 里打印日志、做權(quán)限檢查,或者在 getter 里做懶加載等。
八、與傳統(tǒng) getter/setter 的區(qū)別
- Java / C++ 風(fēng)格:顯式的
get_xxx()、set_xxx()方法。 - Python:更提倡屬性訪問(wèn)風(fēng)格 +
@property隱藏實(shí)現(xiàn)細(xì)節(jié)。
也就是說(shuō),Python 代碼里你幾乎看不到:
obj.get_age() obj.set_age(20)
而是:
obj.age obj.age = 20
但又不犧牲封裝性:內(nèi)部實(shí)現(xiàn)完全可以通過(guò) @property 控制。
九、一些注意事項(xiàng)
- 不要濫用
@property- 若屬性只是單純存取,不需要任何檢查或計(jì)算,直接公有字段也完全沒(méi)問(wèn)題。
- 過(guò)度包裝會(huì)增加心智負(fù)擔(dān)。
- 避免在 getter 中做特別耗時(shí)的操作
- 因?yàn)樵L問(wèn)語(yǔ)法很輕量,調(diào)用者不會(huì)意識(shí)到“這個(gè)屬性代價(jià)這么大”,容易造成性能問(wèn)題。
- 對(duì)耗時(shí)計(jì)算,建議用普通方法,或者明確注釋說(shuō)明。
- 繼承時(shí)的覆蓋
- 子類(lèi)可以重寫(xiě)父類(lèi)的
@property,仍然使用同名屬性,這對(duì)庫(kù)設(shè)計(jì)非常有用。
- 子類(lèi)可以重寫(xiě)父類(lèi)的
十、一個(gè)綜合示例
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self._balance = None
self.balance = balance # 走 setter 做校驗(yàn)
@property
def balance(self):
"""賬戶(hù)余額,不能為負(fù)數(shù)"""
return self._balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("余額不能為負(fù)數(shù)")
self._balance = float(value)
@property
def is_rich(self):
"""計(jì)算屬性:根據(jù)余額判斷是否“富有”"""
return self._balance >= 1_000_000使用:
acc = BankAccount("Alice", 1000)
print(acc.balance) # 1000.0
acc.balance = 2000
print(acc.is_rich) # False
acc.balance = 2_000_000
print(acc.is_rich) # True
acc.balance = -10 # ValueError: 余額不能為負(fù)數(shù)到此這篇關(guān)于Python @property裝飾器全解析的文章就介紹到這了,更多相關(guān)Python @property裝飾器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python socket網(wǎng)絡(luò)編程步驟詳解(socket套接字使用)
這篇文章主要介紹了什么是套接字、PYTHON套接字模塊,提供一個(gè)簡(jiǎn)單的python socket編程,大家參考使用2013-12-12
詳解Pytorch顯存動(dòng)態(tài)分配規(guī)律探索
這篇文章主要介紹了Pytorch顯存動(dòng)態(tài)分配規(guī)律探索,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
詳解Python中類(lèi)方法@classmethod的應(yīng)用技巧
在Python中,類(lèi)方法(class method)是一種特殊的方法,可以在不創(chuàng)建類(lèi)的實(shí)例的情況下調(diào)用,本文將詳細(xì)介紹類(lèi)方法的概念、用法以及在實(shí)際開(kāi)發(fā)中的應(yīng)用場(chǎng)景,希望對(duì)大家有所幫助2024-03-03
使用Python可設(shè)置抽獎(jiǎng)?wù)邫?quán)重的抽獎(jiǎng)腳本代碼
這篇文章主要介紹了Python可設(shè)置抽獎(jiǎng)?wù)邫?quán)重的抽獎(jiǎng)腳本,抽獎(jiǎng)系統(tǒng)包含可給不同抽獎(jiǎng)?wù)咴O(shè)置不同的權(quán)重,先從價(jià)值高的獎(jiǎng)品開(kāi)始抽,已經(jīng)中獎(jiǎng)的人,不再參與后續(xù)的抽獎(jiǎng),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11
python 網(wǎng)絡(luò)爬蟲(chóng)初級(jí)實(shí)現(xiàn)代碼
這篇文章主要介紹了python 網(wǎng)絡(luò)爬蟲(chóng)初級(jí)實(shí)現(xiàn)代碼,需要的朋友可以參考下2016-02-02
PyQt5實(shí)現(xiàn)類(lèi)似別踩白塊游戲
這篇文章主要為大家詳細(xì)介紹了PyQt5實(shí)現(xiàn)類(lèi)似別踩白塊游戲,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01
python list轉(zhuǎn)矩陣的實(shí)例講解
今天小編就為大家分享一篇python list轉(zhuǎn)矩陣的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
python實(shí)現(xiàn)數(shù)獨(dú)游戲 java簡(jiǎn)單實(shí)現(xiàn)數(shù)獨(dú)游戲
這篇文章主要為大家詳細(xì)介紹了python實(shí)現(xiàn)數(shù)獨(dú)游戲和java實(shí)現(xiàn)數(shù)獨(dú)游戲的相關(guān)代碼,比較兩種語(yǔ)言實(shí)現(xiàn)數(shù)獨(dú)游戲的區(qū)別2018-03-03

