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. MOTCF 没时间解释了 条件竞争漏洞

    moctf 没时间解释了 条件竞争漏洞 题目链接 条件竞争: 在本题目中,上传文件的时候服务器无条件的接收任何类型的文件,但是你上传之后服务器会给你的文件内容修改为too slow. 比如你上传了一句 ...

  2. SDRAM学习(一)之刷新心得

    本篇博文共有两种刷新方式 SDRAM数据手册给出每隔64ms就要将所有行刷新一遍, 因此每隔64_000_000 ns/2^12=15625ns 就要刷新一次.(因为一个L-Bank的行是12位,所以 ...

  3. hnust CZJ-Superman

    问题 B: CZJ-Superman 时间限制: 1 Sec  内存限制: 128 MB提交: 636  解决: 87[提交][状态][讨论版] 题目描述 “那是只鸟?那是飞机?那是——超人!” 程序 ...

  4. oracle function用法

    函数调用限制1.SQL语句中只能调用存储函数(服务器端),而不能调用客户端的函数2.SQL只能调用带有输入参数,不能带有输出,输入输出函数3.SQL不能使用PL/SQL的特有数据类型(boolean, ...

  5. Tomcat网站根目录的配置

    在</Host>前插入: <Host> … … <Context path="" docBase="E:\Users\Administrat ...

  6. iOS 代理为啥要用weak修饰?

    在开发中我们经常使用代理,或自己写个代理,而代理属性都用weak(assign)修饰,看过有些开发者用strong(retain),但并没发现有何不妥,也不清楚weak(assign)与strong( ...

  7. Matcher类详解2-group

    Matcher.group是针对()来说的,group(0)就是指的整个串,group(1) 指的是第一个括号里的东西即匹配的第一个子表达式,group(2)指的第二个括号里的东西即匹配的第二个子表达 ...

  8. C语言中的内存相关问题

    内存是用来存储数据与程序的,对我们写程序来说非常重要.所以内存对程序来说几乎是本质需求.越简单的程序需要越少的内存,而越庞大越复杂的程序需要更多的内存. 注意:在嵌入式系统中有ROM和RAM两类内存, ...

  9. 谈谈JS中的原型

    不知道大家对JS中的原型理解的怎么样,我想如果大家对JS中的原型对象以及prototype属性十分熟悉的话对后面原型链以及继承的理解会十分的容易,这里想和大家分享自己对其的理解,请先看下面这段代码O( ...

  10. Linux定时关机

    sudo shutdown -h +120 :两小时后关机sudo shutdown -h 23:00 :表示在23点定时关机 一.shutdown命令关机 各参数功能: -c 取消前一个shutdo ...