闭包和装饰器

ぃ、小莉子 提交于 2020-02-10 17:45:12

闭包

  • 当一个函数在内部定义函数,并且内部的函数应用外部函数的参数或者局部变量,当内部函数被当做返回值时,相关参数和变量保存在返回的函数中,这种结果叫闭包。
# 闭包结构,myF5用到了myF4的参数args
def myF4(*args):
    def myF5():
        rst = 0
        for n in args:
            rst += n
        return rst
    return myF5
# 常见的闭包错误
def count():
    # 定义列表,列表里存放的是定义的函数
    fs = []
    for i in range(1,4):
        # 定义了一个函数f
        # f是一个闭包结构
        def f():
            return i*i
        fs.append(f)
    return fs

f1,f2,f3 = count()
print(f1())
print(f2())
print(f3())
# 此时的输出会是9,9,9,并不是我们想要的1,4,9
  • 造成上述状况的原因是,返回函数引用变量i,i并非立即执行,而是等到三个函数都返回的时候才统一使用,此时i已经变成了3,最终调用的时候,都是返回3*3
  • 此问题描述成: 返回闭包时,返回函数不能引用任何循环变量
  • 解决方案: 用函数的参数绑定循环变量的当前值,无论该循环变量以后如何改变,已经绑定的函数参数值不再改变
def count():
    # 定义列表,列表里存放的是定义的函数
    fs = []
    for i in range(1,4):
        # 定义了一个函数f
        # f是一个闭包结构
        def f(a=i):
            return a*a
        fs.append(f)
    return fs

装饰器

装饰器的形成

假设现在有一个hello函数

def hello():
    print("Hello world")
    
hello()

现在有新的需求需要对hello功能进行扩展,每次打印hello之前打印当前系统时间,而实现这个功能又不能改动现有代码

import time

def printTime(f):
    def inner(*args, **kwargs):
        print("Time:",time.ctime())
        return f(*args, **kwargs)
    return inner

# 上面定义了装饰器,使用的时候需要用到@
@printTime
def hello():
    print("Hello world")
hello()
>>> Time: Sat Feb  8 17:39:47 2020
    Hello world

装饰器的作用

  • 不想修改函数的调用方式,但是想在原来的函数前后添加功能
  • 装饰器的好处是,一旦定义,则可以装饰任意函数
  • 一旦被其装饰,则把装饰器的功能直接添加到定义函数的功能上

原则: 开放封闭原则

  • 开放:对扩展是开放的
  • 封闭:对修改是封闭的

可以看到装饰器一定是闭包,但闭包不一定是装饰器。

装饰器的固定模式

def wrapper(f):    # f是被装饰的函数
    def inner(*args,**kwargs):
        # 添加 被装饰函数前要添加的功能
        ret = f(*args,**kwargs)
        # 被装饰函数后要添加的功能
        return ret
    return inner

@wrapper
def f():    # 被装饰的函数
    pass 

f()

# 添加装饰器后执行f() 相当于是
# 没添加装饰器时
#f = wrapper(f)
#f()

打印被装饰函数的函数名

如果函数没有被装饰,要想打印函数名的话,执行以下命令。

def hello():
    print("Hello world")
print(hello.__name__)
>>> hello

当函数被装饰后,如果直接用上面的方法,打印出的名字会改变。

import time
def printTime(f):
    def inner(*args, **kwargs):
        print("Time:",time.ctime())
        return f(*args, **kwargs)
    return inner

@printTime
def hello():
    print("Hello world")

print(hello.__name__)
>>> inner

对此,我们需要从functools引入wraps包

import time
from functools import wraps

def printTime(f):
    @wraps(f)        # 用wraps装饰被装饰的函数f
    def wrapper(*args, **kwargs):
        print("Time:",time.ctime())
        return f(*args, **kwargs)
    return wrapper

@printTime
def hello():
    print("Hello world")
    
print(hello.__name__)
>>> hello

带参数的装饰器

假如我们之前已经对多个函数进行装饰,现在想取消掉对每个函数的装饰,如果一个一个加注释,当函数数量过大的时候,太费时间。我们可以通过flag标志,判断函数是否需要装饰

import time

flag = True

def outer(flag):
    def printTime(f):
        def wrapper(*args, **kwargs):
            if flag:
                print("Time:",time.ctime())
                return f(*args, **kwargs)
            else:
                return f(*args, **kwargs)
        return wrapper
    return printTime

@outer(flag)    # 先调用outer(flag)返回printTime,最后相当于是@printTime
def hello1():
    print("Hello world")
    
@outer(flag)
def hello2():
    print("Hi")
    
hello1()
hello2()

多个装饰器装饰一个函数

def outer1(f):
    def wrapper1(*args, **kwargs):
        print("outer1, before func")
        f(*args, **kwargs)
        print("outer1, after func")
    return wrapper1
    
def outer2(f):
    def wrapper2(*args, **kwargs):
        print("outer2, before func")
        f(*args, **kwargs)
        print("outer2, after func")
    return wrapper2
    
@outer2
@outer1
def func():
    print("In func")
    
func()

运行结果为

outer2, before func
outer1, before func
In func
outer1, after func
outer2, after func

解释下运行结果为什么如上所示

函数执行是从上往下,第一步先定义outer1,然后定义outer2,执行到@outer2时,因为紧挨着他的是@outer1 ,不是某个函数的定义,先执行@outer1 ,相当于是执行func = outer1(func),从而将wrapper1返回给func;再执行@outer2,相当于是func = outer2(wrapper1),进而将wrapper2返回给func;最后调用func()时相当于调用wrapper2()。

红色标记表示在调用func()前的执行顺序,蓝色标记表示调用后的执行顺序。
函数执行顺序

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!