Python语言装饰器进阶之二
小标 2019-01-02 来源 : 阅读 707 评论 0

摘要:本文主要向大家介绍了Python语言装饰器进阶之二,通过具体的内容向大家展示,希望对大家学习Python语言有所帮助。

本文主要向大家介绍了Python语言装饰器进阶之二,通过具体的内容向大家展示,希望对大家学习Python语言有所帮助。
举个栗子

def hello():
    print('Hello, World.')

print(dir(hello))
结果如下:
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
其中:
__name__: 代表方法的名字
__doc__: 代表方法的字符串文档(实际上就是"""..."""这种形式的注释)
__moudle__: 方法所属模块的名字
__dict__: 属性字典(这个属性在面向对象编程时很重要,用好了能大大节约Python内存的开支)
__defaults__: 方法参数中,默认参数的值(实际上Python方法的默认参数是创建方法对象的时候就存储在这里的)
...
等等
以下面一个为例:
def hello(numa, numb=1, numc=[]):
    """
        Print numa, numb, numc.
    """
    print(numa, numb, numc)
    return True

print(hello.__name__)
print(hello.__doc__)
print(hello.__module__)
print(hello.__dict__)
print(hello.__defaults__)
结果如下:
hello

        Print numa, numb, numc.

__main__
{}
(1, [])
[Finished in 0.1s]
我们可以看到,__doc__实际上就是,方法里面用三引号包裹起来的注释。而__dict__则是方法属性的字典,我们这个方法对象并没有任何的属性,所以说他是空的。
我们给方法增加一个属性:
def hello():
    print('Hello, World.')

hello.name = 'XiaoMing'
print(hello.__dict__)
结果如下:
{'name': 'XiaoMing'}
甚至我们还可以这样:
def hello():
    print('Hello, World.')

hello.__dict__['name'] = 'XiaoMing'
print(hello.name)
结果如下:
XiaoMing
同样的,我们的__defaults__属性本身是一个元组,元组是不可变类型的数据结构。但是现在我们的numc使用的是一个列表,那我们是不是可以改变方法默认参数的值呢:
def hello(numa, numb=1, numc=[]):
    print(numa, numb, numc)

# 一共两个元素,下标1的元素就代表了我们的numc所对应的列表
hello.__defaults__[1].append('Hello')
hello(100)
结果如下:
100 1 ['Hello']
所以,在我们方法的默认参数上面,应该避免使用数组这种可变类型的数据结构。因为Python本身把__defaults__属性设置为元组,那就是希望人们无法去修改它,如果我们使用了可变类型的数据结构就违背了Python的本意。
说了这么多废话,没有进入主题,来看装饰器对方法元数据的影响,上一章的例子:
def add_cache(func):
    """
        This add_cache
    """
    cache = {}
    def wrap(*args):
        """
            This wrap
        """
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrap

@add_cache
def fibonacci(n):
    """
        This fibonacci
    """
    if n <= 1:
        return 1
    return fibonacci(n - 1) + fibonacci(n - 2)

# 实际上返回的对象是wrap方法的对象,所以得到的也是wrap方法的元数据
print(fibonacci.__name__)
print(fibonacci.__doc__)
结果如下:
wrap

            This wrap
  如何保存被装饰方法的元数据不被改变
这样就存在一个问题,我们的方法被装饰以后,原来的某些东西,我们无法访问了,这肯定是不行的,那我们必须想办法能够在装饰以后还保持某些元数据是原来方法的元数据。
简单思考以后我们可以这样做:
def add_cache(func):
    """
        This add_cache
    """
    cache = {}
    def wrap(*args):
        """
            This wrap
        """
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    # 返回之前,我们修改这个对象的元数据让它等于原方法的元数据
    wrap.__name__ = func.__name__
    wrap.__doc__ = func.__doc__
    return wrap

@add_cache
def fibonacci(n):
    """
        This fibonacci
    """
    if n <= 1:
        return 1
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci.__name__)
print(fibonacci.__doc__)
结果和我们设想的一样:
fibonacci

        This fibonacci
虽然是实现了我们的目的,但是这么做非常的不优雅,有没有比较优雅的做法呢。
我们可以使用Python标准库functools下的update_wrapper来实现:
from functools import update_wrapper

def add_cache(func):
    """
        This add_cache
    """
    cache = {}
    def wrap(*args):
        """
            This wrap
        """
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    # 使用update_wrapper来进行替换
    update_wrapper(wrap, func, assigned=('__name__',), updated=('__dict__',))
    return wrap

@add_cache
def fibonacci(n):
    """
        This fibonacci
    """
    if n <= 1:
        return 1
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci.__name__)
print(fibonacci.__doc__)
结果如下:
fibonacci

            This wrap
解析:
update_wrapper:
    第一个参数:代表装饰方法
    第二个参数:代表被装饰方法
    assigned:代表那些属性是需要替换的,不写的就代表不替换。(可以省略不写assigned=)
    updated:代表哪些属性需要合并,因为原方法有一些属性,装饰方法也有一些属性,所以他们两个里面的内容,需要合并在一起。(同样可以省略不写updated=)

需要注意的是呢,update_wrapper中的assigned和updated都有一个默认的参数,来看一下这个方法的源代码:
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotations__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):
所以,即使我们不指定后两个参数,也是可以实现我们的需求的。
还有一种更方便的做法,Python为我们提供了一个装饰器:@wraps(),同样在functools下面,这个装饰器是用在装饰方法上面的,它接收三个参数,分别是被装饰方法,assigned和updated。当然,后两个参数都有默认值,同样是WRAPPER_ASSIGNMENTS和WRAPPER_UPDATES,所以我们可以这样:
from functools import wraps

def add_cache(func):
    """
        This add_cache
    """
    cache = {}
    # 使用装饰器来保存被装饰方法的元数据
    @wraps(func)
    def wrap(*args):
        """
            This wrap
        """
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrap

@add_cache
def fibonacci(n):
    """
        This fibonacci
    """
    if n <= 1:
        return 1
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci.__name__)
print(fibonacci.__doc__)
结果如下:
fibonacci

        This fibonacci
实际上在@wraps()这个装饰器内部,使用的就是update_wrapper()方法。

本文由职坐标整理并发布,希望对同学们学习Python有所帮助,更多内容请关注职坐标编程语言Python频道!

本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程