之前一直对python中装饰器的概念比较模糊,今天阅读了一些相关的文章,现总结一下。

闭包

闭包(closure)就是一个函数,这个函数可以记住封闭作用域里的值,而不管封闭作用域是否还在内存中。

闭包的形成条件:

  • 在外部函数中定义了内部函数
  • 内部函数里使用了外部函数的临时变量
  • 外部函数的返回值是内部函数的引用

以下是一个闭包的示例:

# 闭包函数的实例
# outer是外部函数
def outer(name):
    age = 10  # name,age是局部变量
    # inner是内部函数(嵌套函数)
    def inner():
        # 在内部函数中,用到了外部函数的临时变量
        print("%s今年%d岁了。" % (name, age))

    # 外部函数的返回值是内部函数的引用
    return inner

# 调用外函数传入参数"张三"
# 此时外函数两个临时变量 name是"张三" age是10 ,并创建了内函数,然后把内函数的引用返回存给了person1
# 外函数结束的时候发现内部函数将会用到自己的临时变量,这两个临时变量就不会释放,会绑定给这个内部函数
person1 = outer("张三")
# 调用内部函数,看一看内部函数是不是能使用外部函数的临时变量age
# person1存了外函数的返回值,也就是inner函数的引用,这里相当于执行inner函数
person1()     # 张三今年10岁了。
person2 = outer("李四")
person2()     # 李四今年10岁了。

一般情况下,一个函数结束时,函数的局部变量所占内存会被释放。但是闭包特殊,如果外部函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,那么在其结束时,返回内部函数的同时,会把外部函数的临时变量绑定给了内部函数。所以外部函数结束后,调用内部函数的时候仍然能够使用外部函数的临时变量。

修改闭包变量

在闭包中,内部函数可使用 nonlocal 关键字来修改外部函数的临时变量。(仅python3中支持)

# 修改闭包变量的实例
# outer是外部函数
def outer(name):
    age = 10  # name,age是局部变量
    # inner是内部函数(嵌套函数)
    def inner():
        # 在内部函数中,用到了外部函数的临时变量
        # 内函数中想修改闭包变量,用nonlocal关键字声明
        nonlocal age
        age = age + 1
        print("%s今年%d岁了。" % (name, age))

    # 外部函数的返回值是内部函数的引用
    return inner

person3 = outer("张三")
person3()     # 张三今年11岁了。
print(person3.__closure__)   # 使用__closure__属性查看闭包变量,返回一个变量元组
person4 = outer("李四")
person4()    # 李四今年11岁了。
print(person4.__closure__)

闭包变量唯一

使用闭包的过程中,外部函数被调用一次返回了内部函数的引用,虽然每次调用内部函数,是一个函数执行后消亡的过程,但是闭包变量实际上只有一份,每次开启的内部函数都在使用同一份闭包变量。

# 多次调用内部函数时,闭包中的变量相同
def outer(name):
    age = 10  # name,age是局部变量
    # inner是内部函数(嵌套函数)
    def inner():
        # 在内部函数中,用到了外部函数的临时变量
        # 内函数中想修改闭包变量,用nonlocal关键字声明
        nonlocal age
        age = age + 1
        print("%s今年%d岁了。" % (name, age))

    # 外部函数的返回值是内部函数的引用
    return inner

person5 = outer("张三")
person5()    # 李四今年11岁了。
person5()    # 李四今年12岁了。

@函数装饰器

闭包在python中的主要应用在@函数装饰器中。

“@函数”(函数A)装饰另一个函数(函数B)时的执行流程:

  1. 将被装饰的函数(函数B)作为参数传给@符号引用的函数(函数A)。
  2. 将函数B替换为第一步的返回值。

装饰器的示例:

# 装饰器
def auth(fn):
    def auth_fn(*args):
        print("用一条语句模拟权限检查")
        fn(*args)
    return auth_fn

@auth
def test(a, b):
    print("执行test函数,参数a:%s,参数b:%s" % (a,b))

test(20,15)
"""
输出:
用一条语句模拟权限检查
执行test函数,参数a:20,参数b:15

执行逻辑:
装饰效果相当于auth(test)
test被替换为auth_fn,即执行auth_fn(20,15)
"""

装饰器可以用于在被修饰函数前面或者后面添加一些额外的处理逻辑,比如示例所示的权限检查,比如记录日志等操作。此时无需修改被修饰函数的代码,只需要增加一个修饰即可。

单例模式

python中单例模式的实现可以使用装饰器。

# 单例模式
def Singleton(cls):
    _instance = {}

    def _singleton(*args, **kargs):
        if cls not in _instance:
            _instance[cls] = cls(*args, **kargs)
        return _instance[cls]

return _singleton


@Singleton
class A(object):
    def __init__(self, x=0):
        self.x = x

    def get_x(self):
        print(self.x)

a1 = A(2)
a2 = A(3)
a1.get_x()  # 2
a2.get_x()  # 2

注意点

由于局部变量常驻在内存中,可能会造成内存泄漏问题。

参考文献

  1. 《疯狂python讲义》

  2. python中闭包详解

  3. Python中的单例模式的几种实现方式的及优化