Python 闭包函数和装饰器

Life consists not in holding good cards but in playing those you hold well.

一、闭包函数

装饰器的本质是闭包,我们先了解下闭包函数:

  • 1.在函数内部定义的函数
  • 2.引用了外部变量(但非全局变量)

也就是说,引用了外部变量(非全局变量)的这个嵌套内部函数被称为闭包函数。

判断是否是闭包函数

__closure__ 属性,用来判断该函数是否是闭包函数。如果是,返回 cell;否则,返回 None。
代码示例:

global_param = 100  # 全局变量


def outFunc(x):
    temp0 = 1 + x

    # 引用外部变量(非全局变量),inFunc1是闭包函数
    def inFunc1():
        temp1 = 2
        return temp0 + temp1

    # 引用外部的"全局变量",inFunc2不是闭包函数
    def inFunc2():
        temp2 = 3
        return temp2 + global_param

    # 不引用外部的变量,inFunc3不是闭包函数
    def inFunc3():
        temp3 = 4
        return temp3

    # 函数名.__closure__属性,用来判断函数是否是闭包
    print("inFunc1是否是闭包:", inFunc1.__closure__)  # 是闭包,返回cell
    print("inFunc2是否是闭包:", inFunc2.__closure__)  # 不是闭包,返回None
    print("inFunc3是否是闭包:", inFunc3.__closure__)  # 不是闭包,返回None

    return inFunc1() + inFunc2() + inFunc3()


print(outFunc(5))
print("outFunc是否是闭包:", outFunc.__closure__)

运行结果:

inFunc1是否是闭包: (,)
inFunc2是否是闭包: None
inFunc3是否是闭包: None
115
outFunc是否是闭包: None

分析:

  • inFunc1 引用了外部变量(且非全局变量)temp0,是闭包函数。
  • inFunc2 引用了外部变量(但却是全局变量)global_param,不是闭包函数。
  • inFunc3 没有引用外部变量,不是闭包函数。
  • outFunc 不是内嵌函数,不是闭包函数。
通过闭包函数改变外部变量
def outer_func():
    loc_list = []

    def inner_func(name):
        loc_list.append(len(loc_list) + 1)
        print('name=%s, loc_list=%s' % (name, loc_list))

    return inner_func


# 获取闭包函数
wrapper_func = outer_func()
print(wrapper_func.__name__, wrapper_func.__closure__)
# 通过多次调用改变外部变量
wrapper_func("func1")
wrapper_func("func2")
wrapper_func("func3")
print("------")
outer_func()('func1')
outer_func()('func2')
outer_func()('func3')

运行结果:

inner_func (,)
name=func1, loc_list=[1]
name=func2, loc_list=[1, 2]
name=func3, loc_list=[1, 2, 3]
------
name=func1, loc_list=[1]
name=func2, loc_list=[1]
name=func3, loc_list=[1]
闭包陷阱

注意,外部变量的取值,是调用闭包函数的那一时刻所对应的值,这里容易混淆。
通过下面两个示例,有助于我们进一步理解闭包陷阱。
1)是闭包函数:

def my_func(*args):
    fs = []
    # i是外部变量,执行完for循环后i=2
    for i in range(3):
        # func引用外部的i变量,是闭包函数
        def func():
            return i * i

        print(id(func), func.__closure__)
        fs.append(func)

    # 到这里,i=2
    return fs


# 执行完后,返回三个函数,并且外部变量i=2
fs1, fs2, fs3 = my_func(3)
# i是调用函数这一时刻的外部变量i值:由于i=2,所以i*i=4
print(fs1())  # 4
print(fs2())  # 4
print(fs3())  # 4

运行结果:

4330552352 (,)
4330917616 (,)
4330941184 (,)
4
4
4

2)不是闭包函数:

def my_func(*args):
    fs = []
    for i in range(3):
        # func内部没有引用外部的变量,不是闭包函数
        def func(_i=i):
            return _i * _i

        print(id(func), func.__closure__)
        fs.append(func)
    return fs


# 执行到这里,返回三个函数及对应的默认参数值
fs1, fs2, fs3 = my_func()
print(fs1())
print(fs2())
print(fs3())

运行结果:

4330549760 None
4330941040 None
4330940608 None
0
1
4

二、Python 装饰器

装饰器(Decorators)是 Python 的一个重要部分,用来给其它函数添加额外功能(在不改变原函数代码的情况下)的函数。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等。

它还可以使代码变得更简洁。比如,如果要给某几个函数添加“计时”功能,你无需同时修改这几个函数的代码,只需定义一个计时函数,通过@符号添加装饰器即可。

另外,对于一个函数有多个装饰器的情况,通过调整装饰器的顺序,并设置断点查看运行过程,得到结论:

  • 1.装饰器的加载顺序是:从下到上,装饰器的执行顺序是:从上到下。
  • 2.更具体一点,多个装饰器之间是嵌套关系,按就近原则从里到外依次嵌套
    @transform_input
    @cal_time
    def func(*args):
        ...
    
    等价于
    
    transform_input_wrapper(*args) {
        result = cal_time_wrapper(*args) {
            result = func(*args) {
                ...
            }
            return result
        }
        return result
    }
    

代码示例:

from time import time, sleep


# 功能1.定义一个函数,用来计算函数func的时间
def cal_time(func):
    print("自定义{统计时间}装饰器...")

    # 引用外部的func变量,cal_time_wrapper是闭包函数
    def cal_time_wrapper(*args):
        print("{统计时间}装饰器的代码 before")
        start_time = time()  # time()获取系统当前时间
        print("调用函数:", func.__name__)
        result = func(*args)
        end_time = time()  # time()获取系统当前时间
        print("{统计时间}装饰器的代码 after")
        print("装饰器:{},{}".format(func.__name__, end_time - start_time))
        return result

    print("cal_time_wrapper()是否是闭包:", cal_time_wrapper.__closure__)

    return cal_time_wrapper


# 功能2.定义一个函数,对输入参数进行转换,如果参数是float就转换为int
def transform_input(func):
    print("自定义{转换参数}装饰器...")

    # 引用外部的func变量,transform_input_wrapper是闭包函数
    def transform_input_wrapper(*args):
        print("{转换参数}装饰器的代码 before")
        # 转换参数
        args = tuple(int(param) if type(param) == float else param for param in args)
        print("调用函数:", func.__name__)
        result = func(*args)
        print("{转换参数}装饰器的代码 after")

        return result

    print("transform_input_wrapper()是否是闭包:", transform_input_wrapper.__closure__)

    return transform_input_wrapper


@transform_input
@cal_time
def test1(a, b, c, d):
    # 睡眠1秒
    sleep(0.1)
    return (a + b + c + d) / 2


@cal_time
@transform_input
def test2(a, b, c):
    sleep(0.1)
    return (a + b + c) / 2


@cal_time
def test3(a, b):
    sleep(0.1)
    return (a + b) / 2


@cal_time
def test4(a):
    sleep(0.1)
    return a / 2


print("------")
print(test1(1, 2, 3.5, 4))
print("------")
print(test2(1, 2, 3))
print("------")
print(test3(1, 2))
print("------")
print(test4(1))

运行结果:

自定义{统计时间}装饰器...
cal_time_wrapper()是否是闭包: (,)
自定义{转换参数}装饰器...
transform_input_wrapper()是否是闭包: (,)
自定义{转换参数}装饰器...
transform_input_wrapper()是否是闭包: (,)
自定义{统计时间}装饰器...
cal_time_wrapper()是否是闭包: (,)
自定义{统计时间}装饰器...
cal_time_wrapper()是否是闭包: (,)
自定义{统计时间}装饰器...
cal_time_wrapper()是否是闭包: (,)
------
{转换参数}装饰器的代码 before
调用函数: cal_time_wrapper
{统计时间}装饰器的代码 before
调用函数: test1
{统计时间}装饰器的代码 after
装饰器:test1,0.10467410087585449
{转换参数}装饰器的代码 after
5.0
------
{统计时间}装饰器的代码 before
调用函数: transform_input_wrapper
{转换参数}装饰器的代码 before
调用函数: test2
{转换参数}装饰器的代码 after
{统计时间}装饰器的代码 after
装饰器:transform_input_wrapper,0.10127425193786621
3.0
------
{统计时间}装饰器的代码 before
调用函数: test3
{统计时间}装饰器的代码 after
装饰器:test3,0.10306096076965332
1.5
------
{统计时间}装饰器的代码 before
调用函数: test4
{统计时间}装饰器的代码 after
装饰器:test4,0.10345077514648438
0.5

你可能感兴趣的:(Python 闭包函数和装饰器)