Python 從attribute到property詳解
字面意思上的區(qū)別
Attribute與property, 都可翻譯成屬性. 雖然無論是在中文中還是英文中 它們的意思都幾乎一樣, 但仍有些許差別. Google了好幾下, 找到了一個看起來比較靠譜的解釋:
According to Webster, a property is a characteristic that belongs to a thing's essential nature and may be used to describe a type or species.
An attribute is a modifier word that serves to limit, identify, particularize, describe, or supplement the meaning of the word it modifies.
簡單來說, property是類的本質(zhì)屬性, 可用于定義和描述一個類別或物種; attribute則是用于詳細(xì)說明它所描述的物體, 是物體的具體屬性.
例如: 人都有嘴巴. 有的人嘴巴很大, 嘴巴是人的property之一, 而大嘴巴只能說是部分人的attribute.
從這個意義上講, property是attribute的子集.
Python里的attribute與property
回到Python.
Attribute與property在Java中不作區(qū)分, 但在Python中有所不同. 下面是Fluent Python(Chapter 19)給出的(非正式)定義:
接下來分別解釋.
attribute
所有的數(shù)據(jù)屬性(data attribute)與方法(method)都是attribute. 根據(jù)attribute的所有者, 可分為class attribute與instance attribute. class或instance的所有attribute都存儲在各自的__dict__屬性中.
例如:
# Python3 class Foo(): name = 'Foo class attribute' def fn(self): pass print('class attribute:', Foo.__dict__) print() foo = Foo() foo.name = 'foo instance attribute' print('instance attribute:', foo.__dict__)
輸出:
class attribute: {'fn': <function Foo.fn at 0x7fd135ec8ea0>, ... , 'name': 'Foo class attribute'}
instance attribute: {'name': 'foo instance attribute'}
property
property是出于安全考慮用setter/getter方法替代data attribute, 例如, 只讀屬性與屬性值合法性驗證.
只讀屬性
例如:
class Foo(): def __init__(self, name): self.name = name foo = Foo('I do not want to be changed') print('foo.name = ', foo.name) foo.name = 'Unluckily, I can be changed' print('foo.name = ', foo.name)
輸出:
foo.name = I do not want to be changed
foo.name = Unluckily, I can be changed
在上面的代碼中, 假如我們只想將foo的name屬性暴露給外部讀取, 但并不想它被修改, 我們該怎么辦? 之前在Python 定義只讀屬性中列出了兩種解決方案. 第一種方案:”通過私有屬性”, 其實就是用property替代attribute.
將上面的foo.name改寫成property:
class Foo(): def __init__(self, name): self.__name = name @property def name(self): return self.__name foo = Foo('I do not want to be changed') print('foo.name = ', foo.name) foo.name = 'Luckily, I really can not be changed'
輸出:
foo.name = I do not want to be changed --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-69-101c96ba497e> in <module>() 9 foo = Foo('I do not want to be changed') 10 print('foo.name = ', foo.name) ---> 11 foo.name = 'Luckily, I really can not be changed' AttributeError: can't set attribute
有兩點需要注意:
foo.name確實已經(jīng)不能通過foo.name = ...來修改了, 即, foo.name已經(jīng)是只讀屬性.
將foo.name從attribute變成property之后, 它的訪問方式并沒有改變. 也就是說, 對外接口沒有改變. 這個優(yōu)點可以讓我們從容的寫代碼, 不用在一開始就糾結(jié)于是使用property還是attribute, 因為可以都使用attribute, 如果有需要, 以后可以在不影響外部代碼的前提下隨時修改. 而在Java里要做到這一點很難(如果可以做到的話).
屬性值合法性驗證
在上面的例子中, foo.name只有g(shù)etter方法, 是只讀的, 但其實property也是可修改的, 只需要為它添加一個setter方法就行了. 那么問題就來了, 如果property也是可讀可改, 那為何要費事將attribute改寫成property呢?
想象一個簡單的購物相關(guān)的業(yè)務(wù)場景. 一個Item代表用戶購買的一樣?xùn)|西, 主要有類別, 價格和數(shù)量屬性:
class Item(): def __init__(self, category, count, price): self.cat = category self.count = count self.price = price
正常的調(diào)用是類似于這樣的, 價格與數(shù)量都是正數(shù):
item = Item('Bread', 1, 10)
可是, 若價格或數(shù)量設(shè)置為負(fù)數(shù)也不會報錯:
item.price = -10 item.count = -1 invalid_item1 = Item('Bread', -1, 10) invalid_item2 = Item('Bread', 1, -10)
從語法上看, 這些語句都是合法的, 但從業(yè)務(wù)上看, 它們都是不合法的. 那么, 怎樣才能防止這種非法賦值呢? 一種解決方案是按照J(rèn)ava風(fēng)格, 實現(xiàn)一個Java式的setter方法, 通過item.set_price(price)設(shè)置price屬性, 然后在set_price方法里寫驗證代碼. 這樣是可行的, 但不夠Pythonic. Python的風(fēng)格是讀與寫都通過屬性名進行:
print(item.price)
item.price = -10
這樣做的好處之前提到過: 將attribute改寫成property時不會改變對外接口. 那么, 如何在執(zhí)行item.price = -10時檢驗-10的合法性呢? 最直白的方法是在__setattr__方法里設(shè)置攔截, 但很麻煩, 特別是當(dāng)需要驗證的屬性很多時.(不信的話可以參照Python 定義只讀屬性的方案二試試).
Python提供的最佳方案是通過property的setter方法:
class Item(): def __init__(self, category, count, price): self.__cat = category # attribute self.count = count # property self.price = price # property @property def cat(self): return self.__cat @property def count(self): return self.__dict__['count'] @count.setter def count(self, value): if value < 0: raise ValueError('count can not be minus: %r'%(value)) self.__dict__['count'] = value @property def price(self): return self.__dict__['price'] @price.setter def price(self, value): if value < 0: raise ValueError('price can not be minus: %r'%(value)) self.__dict__['price'] = value
之前合法的語句現(xiàn)在仍然可以正常運行:
item = Item('Bread', 1, 10) item.price = 20 item.count = 2 print(item.price)
但下面的語句執(zhí)行時便會報錯了:
item = Item('Bread', 1, -10) # or item.price = -10
會報出同一個錯誤:
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-93-4fcbd1284b2d> in <module>() ----> 1 item.price = -10 <ipython-input-91-7546240b5469> in price(self, value) 27 def price(self, value): 28 if value < 0: ---> 29 raise ValueError('price can not be minus: %r'%(value)) 30 self.__dict__['price'] = value ValueError: price can not be minus: -10
定義property的其他方式
@property中的property雖可被當(dāng)作修飾器來使用, 但它其實是一個class(具體API請參考文檔), 所以上面的代碼還可以改寫為:
class Item(): def __init__(self, category, count, price): self.__cat = category # attribute self.count = count # property self.price = price # property def get_cat(self): return self.__cat def get_count(self): return self.__dict__['count'] def set_count(self, value): if value < 0: raise ValueError('count can not be minus: %r'%(value)) self.__dict__['count'] = value def get_price(self): return self.__dict__['price'] def set_price(self, value): if value < 0: raise ValueError('price can not be minus: %r'%(value)) self.__dict__['price'] = value bill = property(get_bill) cat = property(get_cat) count = property(get_count, set_count) price = property(get_price, set_price)
功能上達(dá)到要求了, 可代碼本身看起來很冗長, 比Java中的getter/setter風(fēng)格還要長. 這時可以通過property factory來簡化代碼:
先定義可共用的property factory函數(shù):
def readonly_prop(storage_name): def getter(instance): return instance.__dict__[storage_name] return property(getter) def positive_mutable_prop(storage_name): def getter(instance): return instance.__dict__[storage_name] def setter(instance, value): if value < 0: raise ValueError('%s can not be minus: %r'%(storage_name, value)) instance.__dict__[storage_name] = value return property(getter, setter)
然后, 之前的示例代碼可以簡化為:
class Item(): def __init__(self, category, count, price): self.__cat = category # attribute self.count = count # property self.price = price # property cat = readonly_prop('__cat') count = positive_mutable_prop('count') price = positive_mutable_prop('price')
這樣一來, 在保證代碼簡潔的前提下實現(xiàn)了訪問控制和合法性驗證.
property不會被instance attribute覆蓋
之前在Python對象的屬性訪問過程一文中展示了attribute的解析過程, 從中知道class attribute可以被instance attribute覆蓋:
class Foo(): name = 'Foo' foo = Foo() foo.name = 'foo' codes = ['Foo.name', 'foo.name'] for code in codes: print(code, '=', eval(code))
輸出為:
Foo.name = Foo
foo.name = foo
但在property身上不會發(fā)生這種事情:
class Foo(): @property def name(self): return 'Foo' foo = Foo() foo.__dict__['name'] = 'foo'# 已經(jīng)不能通過foo.name賦值了 codes = ['Foo.name', 'foo.name'] for code in codes: print(code, '=', eval(code))
輸出:
Foo.name = <property object at 0x7fd135e7ecc8>
foo.name = Foo
至少可以看出兩點:
1. 通過class Foo訪問Foo.name得到的是property對象, 而非property值.
2. 訪問 foo.name時返回的是Foo.name的property值. 究其原因, 是因為在屬性解析過程中, property的優(yōu)先級是最高的.
總結(jié)
1.Python的attribute與property不同:
attribute: data attribute + method
property: replace attribute with access control methods like getter/setter, for security reasons.
2.可以通過多種方式定義property:
@property
property(getter, setter)
property factory
3.property在屬性解析時的優(yōu)先級最高, 不會被instance attribute覆蓋.
以上這篇Python 從attribute到property詳解就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
- python中關(guān)于property的最詳細(xì)使用方法
- python中@property的作用和getter setter的解釋
- Python特殊屬性property原理及使用方法解析
- 屬性與 @property 方法讓你的python更高效
- Python內(nèi)置函數(shù)property()如何使用
- 一文詳述 Python 中的 property 語法
- Python如何使用@property @x.setter及@x.deleter
- Python @property及getter setter原理詳解
- Python @property裝飾器原理解析
- Python 中@property的用法詳解
- python中property和setter裝飾器用法
- python property的使用技巧分享
相關(guān)文章
Python調(diào)用ctypes使用C函數(shù)printf的方法
這篇文章主要介紹了Python調(diào)用ctypes使用C函數(shù)printf,需要的朋友可以參考下2017-08-08Python閉包之返回函數(shù)的函數(shù)用法示例
這篇文章主要介紹了 Python閉包之返回函數(shù)的函數(shù)用法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-01-01anaconda安裝pytorch1.7.1和torchvision0.8.2的方法(親測可用)
這篇文章主要介紹了anaconda安裝pytorch1.7.1和torchvision0.8.2的方法(親測可用),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02Python報錯SyntaxError:unexpected?EOF?while?parsing的解決辦法
在運行或編寫一個程序時常會遇到錯誤異常,這時python會給你一個錯誤提示類名,告訴出現(xiàn)了什么樣的問題,下面這篇文章主要給大家介紹了關(guān)于Python報錯SyntaxError:unexpected?EOF?while?parsing的解決辦法,需要的朋友可以參考下2022-07-07Python如何獲得百度統(tǒng)計API的數(shù)據(jù)并發(fā)送郵件示例代碼
這篇文章主要給大家介紹了關(guān)于Python如何獲得百度統(tǒng)計API的數(shù)據(jù)并發(fā)送郵件的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01Python中面向?qū)ο竽銘?yīng)該知道的一下知識
這篇文章主要介紹了Python中面向?qū)ο竽銘?yīng)該知道的一下知識,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-07-07Python實現(xiàn)擴展內(nèi)置類型的方法分析
這篇文章主要介紹了Python實現(xiàn)擴展內(nèi)置類型的方法,結(jié)合實例形式分析了Python嵌入內(nèi)置類型擴展及子類方式擴展的具體實現(xiàn)技巧,需要的朋友可以參考下2017-10-10Python網(wǎng)絡(luò)爬蟲項目:內(nèi)容提取器的定義
本篇文章主要介紹了Python網(wǎng)絡(luò)爬蟲項目,這能有效的節(jié)省程序員的時間,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2016-10-10