Python3中的特性-----Property介紹
Python的Property詳細檔案
今天我們就來好好聊聊Python3里面的Property
特性的引入
特性和屬性的區(qū)別是什么?
在python 中 屬性 這個 實例方法, 類變量 都是屬性.
在python 中 數(shù)據(jù)的屬性 和處理數(shù)據(jù)的方法 都可以叫做 屬性.
class Animal:
name = 'animal'
def bark(self):
print('bark')
pass
@classmethod
def sleep(cls):
print('sleep')
pass
@staticmethod
def add():
print('add')
在命令行里面執(zhí)行
>>> animal = Animal()
>>> animal.add()
add
>>> animal.sleep()
sleep
>>> animal.bark()
bark
>>> hasattr(animal,'add') #1
True
>>> hasattr(animal,'sleep')
True
>>> hasattr(animal,'bark')
True
可以看出#1 animal 中 是可以拿到 add ,sleep bark 這些屬性的.
特性: property 這個是指什么? 在不改變類接口的前提下使用
存取方法 (即讀值和取值) 來修改數(shù)據(jù)的屬性.
什么意思呢?
就是通過 obj.property 來讀取一個值,
obj.property = xxx ,來賦值
還以上面 animal 為例:
class Animal:
@property
def name(self):
print('property name ')
return self._name
@name.setter
def name(self, val):
print('property set name ')
self._name = val
@name.deleter
def name(self):
del self._name
這個時候 name 就是了特性了.
>>> animal = Animal()
>>> animal.name='dog'
property set name
>>> animal.name
property name
'dog'
>>>
>>> animal.name='cat'
property set name
>>> animal.name
property name
'cat'
肯定有人會疑惑,寫了那么多的代碼, 還不如直接寫成屬性呢,多方便.
比如這段代碼:
>>> class Animal:
...? ? ?name=None
...
>>> animal = Animal()
>>> animal.name
>>> animal.name='frank'
>>> animal.name
'frank'
>>> animal.name='chang'
>>> animal.name
'chang'
>>> animal.name=250
>>> animal
>>> animal.name
250
>>> type(animal.name)
這里給?animal.name?賦值成 250, 程序從邏輯上來說 沒有問題. 但其實這樣賦值是毫無意義的.
我們一般希望 不允許這樣的賦值,就希望 給出?報錯或者警告?之類的.
animal= Animal()
animal.name=100
property set name
Traceback (most recent call last):
File "", line 1, in
File "", line 13, in name
ValueError: expected val is str
其實當name 變成了property 之后,我們就可以對name 賦值 進行控制. 防止一些非法值變成對象的屬性.
比如說name 應(yīng)該是這個字符串, 不應(yīng)該是數(shù)字 這個時候 就可以在 setter 的時候 進行判斷,來控制 能否賦值.
要實現(xiàn)上述的效果, 其實也很簡單 setter 對value進行判斷就好了.
class Animal:
@property
def name(self):
print('property name ')
return self._name
@name.setter
def name(self, val):
print('property set name ')
# 這里 對 value 進行判斷
if not isinstance(val,str):
raise? ValueError("expected val is str")
self._name = val
感受到 特性的魅力了吧,可以通過 賦值的時候 ,對 值進行校驗,方式不合法的值,進入到對象的屬性中. 下面 看下 如何設(shè)置只讀屬性, 和如何設(shè)置讀寫 特性.
假設(shè) 有這樣的一個需求 , 某個類的屬性一個初始化之后 就不允許 被更改,這個 就可以用特性這個問題 , 比如一個人身高是固定, 一旦 初始化后,就不允許改掉.
設(shè)置只讀特性
class Frank:
def __init__(self, height):
self._height = height
@property
def height(self):
return self._height
>>> frank = Frank(height=100)
>>> frank.height
100
>>> frank.height =150
Traceback (most recent call last):
File "", line 1, in
AttributeError: can't set attribute
這里初始化 frank后 就不允許 就修改 這個 height 這個值了. (實際上也是可以修改的)
設(shè)置讀寫特性
class Frank:
def __init__(self, height):
self._height = height
@property
def height(self):
return self._height
@height.setter
def height(self, value):
"""
給特性賦值
"""
self._height = value
比如對人的身高 在1米 到 2米之間 這樣的限制
>>> frank = Frank(height=100)
>>> frank.height
100
>>> frank.height=165
>>> frank.height
165
對特性的合法性進行校驗
class Frank:
def __init__(self, height):
self.height = height? # 注意這里寫法
@property
def height(self):
return self._height
@height.setter
def height(self, value):
"""
判斷邏輯 屬性的處理邏輯
定義 了 setter 方法之后就? 修改 屬性 了.
判斷 屬性 是否合理 ,不合理直接報錯. 阻止賦值,直接拋異常
:param value:
:return:
"""
if not isinstance(value, (float,int)):
raise ValueError("高度應(yīng)該是 數(shù)值類型")
if value < 100 or value > 200:
raise ValueError("高度范圍是100cm 到 200cm")
self._height = value
>>> frank = Frank(100)
>>> frank.height
100
>>> frank.height='aaa'
Traceback (most recent call last):
File "", line 1, in
File "", line 21, in height
ValueError: 高度應(yīng)該是 數(shù)值類型
>>> frank.height=250
Traceback (most recent call last):
File "", line 1, in
File "", line 23, in height
ValueError: 高度范圍是100cm 到 200cm
這樣就可以進行嚴格的控制, 一些特性的方法性 ,通過寫setter 方法 來保證數(shù)據(jù) 準確性,防止一些非法的數(shù)據(jù)進入到實例中.
Property是什么?
實際上是一個類 , 然后就是一個裝飾器. 讓一個方法變成 一個特性.
其實特性模糊了方法和數(shù)據(jù)的界限.
方法是可調(diào)用的屬性 , 而property 是 可定制化的'屬性'?. 一般方法的名稱是一個動詞(行為). 而特性property 應(yīng)該是名詞.
如果我們一旦確定了屬性不是動作, 我們需要在標準屬性 和 property 之間做出選擇 .
一般來說你如果要控制 property 的 訪問過程,就要用property. 否則用標準的屬性即可 .
attribute屬性和property特性的區(qū)別在于當property被讀取, 賦值, 刪除時候, 自動會執(zhí)行某些特定的動作.
peroperty 詳解
特性都是類屬性,但是特性管理的其實是實例屬性的存取。
----- 摘自 fluent python
下面的例子來自 fluent python
看一下幾個例子來說明幾個特性和屬性區(qū)別
>>> class Class:
"""
data 數(shù)據(jù)屬性和 prop 特性。
"""
...? ? ?data = 'the class data attr'
...
...? ? ?@property
...? ? ?def prop(self):
...? ? ? ? ?return 'the prop value'
...
>>>
>>> obj= Class()
>>> vars(obj)
{}
>>> obj.data
'the class data attr'
>>> Class.data
'the class data attr'
>>> obj.data ='bar'
>>> Class.data
'the class data attr'
實例屬性遮蓋類的數(shù)據(jù)屬性 , 就是說如果obj.data重新修改了 , 類的屬性不會被修改 .
下面嘗試obj 實例的prop特性
>>> Class.prop
>>> obj.prop
'the prop value'
>>> obj.prop ='foo'
Traceback (most recent call last):
File "", line 1, in
AttributeError: can't set attribute
>>> obj.__dict__['prop'] ='foo'
>>> vars(obj)
{'data': 'bar', 'prop': 'foo'}
>>> obj.prop? #1
'the prop value'
>>> Class.prop ='frank'
>>> obj.prop
'foo'
我嘗試修改 obj.prop 會直接報錯 ,這個容易理解, 因為property沒有實現(xiàn) setter 方法 . 我直接修改obj.dict
然后 在#1的地方, 發(fā)現(xiàn) 還是正常調(diào)用了特性 ,而沒有屬性的值.
當我改變Class.prop變成一個屬性的時候 .
再次調(diào)用obj.prop才調(diào)用到了 實例屬性.
再看一個例子 添加 特性
class Class:
data = 'the class data attr'
@property
def prop(self):
return 'the prop value'
>>> obj.data
'bar'
>>> Class.data
'the class data attr'
# 把類的data 變成 特性
>>> Class.data = property(lambda self:'the "data" prop value')
>>> obj.data
'the "data" prop value'
>>> del Class.data
>>> obj.data
'bar'
>>> vars(obj)
{'data': 'bar', 'prop': 'foo'}
改變 data 變成特性后, obj.data也改變了. 刪除這個特性的時候 , obj.data 又恢復(fù)了.
本節(jié)的主要觀點是, obj.attr 這樣的表達式不會從 obj 開始尋找 attr,而是從
obj.__class__ 開始,而且,僅當類中沒有名為 attr 的特性時, Python 才會在 obj 實
例中尋找。這條規(guī)則適用于特性 .
property 實際上 是一個類
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
pass
# known special case of property.__init__
完成 的要實現(xiàn)一個特性 需要 這 4個參數(shù), get , set ,del , doc 這些參數(shù).但實際上大部分情況下,只要實現(xiàn) get ,set 即可.
Property的兩種寫法
第一種寫法
使用 裝飾器 property 來修飾一個方法
# 方法1
class Animal:
def __init__(self, name):
self._name = name
@property
def name(self):
print('property name ')
return self._name
@name.setter
def name(self, val):
print('property set name ')
if not isinstance(val, str):
raise ValueError("expected val is str")
self._name = val
@name.deleter
def name(self):
del self._name
第二種寫法
直接 實現(xiàn) set get delete 方法 即可, 通過property 傳入 這個參數(shù)
# 方法二
class Animal2:
def __init__(self, name):
self._name = name
def _set_name(self, val):
if not isinstance(val, str):
raise ValueError("expected val is str")
self._name = val
def _get_name(self):
return self._name
def _delete_name(self):
del self._name
name = property(fset=_set_name, fget=_get_name,fdel= _delete_name,doc= "name 這是特性描述")
if __name__ == '__main__':
animal = Animal2('dog')
>>> animal = Animal2('dog')
>>>
>>> animal.name
'dog'
>>> animal.name
'dog'
>>> help(Animal2.name)
Help on property:
name 這是特性描述
>>> animal.name='cat'
>>> animal.name
'cat'
替換背景的新方法:選擇背景圖設(shè)置后,左邊模板、、剪貼板和圖庫都可以點擊,根據(jù)選擇的內(nèi)容,設(shè)置背景到當前的編輯布局上。如果選擇了的是圖片,都把圖片設(shè)置被背景圖。如果選擇了一個帶背景的模板,就把這個模板的背景給復(fù)制過來。
常見的一些例子
A、對一些值進行合法性校驗.
在舉一個小例子 比如 有一個貨物, 有重量 和 價格 ,需要保證 這兩個屬性是正數(shù) 不能是 0 , 即>0 的值
基礎(chǔ)版本的代碼:
class Goods:
def __init__(self, name, weight, price):
"""
:param name: 商品名稱
:param weight:? 重量
:param price: 價格
"""
self.name = name
self.weight = weight
self.price = price
def __repr__(self):
return f"{self.__class__.__name__}(name={self.name},weight={self.weight},price={self.price})"
@property
def weight(self):
return self._weight
@weight.setter
def weight(self, value):
if value < 0:
raise ValueError(f"expected value > 0, but now value:{value}")
self._weight = value
@property
def price(self):
return self._price
@price.setter
def price(self, value):
if value < 0:
raise ValueError(f"expected value > 0, but now value:{value}")
self._price = value
>>> goods = Goods('apple', 10, 30)
...
>>> goods
Goods(name=apple,weight=10,price=30)
>>> goods.weight
10
>>> goods.weight=-10
Traceback (most recent call last):
File "", line 1, in
File "", line 26, in weight
ValueError: expected value > 0, but now value:-10
>>> goods.price
30
>>> goods.price=-3
Traceback (most recent call last):
File "", line 1, in
File "", line 37, in price
ValueError: expected value > 0, but now value:-3
>>> goods
Goods(name=apple,weight=10,price=30)
>>> goods.price=20
>>> goods
Goods(name=apple,weight=10,price=20)
代碼 可以正常的判斷出來 ,這些非法值了. 這樣寫 有點問題是什么呢? 就是 發(fā)現(xiàn) weight ,price 判斷值的邏輯 幾乎是一樣的代碼… 都是判斷是 大于 0 嗎? 然而我卻寫了 兩遍相同的代碼 .
優(yōu)化后的代碼
有沒有更好的解決方案呢?
是有的, 我們可以寫一個 工廠函數(shù) 來返回一個property , 這實際上是兩個 property 而已.
下面 就是工廠函數(shù) ,用來生成一個 property 的.
def validate(storage_name):
"""
用來驗證 storage_name 是否合法性 , weight? , price
:param storage_name:
:return:
"""
pass
def _getter(instance):
return instance.__dict__[storage_name]
def _setter(instance, value):
if value < 0:
raise ValueError(f"expected value > 0, but now value:{value}")
instance.__dict__[storage_name] = value
return property(fget=_getter, fset=_setter)
class Goods:
weight = validate('weight')
price = validate('price')
def __init__(self, name, weight, price):
"""
:param name: 商品名稱
:param weight:? 重量
:param price: 價格
"""
self.name = name
self.weight = weight
self.price = price
def __repr__(self):
return f"{self.__class__.__name__}(name={self.name},weight={self.weight},price={self.price})"
>>> goods = Goods('apple', 10, 30)
>>> goods.weight
10
>>> goods.weight=-10
Traceback (most recent call last):
File "", line 1, in
File "", line 16, in _setter
ValueError: expected value > 0, but now value:-10
>>> goods
Goods(name=apple,weight=10,price=30)
>>> goods.price=-2
Traceback (most recent call last):
File "", line 1, in
File "", line 16, in _setter
ValueError: expected value > 0, but now value:-2
>>> goods
Goods(name=apple,weight=10,price=30)
B、緩存某些值
...?from urllib.request import urlopen
... class WebPage:
...
...? ? ?def __init__(self, url):
...? ? ? ? ?self.url = url
...
...? ? ? ? ?self._content = None
...
...? ? ?@property
...? ? ?def content(self):
...? ? ? ? ?if not self._content:
...? ? ? ? ? ? ?print("Retrieving new page")
...? ? ? ? ? ? ?self._content = urlopen(self.url).read()[0:10]
...
...? ? ? ? ?return self._content
...
>>>
>>>
>>> url = 'http://www.baidu.com'
>>> page = WebPage(url)
>>>
>>> page.content
Retrieving new page
b'
>>> page.content
b'
>>> page.content
b'
可以看出 第一次調(diào)用了 urlopen 從網(wǎng)頁中讀取值, 第二次就沒有調(diào)用urlopen 而是直接返回content 的內(nèi)容.
總結(jié)
python的特性算是python的高級語法,不要因為到處都要用這個特性的語法.實際上大部分情況是用不到這個語法的. 如果代碼中,需要對屬性進行檢查就要考慮用這樣的語法了. 希望你看完之后不要認為這種語法非常常見, 事實上不是的. 其實更好的做法對屬性檢查可以使用描述符來完成. 描述符是一個比較大的話題,本文章暫未提及,后續(xù)的話,可能 會寫一下 關(guān)于描述的一些用法 ,這樣就能更好的理解python,更加深入的理解python.
參考文檔
fluent python(流暢的Python)
Python3面向?qū)ο缶幊?/p>
Python為什么要使用描述符?
https://juejin.im/post/5cc4fbc0f265da0380437706
https://tech-summary.readthedocs.io/en/latest/python_property.html
"歡迎關(guān)注,了解更多python內(nèi)幕"
Python
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。