一、funtools.wraps裝飾器
1.未使用wraps裝飾器
Python裝飾器在裝飾其他函數的的時候,被裝飾後的函數的函數名等屬性會發生改變。例如:
def wrapper(func):
def inner(*args, **kwargs):
"""這是裝飾器的文檔字元串"""
return func(*args, **kwargs)
return inner
@wrapper
def buying(self):
"""這是被裝飾函數的文檔字元串"""
print(f"商品: {self.goods}, 重量: {self.weight}, 價格: {self.price}")
print(buying.__name__)
print(buying.__doc__)
'''
inner
這是裝飾器的文檔字元串
'''
運作結果如下:
此時,被wrapper裝飾器裝飾的函數buying,函數名稱已經變為了inner,文檔字元串也變成了inner函數的文檔字元串。
2.使用了wraps裝飾器
為了避免這種情況的發生,Python的functools包中提供了一個叫wraps的裝飾器來消除這樣的副作用。在定義裝飾器的時候,可以在裝飾器的内函數之前加上functools的wraps,這樣它就能保留被裝飾函數的名稱和函數屬性。
from functools import wraps
def wrapper(func):
@wraps(func)
def inner(*args, **kwargs):
"""這是裝飾器的文檔字元串"""
return func(*args, **kwargs)
return inner
print(buying.__name__)
print(buying.__doc__)
運作結果如下:
内函數inner在被functools.wraps裝飾後,此時再裝飾其他函數時,被裝飾函數的函數名、文檔字元串等函數屬性就不會再受到影響。
二、property裝飾器
@property是python的一種裝飾器,是用來修飾方法的,它能夠将方法轉換為相同名稱的隻讀屬性,可以與所定義的屬性配合使用,這樣可以防止屬性被修改;另外,@property裝飾器也可以用來建立隻讀屬性。
注意事項:
- 調用不帶property的方法時,需使用正常的方法調用方式,方法後面需要加();
- 調用帶property的方法時,方法後面不需要加();
1.将方法轉換為屬性
class Property(object):
def __init__(self):
self.name = "test_property"
def without_property(self):
return "without_property"
@property
def with_property(self):
return "with property"
ppt = Property()
print(ppt.without_property())
print(ppt.with_property)
print(ppt.name)
'''
運作結果:
without_property
with property
test_property
'''
通過運作結果可以看出,with_property在被@property裝飾器裝飾後,已經變成了Property類中的一個屬性,被調用時可以直接通過‘對象名.屬性名’進行調用,後面不必再帶上括号。
2.建立隻讀屬性
用@property裝飾器将phone_number函數轉換為屬性,函數内傳回私有屬性self.__phone_number, 使用者進行屬性調用的時候,直接調用phone_number即可,而不用知道屬性名__phone_number,是以使用者無法更改屬性,進而達到了保護類中屬性的目的。
class Property(object):
def __init__(self):
self.name = "test_property"
self.__phone_number = 15252188888
@property
def phone_number(self):
return self.__phone_number
print(ppt.phone_number)
'''
15252188888
'''
3.設定和擷取屬性值
1)通過函數設定和擷取屬性值
# 通過函數設定屬性值
class Setter(object):
def get_score(self):
return self._score
def set_score(self, value):
if not isinstance(value, int):
raise ValueError("value must be integer!")
if value < 0 or value > 100:
raise ValueError("value must between 0~100~")
self._score = value
setter = Setter()
setter.set_score(88) # 通過set_score()方法設定屬性值
print(setter.get_score()) # 通過get_score()方法擷取屬性值
'''
88
'''
2)通過@property設定和擷取屬性值
如下,在SetterAttribute類中定義一個score方法,通過@property裝飾器裝飾器,使其變為了屬性;此時的score作為一個屬性存在,故第二個score方法也是一個屬性,通過@score.setter裝飾後,傳入的value會作為score的屬性值。是以在SetterAttribute執行個體化後,sa.score既可以設定屬性值,也可以讀取屬性值。但需要先傳入score的屬性值,此時才能存在score這個屬性,才能擷取到其屬性值。
class SetterAttribute(object):
# 通過@property裝飾器将score()方法轉換為屬性
@property
def score(self):
return self._score
# 通過@score.setter設定score的屬性值
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError("value must be integer!")
if value < 0 or value > 100:
raise ValueError("value must between 0~100~")
self._score = value
sa = SetterAttribute()
sa.score = 99
print(sa.score)
'''
99
'''
三、多裝飾器的執行順序
一個函數可以同時被多個裝飾器裝飾,它的執行順序是從裡到外,最先調用最裡層的裝飾器,最後調用最外層的裝飾器。
# 多個裝飾器執行順序
def decorator_a(func):
print('Get in A')
def inner_a(*args, **kwargs):
print('Get in inner_a')
return func(*args, **kwargs)
return inner_a
def decorator_b(func):
print('Get in B')
def inner_b(*args, **kwargs):
print('Get in inner_b')
return func(*args, **kwargs)
return inner_b
@decorator_b
@decorator_a
def f(x):
print('fun a')
return x * 2
f(2)
'''
運作結果:
Get in A
Get in B
Get in inner_b
Get in inner_a
fun a
'''
執行過程分析:
- 執行f()函數會先調用最裡層的裝飾器@decorator_a,再調用最外層的裝飾器@decorator_b;
- 執行@decorator_a時,會先執行語句print('Get in A')列印得到'Get in A',接着得到傳回值inner_a函數,由于隻是接收了這個函數、并沒有調用它,是以不會執行inner_a函數的内部邏輯;
- 接着會執行裝飾器@decorator_b的邏輯,列印‘Get in B’,執行decorator_b的時候會得到傳回值函數inner_b;
- 用f來接收inner_b函數,調用f(),也就等于調用了inner_b(),f=decorator_b(decorator_a(f))、f()=inner_b(),進而執行inner_b的内容:列印'Get in inner_b';
- 執行完inner_b函數的内容會再繼續執行inner_a函數的内容:列印'Get in inner_a',最後再傳回func(*args, **kwargs),也就是執行f(2),列印'fun a'。
小結
1.裝飾器在裝飾其他函數的的時候,被裝飾後的函數的函數名等屬性會随着裝飾器而發生改變,可以通過functools.wraps裝飾内函數inner,進而避免此類情況的産生。
2.@property是python的一種裝飾器,它既可以将類中的方法變為屬性,也可以設定隻讀屬性。
3.一個函數可以同時被多個裝飾器裝飾,它的執行順序是從裡到外,最先調用最裡層的裝飾器,最後調用最外層的裝飾器。