浅析Python中的闭包与装饰器

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

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

推荐阅读更多精彩内容

  • 前几天学习python装饰器时,看各种例子,上来就是一个嵌套函数,还返回一个函数对象(返回内嵌函数),学得我是一脸...
    喵小琪阅读 348评论 0 1
  • 一、python函数作用域LEGB python解释器查找变量的原则(顺序):L→E→G→BL:Local函数内部...
    风萧雨霖阅读 446评论 0 0
  • 本文为《爬着学Python》系列第四篇文章。从本篇开始,本专栏在顺序更新的基础上,会有不规则的更新。 在Pytho...
    SyPy阅读 2,489评论 4 11
  • 闭包(closure)是函数式编程的重要的语法结构。闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。 ...
    田飞雨阅读 3,285评论 5 13
  • 在学习 Python 的时候,庆幸自己有 JavaScript 的基础,在学习过程中,发现许多相似的地方,如导包的...
    柏丘君阅读 1,194评论 2 8