2019独角兽企业重金招聘Python工程师标准>>>

装饰器是在函数调用上的修饰。这些修饰仅是当声明一个函数或者方法的时候,才会应用的额外调用。

装饰器的语法看起来像是这个样子:

def decorator(func2bedecorated):#定义装饰器```return wrappedFunc@decorator([dec_opt_args])#使用装饰器
def func2bedecorated([func_opt_args]):#定义被装饰的函数``````

  • 首先需要定义装饰器(或者你也可以用内建的装饰器),其实装饰器就是一个函数,它接受其他函数为参数并返回一个装饰过的函数(或其他对象)。

  • 是的,装饰器在使用的时候后面没有冒号,因此下面的语句也就不需要缩进。

  • 从定义装饰器的语句可以看到,它是有返回值的。他把自定义的wrappedFunc 返回给了 func2bedecorated,也就是被装饰的函数自身,即,原函数被覆盖了。对应到数学概念上,使用装饰器来定义函数就像是写一个复合函数,就像 g(f(x)) 这样,g(x)是装饰器

因此看起来似乎正确的装饰器函数调用方法应该是这样:

func2bedecorated = decorator(func2bedecorated)

或者至少也应该是这样:

decorator(func2bedecorated)

实际上面两种方法都可行。从某种意义上说,装饰器的语法就是一个语法糖。还记得前面介绍过的如何定义类的静态方法么:

class C:def __init__(self):passdef foo():print("calling static method foo().")foo = staticmethod(foo)

这里的staticmethod()其实就是一个内建的装饰器函数。因此上面的示例代码也可以使用装饰器的标准语法写成:

class C:def __init__(self):pass@staticmethod def foo():print("calling static method foo().")

这种语法看起来要漂亮得多。

好了,那么装饰器一般都用在什么地方呢?(除了上面提到的静态方法、或者静态属性)

于是最后给出一个时间戳的例子,因为偶尔会有给函数做性能测试的需求,如果给每个函数都手动添加 time.ctime() 的话,就太不科学了。这时候使用一个时间戳装饰器是个不错的做法:

from time import ctime,sleepdef tsfunc(func):def wrappedFunc():print('[%s] %s() called'%(ctime(),func.__name__))func()print('[%s] %s() ended'%(ctime(),func.__name__))return wrappedFunc@tsfunc
def foo():sleep(1)foo()

这里我们首先定义了一个名为 tsfunc 的时间戳装饰器。可以看到它对传入的 func 都做了什么:

  1. 在装饰器内定义出新的函数名为 wrappedFunc() ,好待会把它返回出去

  2. wrappedFunc 的实际内容仅为:先打印出一行程序启动的时间戳,随后调用 func() , 最后再打印出一行程序结束的时间戳

要做测试的演示函数 foo() 的内容仅是 sleep 一秒钟,可以看到下面的运行结果正如期望:

[Sat Nov 23 20:33:34 2013] foo() called
[Sat Nov 23 20:33:35 2013] foo() ended
>>>

在Python3 中,类也有装饰器了,用法与函数相同(即可以给装饰器函数传入一个class作为参数)。另外地,上面提到过一句,装饰器返回的可以不是一个函数。这一点会在之后的“描述符”(descriptor)中讲到。

然后,很多地方会提到,装饰器分为有参数和无参数两种。这种说法容易造成混淆,让人误以为他们之间只是带不带参数的区别。而实际上有无参数的装饰器在结构和功能上都是不同的。或者直白些讲:带参数的装饰器其实是一个返回另一个装饰器的 deco_maker,不带参数的装饰器才是真正的 real_deco。用伪代码可以表示为:

def real_deco(func_2b_decorated):def wrapped(func_2b_decorated):......return wrappeddef deco_maker(deco_args):def real_deco(func_2b_decorated):......return real_deco

即带参数的装饰器返回的其实是一个真正(不带参数)的装饰器。前面说过,装饰器就是一个函数,一个接受函数做参数并返回一个新函数的函数,所以装饰器也可以用来返回另一个装饰器就不难理解了。@deco 的语法仅是一种语法糖,它和社交网站的 @somebody 没有啥区别,表示通知 deco 来干活了。所以你看到 @deco 这语法里仅是 @ 了一下函数名;但带参数的情况就不同了,带参数的装饰器用起来是这样子的:

@deco_maker(deco_args)
def func_2b_decorated():......

这里 @ 的对象明显已经不是一个函数名了,而是一个函数调用。所以这里真正被叫来干活(装饰 func_2b_decorated)的家伙其实是将要被 deco_maker 返回的那个 real_deco。

这就是带参数的装饰器的作用,他可以通过参数的设定来返回不同的装饰器,这比直接调用不同的装饰器看起来优雅一些。下面贴出一个带参数装饰器的示例,并引出闭包的话题:

from time import timedef logged(when):def log(f,*args,**kargs):print('''Called:
function:%s
args:%r
kargs:%r'''%(f,args,kargs))def pre_logged(f):def wrapper(*args,**kargs):log(f,*args,**kargs)return f(*args,**kargs)return wrapperdef post_logged(f):def wrapper(*args,**kargs):now = time()try:return f(*args,**kargs)finally:log(f,*args,**kargs)print('time delta:%s'%(time()-now))return wrappertry:return {'pre':pre_logged,'post':post_logged}[when]except KeyError as e:raise ValueError(e,'must be "pre" or "post"')

下面是运行示例:

>>> @logged('post')
def hello(name):print("Hello,",name)>>> hello('Worlds!')
Hello, Worlds!
Called:
function:<function hello at 0x000000000A69C2F0>
args:('Worlds!',)
kargs:{}
time delta:0.034002065658569336

本装饰器提供的功能为给函数加 log,而且分为“先输出 log,后调用函数”和“先调用函数,后输出 log”两种方式。log 的内容包含:函数名、非关键字参数、关键字参数的信息。并且如果是 post_log 的方式,还可以打印出函数执行花费的时间。和上面的时间戳功能类似。

这里在使用装饰器的时候,就给“@logged('post')”传了一个“post”参数,用来选定要返回的 real_deco。同时,返回的也是一个闭包。因为 Python 支持静态嵌套域,所以可以从函数内部访问外部作用域的变量。假如在一个外部函数中嵌套了一个内部函数,并且这个内部函数又引用了外部函数作用域里的变量,那么这个内部函数连带外部作用域一起,就叫做闭包。所以说到底闭包仍然是一个函数,但他比一般函数高级之处就是带了多余的自由变量(被引用的外部作用域(非全局)的变量就叫做自由变量)。这使得这个函数在调用的时候能记忆状态了,因为自由变量没有位于内部函数里,所以即使内部函数调用完毕,这个变量也不会被释放。这看起来就像类的属性一样,实际用起来也像类的属性一样。因为 Python 并非专业的函数式编程语言,所以除非有额外要求必须返回一个带自己作用域的回调函数,一般还是建议使用类实现。上面的装饰器就是一个有额外要求的例子。因为我们是要包装函数的,所以也要返回一个函数。返回的装饰器携带了 log() 函数,所以这个装饰器也就是一个闭包。

如果上面这个例子不明显的话,一般用来做闭包举例的是下面这样的计数程序:

def counter(start_at=0):count = [start_at]def incr():count[0] += 1return count[0]return incr

运行结果如下

>>> a = counter()
>>> b = counter(100)
>>> a()
1
>>> a()
2
>>> b()
101
>>> b()
102
>>> a()
3

a 和 b 两个函数对象分别携带了自己的自由变量 count,并可以通过这个变量来计数,这里用函数实现了平时要用一个类来实现的功能,并且对象还是可调用的。不过这里需要注意的是,如你所见在 counter() 函数里使用的是一个列表,既然只使用一个值(count[0]),干吗不用整数呢?这是因为如果把 count[0] 换成整数 count 的话,会报下面的错:

UnboundLocalError: local variable 'count' referenced before assignment

因为内部作用域会覆盖外部作用域。所以当你在内部作用域运行 count += 1,这条语句的时候,解释器会直接认定你在声明本地变量 count 并试图给它赋值。实际上解释器运行 incr() 这个函数时做的第一件事就是把 count 标记为本地变量,因为他看到了这个名字后面有一个赋值符号。而接下来又因为 count 还没有赋值,就被等号后面引用了,这才引发了那个异常。也就是说解释器根本没想着去外部作用域寻找 count 变量。但 count[0] 就不同了,这时的 count 是一个可变类型,解释器会试图寻找 count 并赋值,而不是直接声明本地变量。也许这种状况会让人产生“为什么 Python 不把变量声明和赋值分开呢,那样就不会出错了”的抱怨,但实际上这种动态设定带来的好处远比麻烦多,而且也不是没有方法来解决上面这个问题。(虽然这个方法是从 Python3 开始添加的)

这个解决方法就是 nonlocal 关键字,它的作用与 global 极其相似,区别仅在于作用域不同。Python2.1 以前函数只能访问自己的作用域或者全局作用域,那样有 global 就够用了,现在因为有了闭包,就需要一种声明变量既不是本地,又不是全局的方法,这就是 nonlocal。所以配合 nonlocal,就可以使用整数来实现上面的计数器了:

def counter(start_at=0):count = start_atdef incr():nonlocal countcount += 1return countreturn incr

运行:

>>> a = counter()
>>> a()
1
>>> a()
2

最后要说的是,闭包的功能是可以被类取代的。只要定义了 __call__(self) 方法,这个类的实例就是可调用的,再带上实例本身的属性,就可以当闭包用。

转载于:https://my.oschina.net/lionets/blog/178529

Python 函数(类)的装饰器与闭包相关推荐

  1. python函数基础和装饰器

    一.为什么要有函数?没有函数有什么问题? 1.组织结构不清晰,可读性差 2.代码冗余 3.可扩展性差 二.函数的分类: 1.内置函数:python解释器已经为我们定义好了的函数即内置函数,我们可以拿来 ...

  2. python 元类与装饰器

    直接上代码 元类 metaclass class MyType(type):def __init__(self, *args, **kwargs):print("MyType.__init_ ...

  3. [python 进阶] 第7章 函数装饰器和闭包

    文章目录 7.1 装饰器基础知识 7.2 Python何时执行装饰器 7.3 使用装饰器改进"策略" 7.4 变量作用域(global) 备注 -比较字节码(暂略) 7.5 闭包 ...

  4. python装饰器与闭包_Python:函数装饰器和闭包

    摘自<流畅的python> 7.1 装饰器基础知识 装饰器是可调用的对象,其参数是另一个函数(被装饰的函数). 装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用 ...

  5. 兄弟连学python(06)装饰器:对类或者函数进行功能的扩展

    #第一步,基本函数 ''' def hulk(): print("酒逢知己饮,诗向会人吟!") #调用函数 hulk() #第二步 扩展函数功能(不能修改原函数) #用于扩展基本函 ...

  6. python函数装饰函数_Python精进-装饰器与函数对象

    本文为<爬着学Python>系列第四篇文章. 从本篇开始,本专栏在顺序更新的基础上,会有不规则的更新. 在Python的学习与运用中,我们迟早会遇到装饰器,这个概念对于初识装饰器的新手来说 ...

  7. python装饰器与闭包_Python 装饰器和闭包

    Python 装饰器和闭包 装饰器是 Python 中常见的语法糖,这篇文章讲了闭包和装饰器的原理,并且分析了函数中变量的作用域,以及尝试总结了常见的坑. 装饰器基础 首先来看看装饰器的定义:装饰器本 ...

  8. python装饰器与闭包_python中闭包和装饰器的理解(关于python中闭包和装饰器解释最好的文章)。...

    转载:http://python.jobbole.com/81683/ 呵呵!作为一名教python的老师,我发现学生们基本上一开始很难搞定python的装饰器,也许因为装饰器确实很难懂.搞定装饰器需 ...

  9. CHAR.VI 函数装饰器和闭包

    CHAR.VI 函数装饰器和闭包 函数装饰器用于在源码中"标记"函数,以某种方式增强函数的行为.这是一项强大的功能,但是若想掌握,必须理解闭包. nonlocal 是新近出现的保留 ...

最新文章

  1. Kubernetes vs Docker:了解2021年的容器
  2. 算出当前系统后某个月的日期_Python3.7知其然知其所以然-第十八章 日期函数
  3. LeetCode算法题0:分发糖果【贪心算法】
  4. 5g网络架构_【5G网络架构】系列之二:5G基站—gNodeB。为什么叫gNodeB?取个名而已,需要理由吗?...
  5. Tomcat中web.xml文件的详细说明
  6. PAT (Advanced Level) Practise:1001. A+B Format
  7. 字节跳动和腾讯不正当竞争案将于深圳开庭 抖音:我们也是看新闻才知道本月24日要开庭...
  8. 《Java核心技术 卷1》
  9. ci mysql空闲连接回收_数据库连接空闲回收问题 CommunicationsException: Communications link failure...
  10. 无线通信网络学习之LTE网络架构篇(20141208)
  11. 计算机sci二区期刊,SCI二区期刊汇总表
  12. 建筑施工企业工程项目成本管理与控制对策
  13. Excel根据快递单号自动识别快递公司
  14. WPF无边框窗体拖动
  15. 几种你不知道的获取浙A牌照的方法
  16. 计算机组成原理唐朔飞第二版答案第六章,计算机组成原理第六章部分课后题答案(唐朔飞版)...
  17. MySQL的多表查询-多表关系与相关练习题
  18. java留言板_java实现留言板功能实例
  19. activiti——监听器
  20. MIGO 抬头屏幕自定义字段增强示例

热门文章

  1. oracle dba_hist tablepsace,oracle数据库dba_hist等视图中的Delta相关字段介绍
  2. ftp服务器app配置文件,Ubuntu FTP服务器配置与应用
  3. android markdown 框架,Android Studio MarkDown风格README的正确打开姿势
  4. html调用python_HTML网页调用本地Python程序
  5. python交互式编程在哪里_终于明了python交互式编程入门
  6. python3 二进制文件比较_《Python 3程序开发指南(第2版•修订版)》——7.4 随机存取二进制文件...
  7. 孙鑫-MFC笔记三--绘图
  8. 360浏览器清除缓存_手机中的缓存是什么?
  9. Git——撤销和删除操作【git restore / git rm 】
  10. C语言:求圆的面积和周长