众所周知, 在Python的世界里, 函数是作为First Class的存在, 也就是所谓的第一公民, 闭包(Closures)是这个title所享有的特殊性质, 基于此性质的装饰器应用非常广泛, 常见的Flask中的router就是使用了装饰器.
基于装饰器产生了一种叫AOP(Aspect Oriented Programming)的编程方式, 与传统的OOP编程思想区别在于, OOP通过类与对象组织代码, 而AOP则对函数按功能切片, 通过注册装饰器的方式来组织代码.
例如在Web开发中, 需要对系统某些模块添加验证或其他功能, 如果是OOP的话得要挨个修改view函数, 不仅徒增代码冗余, 而且对原有代码具有侵入性. 而AOP的话直接将要添加的逻辑定义成一个装饰器, 在指定的view函数注册装饰器即可, 不破坏原有代码逻辑, 简洁优雅.
那什么是闭包呢? 要理解闭包我们需要清楚以下几个概念:
函数的作用域:
作用域即函数的namespace命名空间,python在遇到一个函数的时候会新建一个作用域,在函数中调用变量的时候会优先在自己的作用域中查找,如果找不到则向外层作用域继续找, 如果全局作用域都找不到该变量就抛出异常
函数变量的生命周期:
函数的变量在函数执行时创建,函数执行完后销毁,如下图所示 函数变量在test函数执行完后已经销毁,后续代码访问 ‘x’变量会导致出错:
闭包:
在Python 的世界里, 一切皆对象的思想贯穿始终, 函数也是对象, 函数可以直接执行, 也可以被引用, 可以嵌套, 甚至可以作为返回值被返回 (这在C语言里是不可想象的), 以下一个简单例子理解闭包:
上图中的outer函数内定义了一个inner的函数,outer返回的是内层函数,按道理来说变量x是在外层函数定义的,那么在外层函数返回之后变量x就被销毁而不应该被inner函数访问,即在非全局作用域下定义的inner函数在定义的时候会记住外层命名空间的变量,即inner包含了外层作用域变量!
装饰器:
在上图的例子中,inner函数记住了outer的作用域,也就是说inner能访问到outer的变量,每次运行outer函数时inner函数都会被重新定义,说白了可以把inner函数当作outer函数的函数体,那么如果变量x是从外部传入的话能够在保持inner函数的情况下为该函数增加一些功能,这就是装饰器的作用,在不改变原有函数结构的情况下增加新的功能,比如常用的为函数增加打印运行时间的功能:
如上图所示:decorater函数里定义了inner函数,其本身接受一个函数作为参数,在inner函数内部执行待传入的函数,test是测试函数,new_test是添加了打印参数功能的新函数,上面的装饰器使用不是很方便,python有个@的语法糖,底层不变但是方便了程序员:
仔细观察上面的代码我们发现, 装饰器本身就是一个函数, 它接受一个可调用对象作为其参数, 然后输出一个新的可调用对象, 可不可以用类来实现呢? 可以的, 只要把类本身变成一个可执行对象, 在__init__方法接收传入的参数, 然后重载__call__方法, 在call方法中调用被包装的函数即可:
但是如果需要一个含有参数的装饰器的话, 就不太一样了, __init__ 方法接收装饰器的参数, 然后 __call__方法传入被装饰函数:
总结:
本文大致总结了我对Python闭包和装饰器的一些理解, 闭包能扩展函数的能力, 在一些函数作为一等公民的语言大都支持闭包, JavaScript甚至能用函数的原型继承模拟出一套OOP的编程方式! 当然我觉得还是Python实现得更优雅一点...