闭包
在了解装饰器之前,先要了解闭包。闭包就是函数内部保存了一些变量信息。
比如下面的例子
python
def outer(x):
def inner(y):
return x+y
return inner
closure = outer(5)
print(closure)
#输出: <function outer.<locals>.inner at 0x00000198065C6F20>
print(closure.__name__)
#输出:inner
在上面的代码中,当执行 outer(5)
的时候,返回的是inner
这个函数引用^1^。也就是说上面的closure
是一个函数,打印出来也确实是一个函数对象。把closure
这个函数的名称打印出来,是inner
,也印证了实际上closure
这个变量指向的是inner
这个函数。
前面说闭包是保存了一些变量信息,实际上在这里就是说closure
这个变量指向的函数,也就是inner
这个函数保存了那个数字5
这个变量的值,5
这个值是在inner
的外层函数outer
中输入的,由于inner
还有closure
指向它,所有它没有在内存中注销,就保存了这个值。
装饰器
理解了闭包,再来看装饰器。
装饰器本质上,是把函数作为参数传递给另外的函数来执行。
比如下面的例子
python
def decorator(func):
def wrapper(*args, **kwargs):
print('我是装饰器中要执行的内容')
return func(*args, **kwargs)
return wrapper
def say_hello():
print(f'{say_hello.__name__}')
print(f'hello')
say_hello = decorator(say_hello)
print('------')
say_hello()
#输出:
# ------
# 我是装饰器中要执行的内容
# wrapper
# Hello
# 在python中有一种语法可以用来使用装饰器,和上面这个完全等价
@decorator
def say_hello():
print(f'{say_hello.__name__}')
print(f'hello')
say_hello()
#输出:
# 我是装饰器中要执行的内容
# wrapper
# Hello
上面例子中,写法一和写法二是完全等价的。 写法一:
python
say_hello = decorator(say_hello)
写法二:
python
@decorator
def say_hello()
print(f'hello')
在这两种写法里面,实际上是分了两个步骤来执行:
- 第一步找到
say_hello
,这个变量实际上指向的是wrapper
对象。同时又把say_hello
这个变量的引用传给给了装饰函数decorator
中闭包的func
变量,这个func
变量此时指向的就是原来say_hello
指向的那个函数体。 - 第二步是执行
say_hello
,就是那个括号,也就是wrapper()
,这个执行下来,所以function
的名称就是wrapper
了
注意:上面的say_hello
实际上是decorator(say_hello)
的执行结果,也就是decorator
函数的返回值,是wrapper
这个对象,返回的是对wrapper
对象的引用,并没有执行decorator
这个函数内部的wrapper
函数。所以这里就会存在闭包的现象,如果在decorator
中定义了参数,那么wrapper
就会记住这个变量值,在这里就是记住的say_hello
这个变量的值,就是那个最初的函数体,也就是闭包的概念了。
python
def decorator(func):
def wrapper(*args, **kwargs):
print('我是装饰器中要执行的内容')
print(f'decorator 中的func 的id是{id(func)}')
return func(*args, **kwargs)
return wrapper
def say_hello():
print(f'{say_hello.__name__}')
print(f'hello')
print(f'say_hello id is {id(say_hello)}')
say_hello = decorator(say_hello)
print('---')
say_hello()
# 输出:
# 注意看func的内存地址和最初的say_hello的内存地址是一样的
# say_hello id is 2307155184192
# ---
# 我是装饰器中要执行的内容
# decorator 中的func 的id是2307155184192
# wrapper
# hello
所以就会引出带参数的装饰器,装饰器工厂。
带参数的装饰器
还是上面的例子,请看
python
def decorator_para(para):
def decorator(func):
def wrapper(*args, **kwargs):
print(f'我是装饰器中要执行的内容-{para}')
return func(*args, **kwargs)
return wrapper
return decorator
# @decorator_para('装饰器参数1')
def say_hello():
print(f'{say_hello.__name__}')
print(f'Hello')
say_hello = decorator_para('装饰器参数1')(say_hello)
say_hello()
这里 写法一:
python
@decorator_para('装饰器参数1')
def say_hello():
print(f'{say_hello.__name__}')
print(f'Hello')
写法二:
python
say_hello = decorator_para('装饰器参数1')(say_hello)
也是等价的。 同样也是分为了两步:
- 第一步先去执行了
decorator_para('装饰器参数1')
得到了真正的装饰器decorator
,而decorator_para('装饰器参数1')
执行的结果是返回了decorator
函数。 - 第二步找到
say_hello
,这个变量实际上指向的是decorator(say_hello)
返回的对象,也就是wrapper
,注意传入的para
在wrapper
中用到了,被引用了,也就被wrapper
记住了,这里就是闭包的应用了。接下来就和不带参数的装饰器相同了
重叠的装饰器
还是举例说明
python
def decorator1(func):
def wrapper1(*args, **kwargs):
print('我是装饰器中1要执行的内容')
return func(*args, **kwargs)
return wrapper1
def decorator2(func):
def wrapper2(*args, **kwargs):
print('我是装饰器中2要执行的内容')
return func(*args, **kwargs)
return wrapper2
@decorator1
@decorator2
def say_hello():
print(f'{say_hello.__name__}')
print(f'Hello')
say_hello = decorator1(decorator2(say_hello))
say_hello()
#输出:
# 我是装饰器中1要执行的内容
# 我是装饰器中2要执行的内容
# wrapper1
# Hello
上面的两种写法是完全一样的。 在执行say_hello()
的时候实际上分为了几个步骤:
- 首先
say_hello
指向的逻辑是:decorator2(say_hello)
的返回对象是wrapper2
decorator1(wrapper2)
的返回对象是wrapper1
say_hello()
执行顺序:- 先执行
wrapper1(wrapper2)
,在此之前就会执行wrapper1
中的打印内容 wrapper2(say_hello)
,在此之前就执行wrapper2
的打印内容
- 先执行
- 所以最后
say_hello
指向的是wrapper1
,打印say_hello.__name__
才会打印成wrapper1
. 可以把堆叠装饰器想象成洋葱:当调用函数时,程序会先穿过外层装饰器,再到内层装饰器,最后执行原函数;当原函数执行完毕后,会按照相反的顺序退出各个装饰器。
Footnotes
- 函数名称后面跟了括号,就是执行,不跟括号就是一个函数引用,实际上就是又增加一个变量名去指向这个函数体。 ↩