深入理解Python中的闭包与装饰器:原理、作用域及语法糖

七月
七月
发布时间:2026-10-12 | 2人浏览过
原创

本文深入解析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中闭包与装饰器的内容即为小编分享的全部,希望能对大家有所帮助,也希望大家多多支持本站。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表