python闭包(工厂函数)与装饰器

python闭包(工厂函数)与装饰器

一、什么是闭包(工厂函数)?

1、函数的作用域

首先我们先明白python的一个变量的作用域,python对于每一个变量都有其的命名空间namespace,一个函数只能调用内置作用域(builtin)全局作用域(global)或者是这个函数自己的局部作用域(local)。清楚了这一点后,那什么是闭包呢。
在这里插入图片描述

2、什么是闭包?

简单地说,闭包就是由其他函数动态生成并返回的函数,也称为工厂函数,其关键性质是,被返回的函数可以访问其创建者(这里的创建者一般指的是最外层的函数)的局部命名空间中的变量。
综合来说,闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是闭包仍然能够保留那些绑定。闭包和普通函数的区别在于:即使创建者已经执行完毕,闭包仍能继续访问其创建者的局部命名空间。

3、闭包的作用

闭包可以避免使用全局值并提供某种形式的数据隐藏。它还可以提供面向对象的解决问题的解决方案。当在类中几乎没有方法(大多数情况下是一种方法)时,闭包可以提供一个替代的和更优雅的解决方案。 但是当属性和方法的数量变大时,更好地实现一个类。在闭包中,嵌套的作用域往往被lambda函数创建表达式来进行代替。
比如:

def closure(N):
    def action(X):
        return X ** N
    return action

def maker(N):
    return lambda X: X ** N

是一致的。

4、举两个普通闭包的例子
# 闭包:返回函数的函数
# 闭包的作用是被访问的函数可以访问其创建者的局部命名空间内的变量
def make_closure(a):
    def closure(b):
        print('I know the variable a of outer function is %d'%a)
        print('The answer of plus is', a + b)
    return closure

closure = make_closure(10)
closure(3)
I know the variable a of outer function is 10
The answer of plus is 13

这个例子中,我们的闭包可以接受外部函数的一个变量,并且打印它,内部函数暂时不执行任何功能。内部函数在单独执行时,仍然保留了外部函数的自由变量a的绑定,这就是一个简单的闭包。

# 求任意数的任意次方
def make_multiple_of(n):
    def multiple(x):
        print('The times is {}'.format(n))
        return x ** n
    return multiple

time3 = make_multiple_of(3)
time5 = make_multiple_of(5)

print(time3(2))
print(time5(2))
The times is 3
8
The times is 5
32

在这个例子中,multiple函数接受make_multiple_of函数指定的幂,在multiple函数单独执行的时候,仍然保留了make_multiple_of的变量绑定,完成了求次方的功能。

5、lambda嵌套的闭包

lambda匿名函数,能够创建一个可以被调用的函数,但是它返回该函数本身而不是将其赋值给一个变量名。

一个普通的例子

def make_closure(title):
    def greet(name):
        return title + ' ' + name
    return greet

say_hi = make_closure('Hi')
say_hi('Eric')
'Hi Eric'

下面我们使用一个lambda匿名函数来实现这个闭包

def make_closure(title):
    return lambda name: title + ' ' + name

say_hi = make_closure('Hi')
say_hi('Alice')
'Hi Alice'

接下来我们使用两个lambda匿名函数来实现上面的闭包

say_hi = lambda title: lambda name: title + ' ' + name
say_hi('Hi')('Eric')
'Hi Eric'

直接调用,不给lambda匿名函数赋予变量名

(lambda title: lambda name: title + ' ' + name)('Hi')('Eric')
'Hi Eric'
6、闭包的__closure__属性

闭包比普通的函数多了一个 closure 属性,该属性记录着自由变量的地址。

print(time3.__closure__)
(lambda title: lambda name: title + ' ' + name)('Hi')('Eric')
(<cell at 0x0000028A02079D00: int object at 0x00007FFC9D241F40>,)

二、装饰器

1、普通的装饰器

实际上,实现特殊方法__call__()的任何对象都被称为可调用。 因此,在最基本的意义上,装饰器是可调用的,并且可以返回可调用。基本上,装饰器接收一个函数,添加一些函数并返回。

写在前面,在python中一切皆对象,函数也不例外,所以我们可以将函数赋值另外的变量,使这两个变量指向同一个函数的引用。

先定义一个简单的装饰器

# 一个装饰器
def make_wrapper(func):
    def wrapper():
        print('func has been decorated')
        func()
    return wrapper

def ordinary():
    print('This is an ordinary function')

为了使用我们定义好的装饰器,我们可以这样调用:

ordinary = make_wrapper(ordinary)

这样我们其实是利用了闭包,打包保留了一些自由变量的绑定,将ordinary函数再赋给本身,但是这时的ordinary函数与一开始的函数多了一些变量的绑定,还是可以像调用之前的ordianry函数一样调用它。

ordinary()
func has been decorated
This is an ordinary function
2、装饰器语法糖

我们可以使用@wrapper_name这样的语法糖来代替ordinary = make_wrapper(ordinary)这样的复杂赋值过程,可以使代码更加简洁。

def make_wrapper(func):
    def wrapper():
        print('func has been decorated')
        func()
    return wrapper

@make_wrapper
def ordinary():
    print('This is an ordinary function')

ordinary()
func has been decorated
This is an ordinary function
3、标准的计时装饰器模板

这里写一个可以计算函数运行时间的装饰器,提供一种写装饰器的思路。

import time
from functools import wraps

def time_wrapper(func):
    @wraps(func)  # 这个wraps装饰器是python内置的,主要用于复制函数名称、注释文档等功能
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print("It takes {:2f} second".format(time.time() - start))
        return result
    return wrapper
    
@time_wrapper
def factorial(n):
# 计算阶乘
    result = 1
    for i in range(1, n + 1):
        result = result * i
    return result

在这里插入图片描述

4、总结

装饰器的典型行为可以概括为,通过闭包,接受并且绑定一些自由变量,把被装饰的函数替换成新的函数,二者接受相同的参数,而且(通常)返回被装饰的函数本该返回的值,同时还进行一些额外的操作。

functools.wraps装饰器可以把被装饰的函数的相关属性从func复制到装饰器中。

三、装饰器工厂函数(参数化装饰器)

Python把被装饰的函数作为第一个参数传给装饰器函数,那怎么让装饰器接受其他的函数呢?答案是:创建一个装饰器工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上。

由于装饰器是带参数的,所以我们再利用语法糖的时候也需要带上参数

一般来说,装饰器需要两层函数嵌套,由于装饰器工厂函数需要接受参数,所以装饰器工厂函数需要三层函数嵌套。

# 一个函数注册装饰器,方便查看注册的函数
registry = set()
def register(active=True):
    def decorator(func):
        print('runing registry(active=%s) -> wrapper(%s)' %(active, func))
        if active:
            registry.add(func)
        else:
            registry.discard(func)

        # 这里才是一个装饰器
        def wrapper(*args, **kwargs):
            print('do something')
            result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@register(active=False)
def func1():
    print('runing func1')

@register(active=True)
def func2():
    print('runing func2')

@register(active=True)
def func3():
    print('runing func3')
runing registry(active=False) -> wrapper(<function func1 at 0x0000028A020AC280>)
runing registry(active=True) -> wrapper(<function func2 at 0x0000028A020AC820>)
runing registry(active=True) -> wrapper(<function func3 at 0x0000028A020ACDC0>)

在这里插入图片描述

查看有哪些函数是注册的,func1是没有注册的,所以结果中没有func1
在这里插入图片描述

四、Reference

http://c.biancheng.net/view/5335.html
https://www.yiibai.com/python/closure.html
https://www.yiibai.com/python/decorator.html
《FluentPYthon》

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值