【Python】闭包和装饰器

人生得意须尽欢,莫使金樽空对月

什么是闭包

在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。

听起来感觉比较绕,我们通过代码来分析:

def outer():
    msg = "I'm outer"

    def inner():
        print(msg)
    return inner

inner = outer()
inner() # 输出 I'm outer

通过代码我们可以发现:

  1. 函数里面嵌套函数
  2. 内部函数使用了外部函数的变量msg
  3. 外部函数返回了内部函数名,在Python中也可以说是返回了内部函数的引用

当我们调用outer()的时候,直接返回了inner这个内部函数名,此时outer函数执行完毕,但是我们继续通过返回的值(inner,这个名字随便取),执行inner(),输出打印msg信息。

正常来讲,外部函数执行完毕,其变量应该销毁,但是嵌套函数引用了这个变量,将这个局部变量封闭在了嵌套函数中,这样就形成了一个闭包。

闭包修改数据

通过上面的例子我们知道,内部函数可以访问外部函数的变量,但是我们怎么可以修改呢!因为作用域不同,就好比我们修改全局变量需要声明global一样,对于闭包变量的修改变量同样需要声明,就是利用nonlocal,咱们通过代码来看一下:

def outer():
    x = 200

    def inner():
        nonlocal x  # 想要修改外部函数的值必须添加此关键字
        print("-----1-----%d" % x)  # 200
        x += 100  # 修改了外部函数的数据
        print("-----2-----%d" % x)  # 300
    return inner

inner = outer()
inner()

通过代码打印可以发现,nonlocal关键字可以修改闭包外部函数的变量,当变量名与全局变量的名称相同时,遵循LEGB原则查找变量。

现在我们闭包应该了解的差不多了,接下来就是装饰器,装饰器的实现就和闭包很像。

什么是装饰器

装饰器能够在那个函数执行前或者执行后分别运行一些代码,使得可以再装饰器里面访问并修改原函数的参数以及返回值,以实现约束定义、调试程序、注册函数等目标。

python装饰器本质上就是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数对象(函数的指针)

简单来说他们是修改其他函数功能的函数,我们简单举一个例子:

def outer(func):
    def inner():
        print("-----权限1----")
        print("-----权限2----")
        func()

    return inner

@outer  # 等价于func = outer(func)
def func():
    print("----func----")

func()

看完代码我们发现装饰器有三处不同:

  1. 在函数头上定义了一个@outer
  2. outer函数传入了一个参数func
  3. 在内函数的最后执行了func()

然后我们具体分析一下代码的执行流程:由于装饰器的作用,把我们当前的函数进行了装饰,也就演变成了func = outer(func),把我们的函数名传给了outer函数,返回了inner,此时func = inner,装饰完成。当我们调用func()的时候,实际调用的inner(),然后依次打印--权限1------权限2---,接着执行func(),此时的func就是最开始被装饰的函数,最后打印----func---,代码执行结束。

整个过程比较绕的就是inner函数和outer函数执行顺序发生了变化,主要是函数引用发生了改变。其实也是利用了闭包的原理。

装饰器的小问题

Python装饰器(decorator)在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变)。

这是什么意思呢,我们通过代码举一个例子:

def outer(func):
    def inner():
        """这个函数是inner"""
        print("-----权限1----")
        print("-----权限2----")
        func()

    return inner

@outer  # 等价于func = outer(func)
def func():
    """这个函数是func"""
    print("----func----")

func()
print(func.__name__) # inner
print(func.__doc__) # 这个函数是inner

代码的最后执行我们发现,我们定义的func函数一些属性变成inner的属性,这可不是我们希望的,为此,Python的functools包中提供了一个叫wraps的装饰器来消除这样的副作用。很简单,来看一下代码:

from functools import wraps

def outer(func):
    @wraps(func) # ***修改的地方***
    def inner():
        """这个函数是inner"""
        print("-----权限1----")
        print("-----权限2----")
        func()

    return inner

@outer  # 等价于func = outer(func)
def func():
    """这个函数是func"""
    print("----func----")

func()
print(func.__name__) # inner
print(func.__doc__) # 这个函数是inner

至于为什么@warps(传入参数名)可以做到,具体我也不太清楚,想要了解的可以去网上查查。

多种类型装饰器

有参数,无返回值

def outer(func):
    def inner(*args, **kwargs):
        func(*args, **kwargs)

    return inner

@outer
def func(*args, **kwargs):
    pass

func()

有参数,有返回值

def outer(func):
    def inner(*args, **kwargs):
        return func(*args, **kwargs)

    return inner

@outer
def func(*args, **kwargs):
    return "ok"

func()

类装饰器

class Outer(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        return self.func()

@Outer  # 相当于:func = Outer(func),相当于创建一个对象,而func(),会调用__call__
def func():
    return "ok"

func()  # 相当于 对象名() ,直接调用__call__方法

带有参数的装饰器

def level(level_num):
    def outer(func):
        def inner(*args, **kwargs):
            if level_num == 1:
                print("---权限级别1,验证---")
            elif level_num == 2:
                print("---权限级别2,验证---")
            return func(*args, **kwargs)

        return inner

    return outer


# 1.调用level将参数做实参传递 相当于调用level(1) 返回值 outer
# 2.用上一步的返回值当作装饰器对func函数进行装饰 func = outer(func)

@level(1) 
def func(*args, **kwargs):
    return "ok"

func()
上一篇:Python之系列函数(二)


下一篇:技术分享:基本排序算法