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()方法。

END

作者: 秋名山车神 
链接:http://www.imooc.com/article/16248
来源:慕课网

(转)python装饰器二的更多相关文章

  1. [python 基础]python装饰器(二)带参数的装饰器以及inspect.getcallargs分析

    带参数的装饰器理解无非记住两点: 1.本质不过在基本的装饰器外面再封装一层带参数的函数 2.在使用装饰器语法糖的时候与普通装饰器不同,必须要加()调用,且()内的内容可以省略(当省略时,admin默认 ...

  2. Python学习:11.Python装饰器讲解(二)

    回顾 上一节我们进行了Python简单装饰器的讲解,但是python的装饰器还有一部分高级的使用方式,这一节就针对python装饰器高级部分进行讲解. 为一个函数添加多个装饰器 今天,老板又交给你一个 ...

  3. Python之装饰器(二)

    以前你有没有这样一段经历:很久之前你写过一个函数,现在你突然有了个想法就是你想看看,以前那个函数在你数据集上的运行时间是多少,这时候你可以修改之前代码为它加上计时的功能,但是这样的话是不是还要大体读读 ...

  4. python函数知识七 闭包、装饰器一(入门)、装饰器二(进阶)

    21.闭包 闭包:在嵌套函数内,使用非全局变量(且不使用本层变量) 闭包的作用:1.保证数据的安全性(纯洁度).2.装饰器使用 .__closure__判断是否是闭包 def func(): a = ...

  5. Python装饰器详解

    python中的装饰器是一个用得非常多的东西,我们可以把一些特定的方法.通用的方法写成一个个装饰器,这就为调用这些方法提供一个非常大的便利,如此提高我们代码的可读性以及简洁性,以及可扩展性. 在学习p ...

  6. Python装饰器由浅入深

    装饰器的功能在很多语言中都有,名字也不尽相同,其实它体现的是一种设计模式,强调的是开放封闭原则,更多的用于后期功能升级而不是编写新的代码.装饰器不光能装饰函数,也能装饰其他的对象,比如类,但通常,我们 ...

  7. 一篇关于Python装饰器的博文

    这是一篇关于python装饰器的博文 在学习python的过程中处处受阻,之前的学习中Python的装饰器学习了好几遍也没能真正的弄懂.这一次抓住视频猛啃了一波,就连python大佬讲解装饰器起来也需 ...

  8. python 装饰器 一篇就能讲清楚

    装饰器一直是我们学习python难以理解并且纠结的问题,想要弄明白装饰器,必须理解一下函数式编程概念,并且对python中函数调用语法中的特性有所了解,使用装饰器非常简单,但是写装饰器却很复杂.为了讲 ...

  9. 如何理解Python装饰器

    如何理解Python装饰器?很多学员对此都有疑问,那么上海尚学堂python培训这篇文章就给予答复. 一.预备知识 首先要理解装饰器,首先要先理解在 Python 中很重要的一个概念就是:“函数是 F ...

随机推荐

  1. python安装pattern失败

    做文本分类需要用到pattern.en进行词形还原,安装了一上午都没有成功,mysqlclient安装失败.最后解决办法,pip install --only-binary :all: mysqlcl ...

  2. Leetcode 556.下一个更大元素III

    下一个更大元素III 给定一个32位正整数 n,你需要找到最小的32位整数,其与 n 中存在的位数完全相同,并且其值大于n.如果不存在这样的32位整数,则返回-1. 示例 1: 输入: 12 输出: ...

  3. 一小时学会用Python Socket 开发可并发的FTP服务器!!

    socket是什么 什么是socket所谓socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄.应用程序通常通过"套接字"向网络发出请求 ...

  4. [SDOI2015][bzoj3994] 约数个数和 [莫比乌斯反演]

    题面: 传送门 思路: 首先,我们需要证明一个结论:d(i*j)等于sigma(gcd(x,y)==1),其中x为i的约数,y为j的约数 对于nm的每一个质因子pi分别考虑,设n = pi^ai + ...

  5. openssl-1.0.1c交叉编译动态库(转)

    linux编译相关(13)  版权声明:本文为博主原创文章,未经博主允许不得转载. #交叉编译openssl ------直接修改Makefile新加这一行:CROSS_COMPILE= arm-un ...

  6. jQuery初级

    一.简介 定义 jQuery创始人是美国John Resig,是优秀的Javascript框架: jQuery是一个轻量级.快速简洁的javaScript库.源码戳这 jQuery对象 jQuery产 ...

  7. jquery 实践操作:div 动态嵌套页面

    此篇记录如何在指定 div 中嵌套一个页面 load() 方法: 1. 使用 $.load() 直接导入一个页面 $('#addPage_div').load("temp/handle.ht ...

  8. set基本用法-----2

    #include<cstdio> #include<iostream> #include<cstdlib> #include<cmath> #inclu ...

  9. 更改MVC3默认错误提示信息 模型验证 validation

    原文发布时间为:2011-07-20 -- 来源于本人的百度文章 [由搬家工具导入] Model Metadata and Validation Localization using Conventi ...

  10. .NET结束外部进程 C#结束外部进程

    原文发布时间为:2011-02-15 -- 来源于本人的百度文章 [由搬家工具导入] using System;namespace ConsoleApplication2{    class Prog ...