一装饰器
装饰器背后的主要动机源自 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
来源: http://www.jianshu.com/p/b7c4b154f281