Python 装饰器初探

在谈及Python的时候,装饰器一直就是道绕不过去的坎。面试的时候,也经常会被问及装饰器的相关知识。总感觉自己的理解很浅显,不够深刻。是时候做出改变,对Python的装饰器做个全面的了解了。

1. 函数装饰器

直接上代码,看看装饰器到底干了些什么?

from functools import wraps
import time def time_cost(func):
@wraps(func)
def f(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
end_time = time.time()
print(end_time - start_time)
return f @time_cost
def test(*args, **kwargs):
time.sleep(1.0) if __name__ == "__main__":
test()

上面的Python代码,运行后,会给出test函数的执行时间。代码的执行顺序大概如下,首先是将test作为值传递给time_cost函数,返回函数f,然后再调用f,这是带有time_cost装饰器的test函数的大致执行过程。

从中,不难看出,即使不使用装饰器符号,我们利用Python的语言特性,也能达成上述目的。用装饰器符号的好处是简化了代码,增加了代码的可读性。

这是一段非常简单的对函数使用装饰器的Python代码。等等,@wraps(func)是什么鬼?悄悄干了什么哇?

我们稍微修改下上述代码,结果如下:

from functools import wraps
import time def time_cost(func):
def f(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
end_time = time.time()
print(end_time - start_time) print('hello world')
return f @time_cost
def test(*args, **kwargs):
time.sleep(1.0) if __name__ == "__main__":
print(test.__name__)

发现输出了hello world,同时输出test.__name__,居然变成了f,并不是我们预期的test。根据这样的输出结果,我们不难得出,其实被装饰器time_cost修饰过的函数test本质上已经等同于time_cost(test),此时访问test.__name__实际上访问的是time_cost(test).__name__,得到的当然就是f啦。当我们加上@wraps(func),此时test.__name__变成了test

下面介绍带参数的装饰器,更加难了。在谈论带参数的装饰器之间,首先得引入一个概念,那就”闭包“。如果你以前用过脚本语言,比如JavaScript,那么一定会很熟悉闭包这个概念。下面是一个闭包样例

def add(a):
def wrapper(c):
return a + c
return wrapper if __name__ == "__main__":
add3 = add(3)
add9 = add(9)
print(add3(4) == 7)
print(add9(1) == 10)

从中可以看出,在调用add3的时候,wrapper内部还可以访问到a的值,这就是闭包的作用。理解了闭包,理解带参数的装饰器就容易多了。

from functools import wraps

def logging(level):
def outer_wrapper(func):
@wraps(func)
def inner_wrapper(*args, **kwargs):
print("[{level}]: enter function {func}()".format(
level=level,
func=func.__name__))
return func(*args, **kwargs)
return inner_wrapper
return outer_wrapper @logging(level='WARN')
def show(msg):
print('message:{}'.format(msg)) if __name__ == "__main__":
show('hello world!')

上面给出了一个带参数装饰器的示例。根据我们前面的铺垫,我们不难分析得出,上面的执行过程是logging(level='WARN')->outer_wrapper(show)->inner_wrapper(),所以我们可以理解,在被logging修饰后的show其实就是logging(level='WARN')(show),执行show('hello world!')其实就是在执行logging(level='WARN')(show)()。注意与不带参数的装饰器的区别,带参数的装饰器比不带参数的装饰器多套了一层,对应的装饰器也有了调用。因为在使用装饰器的时候,带了括号,所以装饰器本身多套了一层。被装饰器修饰过的函数在被调用的时候,实际上执行的是装饰器最内层的函数,其余层的在函数被修饰时就已经执行了。

是不是觉得非常自然?对的,我以前对装饰器的理解也就停留在不带参数的装饰器这一深度。

2. 基于类实现的装饰器

依然先上代码

from functools import wraps
import time class time_cost:
def __init__(self, func):
self.func = func def __call__(self, *args, **kwargs):
start_time = time.time()
result = self.func(*args, **kwargs)
end_time = time.time()
print(end_time - start_time)
return result @time_cost
def test(*args, **kwargs):
time.sleep(1.0) if __name__ == "__main__":
test()

上面的基于类实现的不带参数的装饰器实际上利用的是Python中的可调用对象特性,凡是实现了__call__方法的类的实例是可以被调用的。因此被time_cost修饰过的test函数本质上已经变成了time_cost类的实例了。调用test方法的时候,实际上执行的是__call__方法。

下面介绍稍微复杂一点的基于类实现的带有参数的装饰器。

from functools import wraps

class logging:
def __init__(self, level):
self.level = level def __call__(self, func): @wraps(func)
def wrapper(*args, **kwargs):
print("[{level}]: enter function {func}()".format(
level=self.level,
func=func.__name__))
return func(*args, **kwargs)
return wrapper @logging(level='WARN')
def show(msg):
print('message:{}'.format(msg)) if __name__ == "__main__":
show('hello world!')

不同于基于类实现的不带参数的装饰器,基于类实现的带参数的装饰器在__call__里面多了一层wrapper。被装饰器修饰的show方法本质上是logging(level='WARN')(show),此时调用show方法,实际上执行的是wrapper方法。

现在看来,其实装饰器也没有很复杂,在实际的项目中用装饰器可以带来很大便利。

Python 装饰器初探的更多相关文章

  1. python装饰器初探

    一.含有一个装饰器 #encoding: utf-8 ############含有一个装饰器######### def outer(func): def inner(*args, **kwargs): ...

  2. 关于python装饰器

    关于python装饰器,不是系统的介绍,只是说一下某些问题 1 首先了解变量作用于非常重要 2 其次要了解闭包 def logger(func): def inner(*args, **kwargs) ...

  3. python装饰器通俗易懂的解释!

    1.python装饰器 刚刚接触python的装饰器,简直懵逼了,直接不懂什么意思啊有木有,自己都忘了走了多少遍Debug,查了多少遍资料,猜有点点开始明白了.总结了一下解释得比较好的,通俗易懂的来说 ...

  4. Python 装饰器学习

    Python装饰器学习(九步入门)   这是在Python学习小组上介绍的内容,现学现卖.多练习是好的学习方式. 第一步:最简单的函数,准备附加额外功能 1 2 3 4 5 6 7 8 # -*- c ...

  5. python 装饰器修改调整函数参数

    简单记录一下利用python装饰器来调整函数的方法.现在有个需求:参数line范围为1-16,要求把9-16的范围转化为1-8,即9对应1,10对应2,...,16对应8. 下面是例子: def fo ...

  6. python 装饰器学习(decorator)

    最近看到有个装饰器的例子,没看懂, #!/usr/bin/python class decorator(object): def __init__(self,f): print "initi ...

  7. Python装饰器详解

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

  8. 关于python装饰器(Decorators)最底层理解的一句话

    一个decorator只是一个带有一个函数作为参数并返回一个替换函数的闭包. http://www.xxx.com/html/2016/pythonhexinbiancheng_0718/1044.h ...

  9. Python装饰器由浅入深

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

随机推荐

  1. CentOS 5.6怎么安装MongoDB数据库?

    1. 下载Linux版本的 MongoDB 数据库 到官方的下载页面下载mongodb的Linux版本,32位还是64位根据自己的情况自行选择 http://www.mongodb.org/downl ...

  2. python 3+djanjo 2.0.7简单学习(二)--创建数据库和模型

    我们紧接上次,这里将建立数据库,创建第一个模型提示:这里我们不需要去一直启动,django会在我们ctrl+s的时候自动刷新并启动服务,很方便吧  1.数据库配置 现在,打开 vote_mysite/ ...

  3. skimage.io.imread vs caffe.io.load_image

    这两周在跑一个模型,我真的是被折腾的要崩溃了. 最后原因就是数据类型的问题,你说是不是应该管小黑屋啊. skimage.io.imread得到的是uint8的数据,而caffe.io.load_ima ...

  4. React后台管理系统-订单管理

    1.订单管理页面和商品管理页面类似,都是一个搜索组件+列表组件 2.搜索框search组件 import React from 'react';   class ListSearch extends ...

  5. git(将现有项目加入osChina)

    将现有项目加入osChina 在osChina中创建项目 注意不要初始化项目.(其实初始化也没有什么问题,可以直接clone到本地,再把项目添加进去就行了,后续操作一样的) 项目现在基本为空,得到项目 ...

  6. Docker自学纪实(五) 使用Dockerfile构建php网站环境镜像

    一般呢,docker构建镜像容器的方式有两种:一种是pull dockerhub仓库里面的镜像,一种是使用Dockerfile自定义构建镜像. 很多时候,公司要求的镜像并不一定符合dockerhub仓 ...

  7. python__高级 : 动态添加 对象属性, 类属性, 对象实例方法, 类静态方法, 类方法

    给对象添加实例属性,可以直接这样  t.age = 18   ( 假设 t = Test() )  给类添加类属性 , 也可以直接这样  Test.age = 18 那给对象添加实例方法,可以在类外面 ...

  8. 【PHP项目】伪静态规则

    伪静态规则写法RewriteRule-htaccess详细语法使用 2016年03月30日 16:53:59 阅读数:20340 伪静态实际上是利用php把当前地址解析成另一种方法来访问网站,要学伪静 ...

  9. yii rbac

    一.简介 什么是rbac ? rbac是就是基于角色的访问控制. yii提供一套基础的底层接口,我们知道,rbac经历好几个阶段,从rbac0到rbac3,从基础的用户.角色.权限,到动态的rbac处 ...

  10. 《Python语言及其应用》学习笔记

    第二章 ========== 对象的类型决定了可以对它进行的操作.对象的类型还决定了它装着的数据是允许被修改的变量(可变的),还是不可被修改的常量(不可变的). Python是强类型的,你永远无法修改 ...