Python的闭包与函数装饰器

闭包

所谓闭包,简单的说就是在函数 f 里面定义另一个函数 g,然后把 g 作为 f 的返回值。由于 Python 里面函数是一等公民(可以看作对象,随意赋值),所以我们可以很方便地实现闭包:

def multiply(a):
    def f(b):
        return a * b
    return f

l = (1, 2, 3, 4, 5)
# 输出(2, 4, 6, 8, 10)
print(tuple(map(multiply(2), l)))
# 输出(3, 6, 9, 12, 15)
print(tuple(map(multiply(3), l)))

关于闭包,有一个常见的坑,参见以下示例代码:

def funcGenerator(n):
    l = []
    for a in range(n):
        def f(b):
            return a * b
        l.append(f)
    return l

l = (1, 2, 3, 4, 5)
# 猜猜会输出什么?
print(tuple(map(funcGenerator(5)[2], l)))

你可能会觉得输出的是 (2, 4, 6, 8, 10),然而实际情况是输出 (4, 8, 12, 16, 20)!原因在于,Python 里面所有的“变量”本质上都只是对于对象的“引用”(或者说,类似于 C 中的指针),所以,最终所有的 a 都指向了数字 4。

正确的写法是这样的:

def multiply(a):
    def f(b):
        return a * b
    return f

def funcGenerator(n):
    l = []
    for i in range(n):
        l.append(multiply(i))
    return l

一般说来,内层函数可以访问外层函数的变量,但是不能修改,为了解决这个问题,Python 引入了 nonlocal 关键字,只要加入一行声明“nonlocal <变量名>”,就可以修改外层函数的变量了。

小问题:下面的代码会输出什么?(答案在文末)

def multiply(a):
    def f(b):
        nonlocal a
        a += 1
        return a * b
    return f

l = (1, 2, 3, 4, 5)
f1 = multiply(2)
f2 = multiply(2)
print(tuple(map(f1, l)))
print(tuple(map(f1, l)))
print(tuple(map(f2, l)))

函数装饰器

装饰器就是 Python 的一个语法糖,只要理解了闭包,装饰器也就不在话下了。

假设我们需要测量一个函数的执行时间,最简单的办法就是利用 time 标准库中的 perf_counter()。

import time

def f():
    pass

a = time.perf_counter()
f()
print(time.perf_counter() - a)

 但是很多时候,我们只是需要临时测量一下函数的执行时间,有没有什么办法,可以简单地完成这个任务,同时,测量完之后,又可以很快地收拾干净呢?装饰器可以解决这个问题。我们先编写一个装饰器:

import functools

def timing(func):
    # 这一行的作用比较复杂,只要记住一定要加上就好
    @functools.wraps(func)
    def wrapper(*args, **kw):
        a = time.perf_counter()
        r = func(*args, **kw)
        print(time.perf_counter() - a)
        return r
    return wrapper

这里的 timing(func) 就是一个典型的装饰器,要使用它,只需要在函数定义时,加上一行 @timing 即可。

# 相当于:f = timing(f)
@timing
def f():
    pass

f()

这样,f 就被我们“装饰”了,执行 f 的时候,功能不变,只不过结束时,会打印出执行的时间。想要恢复正常时,把 @timing 去掉即可。

我们再看一个更复杂的例子:

def replace(new_func):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            return new_func(*args, **kw)
        return wrapper
    return decorator

@repalce(lambda *args, **kw: None)
def f():
    print('foobar')

f()

这里出现了三重嵌套。replace 是一个函数,它返回一个装饰器。然后我们用这个装饰器修饰 f,实现了“偷梁换柱”,用 lambda *args, **kw: None 这个匿名函数替换了原先的 f。当然,你也可以玩一个四重甚至 n 重嵌套,不过很少用。


答案:

(3, 8, 15, 24, 35)
(8, 18, 30, 44, 60)
(3, 8, 15, 24, 35)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值