天天看点

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.一个函数可以同时被多个装饰器装饰,它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器。