Posts Python-闭包+装饰器
Post
Cancel

Python-闭包+装饰器

前言

本文介绍下 闭包(closure) , 装饰器(decorator) 合理使用 闭包 可以简化程序复杂度,提高可读性。 闭包常常和 装饰器 一起使用,能很大提高程序的可读性和复用性。

闭包

所谓闭包,指的是:函数定义中存在“嵌套函数”场景,且外部函数返回的是一个函数。返回的函数可以赋给变量,在后续被调用。下面举个计算指数的用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def nth_power(exponent):
    def exponent_of(base):
        return base ** exponent
    return exponent_of # 返回值是exponent_of函数

square = nth_power(2) # 计算一个数的平方
cube = nth_power(3) # 计算一个数的立方 
square
# 输出
<function __main__.nth_power.<locals>.exponent(base)>

cube
# 输出
<function __main__.nth_power.<locals>.exponent(base)>

print(square(2))  # 计算2的平方
print(cube(2)) # 计算2的立方
# 输出
4 # 2^2
8 # 2^3

可以看到,上述这种使用闭包的场景:存在多次调用函数的情况(比如要频繁使用square/cube),可以将额外的工作放在外部函数(exponet参数在外部函数定义导入),这样就可以减少多次调用导致不必要的开销,提高程序的运行效率。

装饰器

装饰器闭包 有点类似,区别是:装饰器传入的是函数,装饰器返回的是是内部函数 wrapper(), wrapper() 函数并不会改变传入的函数,只会添加一些额外的功能行为,最常见的应用场景:检查合法输入(validation_check),缓存装饰器(LRU cache)。 一个基本用例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def my_decorator(func):
    def wrapper():
        print('wrapper of decorator')
        func()
    return wrapper

def greet():
    print('hello world')

greet = my_decorator(greet)
greet()

# 输出
wrapper of decorator
hello world

Python 中提供了 @ 的“语法糖”,上述用例可以修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
def my_decorator(func):
    def wrapper():
        print('wrapper of decorator')
        func()
    return wrapper

@my_decorator
def greet():
    print('hello world')

greet()

上述用法是最基本的装饰器用法,存在一些问题:

  1. 该装饰器函数无法导入参数:真实场景下通常会输入不定数量的参数
  2. 装饰器装饰后,原始函数的元信息发生了变化: func.__name 变成了 wrapper

上述,1.问题可以通过传入 *args, **kwargs ,2.可以通过内置的装饰器 @functools.wrap 进行元信息的保护(通过将原函数的元信息拷贝到对应的装饰器函数中),修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('wrapper of decorator')
        func(*args, **kwargs)
    return wrapper

@my_decorator
def greet(message):
    print(message)

greet.__name__

# 输出
'greet'

上述是采用函数作为装饰器使用,也可以采用类作为装饰器,类作为装饰器依赖于内置函数 __call() 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Count:
    def __init__(self, func):
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print('num of calls is: {}'.format(self.num_calls))
        return self.func(*args, **kwargs)

@Count
def example():
    print("hello world")

example()
# 输出
num of calls is: 1
hello world

example()
# 输出
num of calls is: 2
hello world

很多时候,装饰器也存在“嵌套使用”的情况,这里主要谈下其调用的顺序,对于如下嵌套场景:

1
2
3
4
5
@decorator1
@decorator2
@decorator3
def func():
    ...

其等价于:

1
decorator1(decorator2(decorator3()))

调用顺序用例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import functools

def my_decorator1(func):
    print('enter decorator1')
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('execute decorator1')
        func(*args, **kwargs)
    return wrapper

def my_decorator2(func):
    print('enter decorator2')
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('execute decorator2')
        func(*args, **kwargs)
    return wrapper

@my_decorator1
@my_decorator2
def greet(message):
    print(message)

greet('hello world')

# 输出
enter decorator2
enter decorator1
execute decorator1
execute decorator2
hello world
This post is licensed under CC BY 4.0 by the author.

Python-metaclass

Python-垃圾回收