Python基础手册25——装饰器

一、装饰器

装饰器背后的主要动机源自 python 面向对象编程。装饰器是在函数调用之上的修饰。这些修饰仅是当声明一个函数或者方法的时候,才会应用的额外调用。

装饰器的语法以 @ 开头,接着是装饰器函数的名字和可选的参数。紧跟着装饰器声明的是被修饰的函数,和装饰函数的可选参数。

装饰器看起来会是这样:

此外,装饰器可以如函数调用一样“堆叠“起来,这里有一个更加普遍的例子,使用了多个装饰器:


有参数和无参数的装饰器

没有参数的装饰器:

带参数的装饰器 :

现在我们知道装饰器实际就是函数。我们也知道他们接受函数对象。一般说来,当你包装一个函数的时候,你可以在包装的环境下在合适的时机调用这个函数。我们在执行函数之前,可以运行些预备代码,如 post-morrem 分析,也可以在执行代码之后做些清理工作。

你可以考虑在装饰器中置入通用功能的代码来降低程序复杂度。例如,可以用装饰器来:

  • 引入日志
  • 增加计时逻辑来检测性能
  • 给函数加入事务的能力

对于用 python 创建企业级应用,支持装饰器的特性是非常重要的。


二、装饰器的原理探析

我们可以跟着下面的例子更深层次的理解装饰器的原理。

当你要在一个函数A之前或者之后增加一些操作的话,我们可以定义一个新的函数B,在函数B中定义这些操作,并在指定的位置调用函数A。这样我们就等到了一个新的函数对象,他封装的对函数A执行的额外的操作。并且可以把B函数名赋值给其他变量。

但是现在我们有一类函数,想要跟函数A一样增加同样的操作,这是我们就可以把这些函数作为参数传入函数B中,然后在指定的位置通过小括号来调用传入的函数对象。这样我们的函数B就有了参数。这样我们就无法把带参数的函数B赋值给新的变量名,因为把一个参数传给函数,就会调用这个函数。

当然我们可以在全局作用域中定义func变量。

这时如果我们想用一个变量来保存调用不同函数的B函数,就会出现所有的变量都只会调用同一个函数,也就是func最后赋值的函数。

那怎样将func和B绑定到一个作用域呢?让他们绑定到一起成为一个新的函数对象返回。要知道上面的例子中b和b2都是指向了统一个对象B,所以他们的调用结果才都是一样的。

这时我们可以利用嵌套函数的一个特点,就是 在嵌套函数中,将内层函数对象作为返回值返回的话,内层函数对象可以保留其所在的作用域(外层函数的作用域),也就是外层函数中定义的一切变量都可以跟随内层函数得到保留。 利用这个特性,因为形参也处于函数作用域中,所以我们让外层函数来接受参数,当外层函数调用结束后,返回的内层函数还处于一个封闭的作用域中,并可以使用外层函数的参数。

按照上面的思路,我们可以在函数B的外层再封装一层函数C,让外层的函数C来接受参数,而函数B不用自己传入参数,直接使用外层的函数C就可以了。这样我们可以在外层的函数C中直接将使用了外层函数C参数的函数B对象返回。这样我们把A对象作为参数传递给外层函数C,将其调用,但可以接受到一个绑定了形参func(这里就是A)的新函数B对象。

注意这里的函数对象B和单纯定义的函数B是不一样的,因为他和参数func绑定在了一起,是一个新的函数对象。所以上面b和b2是两个各自独立的函数对象。

装饰器就是为了解决上面的问题而存在的,我们使用@符号为函数加上装饰器。

当然我们函数A本身也是可以有参数的,那么在函数C中我们最后返回的函数B对象也应该定义对应的参数才行,那么为了为了通用性,我们可以使用可扩展参数,也就是在B函数中只定义 *args 和 kwargs, args用来接收所有的位置参数,kwargs用来节后所有的关键字参数。在B的代码中,我们再将其解包以args 和 **kwargs 的形式传给函数func 的调用。

当然我们的函数B本身也有可能需要一些参数来实现某些功能。我们可以通过直接在函数B中直接定义参数来实现。但是装饰器的语法并不支持这样操作,他不能自动把装饰器接受到的参数直接传给B中的参数,而且还不影响 *args 和 **kwargs 正常传值。所以Python为了将B函数接受使用的参数和内层调用使用的函数的参数区分开来,又再次使用利用了嵌套函数的特点。我们可以B接受使用的参数放在外层函数的作用域中,但是外层函数接受了 func 作为参数,为了避免 func 参数和这些参数相互影响,所以可以把这些参数又放在了外外层的函数的作用域中,所以我们需要在外层函数外在嵌套一层函数来接受函数B需要的参数。

在函数C外面再嵌套一层函数的方法, 不仅是因为我们上面说的将B需要的参数和func 变量的作用域隔离开来。这里,我们再分析一下装饰器的语法,装饰器是在需要改造的函数的上面加一个@符号后面跟一个我们定义的改造函数名。这个@加函数名的语法其实跟小括号一样,会直接调用这个函数,并把下面装饰的函数作为参数传入。那么我们要给装饰器传入参数时,需要使用小括号,那么小括号也是执行函数的表达式。所以这个装饰器函数在匹配上小括号和@符号时要被执行两次。而且是小括号先执行。所以我们必须在装饰器函数中进行两次嵌套。这样小括号表达式执行了函数后将要返回一个函数对象C,C函数对象和@符号匹配将下面装饰的函数A作为参数传入,再次执行并返回一个新的函数对象B,并赋值给下面函数同名的变量名A。

注意@符号和() 都是函数调用语法。给一个函数加上装饰器,Python解释器会直接执行装饰器函数。最后生成一个新的函数对象,也就是上面例子中的A。


《Python基础手册》系列:

Python基础手册 1 —— Python语言介绍
Python基础手册 2 —— Python 环境搭建(Linux)
Python基础手册 3 —— Python解释器
Python基础手册 4 —— 文本结构
Python基础手册 5 —— 标识符和关键字
Python基础手册 6 —— 操作符
Python基础手册 7 —— 内建函数
Python基础手册 8 —— Python对象
Python基础手册 9 —— 数字类型
Python基础手册10 —— 序列(字符串)
Python基础手册11 —— 序列(元组&列表)
Python基础手册12 —— 序列(类型操作)
Python基础手册13 —— 映射(字典)
Python基础手册14 —— 集合
Python基础手册15 —— 解析
Python基础手册16 —— 文件
Python基础手册17 —— 简单语句
Python基础手册18 —— 复合语句(流程控制语句)
Python基础手册19 —— 迭代器
Python基础手册20 —— 生成器
Python基础手册21 —— 函数的定义
Python基础手册22 —— 函数的参数
Python基础手册23 —— 函数的调用
Python基础手册24 —— 函数中变量的作用域
Python基础手册25 —— 装饰器
Python基础手册26 —— 错误 & 异常
Python基础手册27 —— 模块
Python基础手册28 —— 模块的高级概念
Python基础手册29 —— 包

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,417评论 5 465
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,317评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,319评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,039评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,951评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,803评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,003评论 3 389
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,621评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,909评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,942评论 2 315
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,752评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,529评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,021评论 3 301
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,149评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,454评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,064评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,260评论 2 339

推荐阅读更多精彩内容