天天看點

Python裝飾器3-funtools.wraps與property裝飾器

作者:大剛測試開發實戰

一、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
這是裝飾器的文檔字元串
'''           

運作結果如下:

Python裝飾器3-funtools.wraps與property裝飾器

此時,被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__)           

運作結果如下:

Python裝飾器3-funtools.wraps與property裝飾器

内函數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
'''           

執行過程分析:

  1. 執行f()函數會先調用最裡層的裝飾器@decorator_a,再調用最外層的裝飾器@decorator_b;
  2. 執行@decorator_a時,會先執行語句print('Get in A')列印得到'Get in A',接着得到傳回值inner_a函數,由于隻是接收了這個函數、并沒有調用它,是以不會執行inner_a函數的内部邏輯;
  3. 接着會執行裝飾器@decorator_b的邏輯,列印‘Get in B’,執行decorator_b的時候會得到傳回值函數inner_b;
  4. 用f來接收inner_b函數,調用f(),也就等于調用了inner_b(),f=decorator_b(decorator_a(f))、f()=inner_b(),進而執行inner_b的内容:列印'Get in inner_b';
  5. 執行完inner_b函數的内容會再繼續執行inner_a函數的内容:列印'Get in inner_a',最後再傳回func(*args, **kwargs),也就是執行f(2),列印'fun a'。

小結

1.裝飾器在裝飾其他函數的的時候,被裝飾後的函數的函數名等屬性會随着裝飾器而發生改變,可以通過functools.wraps裝飾内函數inner,進而避免此類情況的産生。

2.@property是python的一種裝飾器,它既可以将類中的方法變為屬性,也可以設定隻讀屬性。

3.一個函數可以同時被多個裝飾器裝飾,它的執行順序是從裡到外,最先調用最裡層的裝飾器,最後調用最外層的裝飾器。