浅析Python中的闭包与装饰器

2017-06-24  本文已影响0人  zgjx

导读:Python中的装饰器经常用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。本文从闭包的概念引入,并以实例形式对装饰器及其应用进行了讲解。

Python变量作用域

在了解闭包之前,我们得先了解一下python解释器查找变量时的规则。在python中,查找一个变量名称的顺序为local-->enclosing function locals-->global-->builtin,简称LEGB。它们各自的含义如下:

1.Local - 当前所在命名空间(如函数、模块),函数的参数也属于命名空间内的变量

2.Enclosing - 外部嵌套函数的命名空间

3.Global - 全局变量,函数定义所在模块的命名空间

4.Builtin - 内置模块的命名空间

举例说明:

val1=0
def fun(val2):
    def Max():
        return max(val1, val2)
    return Max()

print(fun(1))
# 输出:1

在本例中,val1即为Global;而val2,对函数fun而言为Local,对嵌套的函数Max而言就是Enclosing;小写的函数max并没有定义,但我们可以直接使用,因为它是python标准库里的函数,即为Builtin。

闭包的概念

在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数,它是将组成函数的语句和这些语句的执行环境打包在一起时,得到的对象,即闭包=函数块+定义函数时的环境。在Python中,一切皆对象,函数也是一个对象,它可以当作一个参数被传入,也可以作为一个结果被返回。Python以函数对象为基础,为闭包提供了语法支持。现在举个例子说明一下:

def a(m):
    def b(n):
        print(m,n)
    return b

c=a('hello')
c('test1')  # 输出:hello test1
c('test2')  # 输出:hello test2

在这个例子中,调用函数a时就产生了一个闭包b,并且该闭包拥有enclosing变量m,通过最后两个测试的例子可以看出,在函数a执行完成后,变量m依然存在,这是因为它被闭包b引用了,所以不会被回收。另外可以看出,本例中闭包其实就是一个引用了enclosing变量m的函数。

装饰器

装饰器,就是将被装饰的函数当作参数传递给与装饰器对应的函数(名称相同的函数),并返回包装后的被装饰的函数。在Python中,装饰器被用于用@语法糖修辞的函数或类。装饰器有很多作用,一种常用的方法是将其作为一种函数调用日志记录器。现举例说明:

def log(func):
    def wrapper(*args, **kw):
        print(func.__name__ + ' is running')
        return func(*args, **kw)
    return wrapper

@log
def fun():
    print('hello')

if __name__ == '__main__':
    fun()
'''
执行结果:
fun is running
hello
'''

在本例中,log函数返回了一个闭包wrapper,它引用了一个变量func。而定义函数fun时的@log,即可看作fun=log(fun),@log只是一种更直观的写法。而装饰器即为对闭包的一种应用,只不过它传递的变量是函数罢了。

装饰器本身接收一个函数作为参数,但是有时候我们需要装饰器接受另外的参数,此时需要在外层再加一层函数,修改上例如下:

import functools
def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print(text + func.__name__ + ' is running')
            return func(*args, **kw)
        return wrapper
    return decorator

@log('test:')
def fun():
    print('hello')

if __name__ == '__main__':
    fun()
    print(fun.__name__)
'''
执行结果:
test:fun is running
hello
fun
'''

这里的log函数即为一个带参数的装饰器,它其实是对原有装饰器的一种封装。使用装饰器的一个缺点是无法保存原有函数的信息,如本例中的_name_属性,使用python内置的functools即可解决这个问题,可以看到本例最后的输出依然为fun,如果不使用functools,最后的输出将为wrapper。

通过上述两例可以看出,一个函数不用做任何修改,只需要在定义的地方加上装饰器,调用的方式也不用做任何改变,即可完成对函数功能的增强。如果代码中含有大量类似的函数,那么我们就可以直接在定义函数的地方加上装饰器,而不必修改每一个函数,这样不仅可以提高程序的可复用性,也可以提高程序的可读性。

装饰器的应用

装饰器其实就是一个包装函数的函数,它可以为已经存在的对象添加额外的功能,因此经常被用于有切面需求的场景,较为经典的有插入日志、 性能测试、事务处理等。比如在python的轻量级web开发框架flask中,大量地使用装饰器,用flask开发的代码简洁而又优雅,极具python的风格。一个flask的DEMO如下:

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run()

flask的默认端口为5000,使用浏览器打开http://localhost:5000/ ,即可看到结果。在此例中,flask使用app.route装饰器将一个url绑定到对应的函数,非常简单而又直观。

总结

Python的装饰器本质上是对闭包的一种应用,但它不仅可以用函数实现,也可以用类实现,并且还可以一次性使用多个装饰器。虽然定义起来有点复杂,但使用起来却非常灵活和方便。它可以极大地增强函数的功能,同时又不会增加调用者的负担,维护起来也非常地容易。

上一篇下一篇

猜你喜欢

热点阅读