深入理解Python中的闭包与装饰器:原理、作用域及语法糖
本文深入解析Python中闭包与装饰器的核心机制。通过剖析命名空间、作用域查找规则及变量引用特性,阐明闭包的定义与内存独立性。进一步讲解装饰器如何在闭包基础上实现函数功能扩展,涵盖标准写法、@语法糖、带参装饰器及多层嵌套场景,帮助开发者在不修改原代码的前提下实现功能增强。
从表现形式来看,Python中的闭包定义为:若内部函数引用了外部作用域(非全局作用域)的变量,则该内部函数被视为闭包。
以下说明主要基于Python 2.7版本,其他版本可能存在差异。
直接阅读定义可能难以理解,我们先通过示例了解什么是内部函数。
def wai_hanshu(canshu_1):
def nei_hanshu(canshu_2): # 我在函数内部有定义了一个函数
return canshu_1*canshu_2
return nei_hanshu # 我将内部函数返回出去
a = wai_hanshu(123) # 此时 canshu_1 = 123
print a
print a(321) # canshu_2 = 321

在函数内部嵌套定义另一个函数,当向外层函数传递变量并赋值给a后,a变为一个函数对象。再次为该函数对象传参时,可获得内部函数的返回值。按作用域原则,全局作用域通常无法访问局部作用域,但此处通过特定方式成功访问了内部函数。
继续观察以下示例:
def wai_hanshu():
a = []
def nei_hanshu(canshu):
a.append(canshu)
return a
return nei_hanshu
a = wai_hanshu()
print a(123)
print a(321)

可以看出,位于外部函数中的列表a发生了改变。要理解这一现象,需先了解Python的命名空间,因为命名空间是作用域表现的根本原因,此处简要说明。
引入命名空间的主要目的是避免变量冲突。由于Python模块众多,且模块内包含函数、类等,均需使用变量。若每次都要避免与其他变量名冲突,将极大增加开发负担,开发者应专注于自身逻辑而非他人代码中的变量名,因此Python引入了命名空间机制。命名空间分为模块层,模块内又包含全局作用域和局部作用域,结构如下:

模块间拥有不同的命名空间,内部包含全局和局部作用域,局部作用域之间还可嵌套,从而确保变量名不冲突。补充说明:可通过__name__属性获取命名空间的名称:

主文件的命名空间名为'__main__',而模块的命名空间即为模块名。
作用域的产生源于Python查找变量的机制:首先在當前命名空间中查找,若未找到则向上一级命名空间查找,依此类推;若最终未找到,则触发变量未找到的异常。
此前我们提到全局作用域无法访问局部作用域,而局部作用域可以访问全局作用域,原因即在于此。当在局部作用域中创建与外部同名的变量时,Python在当前作用域中找到该变量后便停止向上查找。
在早期Python版本中,局部作用域无法访问其他局部作用域,仅能访问全局变量;而现代版本支持逐级向上查找,此处仅作补充说明。
正是由于这一特性,内部函数可以访问外部函数中的变量,这即构成了所谓的闭包。
注意:需严格区分对象之间的差异,例如:
def wai_hanshu():
a = []
def nei_hanshu(canshu):
a.append(canshu)
return a
return nei_hanshu
a = wai_hanshu() # 我创建了一个对象
b = wai_hanshu() # 我又创建了一个对象
print a
print b
print a(123)
print b(321)

在此例中,虽然操作的都是wai_hanshu中的变量,但a和b是完全独立的两个对象,它们位于不同的内存空间,数据相互独立。需注意避免混淆。
装饰器
实际上,装饰器是在闭包基础上进行了额外步骤,参见代码:
def zsq(func): # 装饰函数
def nei():
print '我在传入的函数执行之前做一些操作'
func() # 执行函数
print '我在目标函数执行后再做一些事情'
return nei
def login(): # 被装饰函数
print '我进行了登录功能'
login = zsq(login) # 我将被装饰的函数传入装饰函数中,并覆盖了原函数的入口
login() # 此时执行的就是被装饰后的函数了

阅读此段代码时,需明确以下几点:
1. 函数参数传递的是引用而非值。
2. 函数名本身也是变量,因此可以重新赋值。
3. 赋值操作时,先执行等号右侧的内容。
理解上述要点并结合代码,即可明白装饰器的概念。所谓装饰器,即在闭包基础上传递一个函数,并覆盖原函数的执行入口。此后调用该函数时,即可额外实现某些功能。装饰器的主要作用是在不修改原函数代码及其他调用代码的前提下,实现功能拓展。
考虑到每次手动重命名操作过于繁琐,Python提供了便捷的写法:
def zsq(func):
def nei():
print '我在传入的函数执行之前做一些操作'
func() # 执行函数
print '我在目标函数执行后再做一些事情'
return nei
@zsq # 自动将其下面的函数作为参数传到装饰函数中去
def login():
print '我进行了登录功能'
login()

此类便捷写法被称为Python的语法糖,你可能在许多地方见过这一术语。
带参数的装饰器:
def zsq(a):
print '我是装饰器的参数', a
def nei(func):
print '我在传入的函数执行之前做一些操作'
func() # 执行函数
print '我在目标函数执行后再做一些事情'
return nei
@zsq('123')
def login():
print '我进行了登录功能'

其效果相当于:login = zsq(123)(login),因此在此处尚未调用时即已执行。
装饰器的嵌套:
此处不再完整编写示例:
@deco1(deco_arg) @deco2 def func(): pass
其效果相当于:func = deco1(deco_arg)(deco2(func))
即按照从上到下的顺序进行嵌套。
关于闭包和装饰器的内容先介绍至此,后续如有需要再行补充。
以上关于深入理解Python中闭包与装饰器的内容即为小编分享的全部,希望能对大家有所帮助,也希望大家多多支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









