迭代器
- 迭代器是一种能够遍历访问集合内元素的数据类型
- 普通的集合能够通过
[index]
索引下标,本质是通过实现__getitem__
魔法方法完成的,而迭代器是不能根据索引读取数据的,其提供了一种惰性的方式从集合当中一个一个地拿数据,并且每拿一个数据,都会将该数据从迭代器当中丢弃 - 迭代器的一个主要优势就是节省内存,例如需要迭代获取大量的数据,如果这些数据存在一个序列对象中,如列表,那么数据将一次性全部载入到内存当中,必然消耗大量的内存,但如果我们将其存放到一个迭代器当中,那么一次只取出一个,内存消耗很少
- 自定义迭代器,需要实现
__iter__
和__next__
的魔法方法,举例:
class A:
def __init__(self, data):
self.data = data
self.index = 0
def __iter__(self):
return self
def __next__(self):
self.index += 1
try:
data = self.data[self.index - 1]
except IndexError:
raise StopIteration
return data
a = A([1,2,3])
for each in a:
print(each)
# 1
# 2
# 3
通过迭代器我们可以惰性求值,甚至迭代一个无穷的对象,例如我们定义一个计算斐波那契数列的迭代器:
class A:
def __init__(self):
self.x = 1
self.y = 1
self.index = 0
def __iter__(self):
return self
def __next__(self):
self.index += 1
if self.index <= 2:
return 1
data = self.x + self.y
self.x = self.y
self.y = data
if self.index > 100:
raise StopIteration
return data
a = A()
for i in range(5):
print(next(a))
# 1
# 1
# 2
# 3
# 5
从上面例子可以看出迭代器就是可将可迭代对象(如列表、元组、字典等)惰性输出的一种工具,比如for...in...
就是应用了迭代器的原理,我们可以简单模拟一下for...in...
的实现:
while True:
try:
next(data)
except StopIteration:
break
上面就等价于:
for xxx in data:
xxx
可迭代对象如果想转成迭代器可以用iter()
函数,然后用next()
函数就可以一个个内容输出了,每输出一个,迭代器里就少一个,直到空了就不能输出了,举例:
>>> str1 = 'abc'
>>> it1 = iter(str1) #str1变成一个迭代器保存到it1里
>>> next(it1)
'a'
>>> next(it1)
'b'
>>> next(it1)
'c'
>>> next(it1)
Traceback (most recent call last):
File "<pyshell#198>", line 1, in <module>
next(it1)
StopIteration #迭代器空了,输出报错
>>> it1 = iter(str1) #再次把str1变成一个迭代器保存到it1里
>>> for each in it1: #结合for输出
print(each)
a
b
c
可迭代对象/迭代器
- 可迭代对象:实现
__iter__
方法就行,可以使用for
语句进行迭代,但不能使用next
函数进行迭代 - 迭代器:在可迭代对象的基础上实现
__next__
方法,可以使用next
函数迭代
例如list
是可迭代对象,但并不是迭代器,可以通过iter
函数将其转为迭代器,举例:
from collections.abc import Iterable, Iterator
li = [1,2,3]
print(isinstance(li, Iterable))
print(isinstance(li, Iterator))
print(isinstance(iter(li), Iterable))
# True
# False
# True
生成器
是迭代器的一个子类,可以独立函数调用,函数可以暂停或者挂起,并在需要时从暂停地方继续或者重新开始,只要函数里有yield
语句就是生成器,举例:
>>> def abc():
print("生成器执行")
yield 1 #此处可以暂停或挂起,下次再调用函数的时候
print("生成器再次执行")
yield 2
>>> a = abc()
>>> next(a)
生成器执行 #执行到yield 1那里暂停
1
>>> next(a)
生成器再次执行 #从yield 1的下一句开始继续执行
2
生成器优势
通过生成器我们可以实现惰性求值,例如斐波那契的实现,假如想要获取每一步的值,如果通过一次性计算完返回,例如下面代码,将会造成大量的空间浪费:
def fab(n):
li = [1, 1]
for i in range(2, n):
li.append(li[i-1] + li[i-2])
return li
print(fab(10))
上面代码将前n个数一次性计算完返回,数组可能十分巨大,内存消耗严重,因此我们可以通过生成器来每一步都中断返回值,然后再继续执行,从而实现O(1)级别的空间消耗,举例:
def fab(n):
x = i = 0
y = 1
while i < n:
yield y
x, y = y, x + y
i += 1
x = fab(10)
for i in range(10):
print(next(x))
生成器/函数
def gen():
yield 1
g = gen()
print(type(g))
# <class 'generator'>
可以看到明明定义的是函数,但执行后返回的不是一个值,而是一个生成器对象,实际在python编译字节码时就会发现该函数里有yield
,所以会返回一个生成器对象
yield/return
yield
可以理解成一个特殊的return
,平常的return返回的是个值,而且返回后下次调用该函数又是重新开始,而替换成yield后返回的是个生成器,而且每次返回后下次调用该函数会继续从上一次yield后的地方继续,举例:
def yield_(i): #yield返回
for each in i:
yield each
#返回一个生成器,每调用一次,下次就从上一次结束的下一步开始,第一次返回0的,第二次返回1的...
def return_(i): #return返回
for each in i:
return each #返回一个值,不管调用多少次,每次都只返回0后结束
a = yield_(range(10))
#里面相当于调用了10次,第一次返回0,第二次返回1...,结果为一个包含0到9的生成器
b = return_(range(10)) #不管调用几次,b永远等于0
for each in a: #生成器循环输出
print(each) #输出0,1,2,3,...9
print(b) #只能输出一次,输出0
生成器相关方法
send
send
方法可以传递值进入生成器内部,同时还可以重启生成器执行到下一个yield
的地方,所以有了send
方法,生成器不但能产出值(通过yield
),同时也能接收值(通过send
),举例:
def gen():
recv = yield 1
# 产出的1被外部接收,外部传入的10被recv接收
yield recv + 1
g = gen()
print(g.send(None))
# 第一次启动生成器的时候必须传入None
print(g.send(10))
# 传入10
# 1
# 11
send
方法让我们能够实现与生成器之间的通信,因此我们可以简单实现一个生产者消费者模型:
import time
def consumer():
while True:
product = yield
print("consume:", product)
def producer():
i = 0
while True:
time.sleep(1)
con.send("product{}".format(i))
i += 1
con = consumer()
next(con)
# 使用send之前需要通过next或者send(None)启动生成器
producer()
结果:
consume: product0
consume: product1
consume: product2
...
-
send
和next
区别:
next
可以理解成send(None)
,因此send
可以说包含next
,next
能做的send
也能做,而且send
还能传自定义值到生成器内部。不过有一点要注意:在生成器第一次启动时,由于还没有执行到yield
语句,因此无法接收值,此时如果传值则会报错,所以第一次send
的时候必须传入一个None
throw
往生成器里抛异常,其会往上一次yield
的地方抛出异常,并且如果异常被捕获,其会继续执行到下一个yield
处,并将yield
值返回给抛异常的地方,举例:
def gen():
try:
yield 1
except TypeError as e:
# 生成器内部捕获异常
print(e)
print("aaa")
yield 2
yield 3
g = gen()
print(next(g))
print(g.throw(TypeError, "please yield str"))
print("after throw")
print(next(g))
# 1
# please yield str
# aaa
# 2
# after throw
# 3
可以看出生成器捕获了抛出的异常以后,其会继续执行,并将yield 2
的值返回给了throw
处
close
关闭生成器,之后生成器将停止输出值,举例:
def gen():
yield 1
yield 2
yield 3
g = gen()
print(next(g))
g.close()
print(next(g))
# 1
# StopIteration
close
方法实际上就是抛出了一个GeneratorExit
异常,举例:
def gen():
try:
yield 1
except GeneratorExit as e:
print("error")
raise GeneratorExit
yield 2
yield 3
g = gen()
print(next(g))
g.close()
print(next(g))
# 1
# error
# 报错:StopIteration
可以看到异常被捕捉,而生成器一旦抛出这个异常,将会停止迭代。此时如果生成器后面还有yield语句,会报错,举例:
def gen():
try:
yield 1
except GeneratorExit as e:
pass
# 如果后面还有yield就会报错
# yield 2
# yield 3
g = gen()
print(next(g))
g.close()
# 1
注:
GeneratorExit
继承自BaseException
,而不是Exception
,因此使用Exception
无法捕获该异常,举例:
try:
raise GeneratorExit
except Exception:
print(111)
except BaseException:
print(222)
print(GeneratorExit.__bases__)
print(Exception.__bases__)
print(BaseException.__bases__)
# 222
# (<class 'BaseException'>,)
# (<class 'BaseException'>,)
# (<class 'object'>,)
可以看到GeneratorExit
和Exception
都是继承自BaseException
,BaseException
才是所有异常的基类
生成器相关语法
yield from
python3.3后加的语法,假如我们有一个需求:需要将多个迭代器合到一个迭代器里并遍历返回内容,那么可以通过下面方法实现:
def chain(*iteratorss):
for iterators in iteratorss:
for item in iterators:
yield item
a = {"x": 1, "y": 2}
b = ["a", "b"]
for each in chain(a, b, range(3)):
print(each)
# x
# y
# a
# b
# 0
# 1
# 2
而yield from
语法则可以遍历迭代器,并将内容一个个给yield
出来,例如上面的实现可以改成下面这样:
def chain(*iteratorss):
for iterators in iteratorss:
yield from iterators
# 通过yield from来替换遍历迭代器yield
a = {"x": 1, "y": 2}
b = ["a", "b"]
for each in chain(a, b, range(3)):
print(each)
yield
/yield from
对比
-
yield
:直接将内容返回 -
yield from
:需要传入一个迭代器,将迭代器里内容遍历地yield
出来,举例:
def y(iterator):
yield iterator
def yf(iterator):
yield from iterator
for each in y(range(3)):
print(each)
print("-"*10)
for each in yf(range(3)):
print(each)
# range(0, 3)
# ----------
# 0
# 1
# 2
可以看到如果传入一个迭代器,那么yield
将返回一个迭代器对象,而yield from
返回的是迭代器内部的所有元素
yield from
语法还实现了在yield from
子迭代器时,子迭代器和调用yield from
函数的外部能直接通信,举例:
def gen():
v = yield 1
print("send:", v)
yield 2
return 100
# gen只能yield两次,如果想要获取return的内容,需要执行第三次;
# 此时将抛出一个StopIteration的异常,而异常信息则是return的内容
def yf():
val = yield from gen()
# 外部send的值直接传入生成器;
# 生成器yield的值直接yield到外部;
# 生成器return的值通过val接收
print("return:", val)
# yield from获取gen抛出的StopIteration的异常,并获取异常的信息
yield "yf yield"
g = yf()
print("yield:", next(g))
print("yield:", g.send(10))
# send的值将会通过yf生成器传递到gen生成器里;
# gen生成器yield的值会通过yf生成器yield到这里
print("yield:", next(g))
# yield: 1
# send: 10
# yield: 2
# return: 100
# yield: yf yield
可以看到外部接收到了gen
生成器yield
的值,外部往yf
传入值时,会被传入到gen
当中,当gen
返回值时,将会被yf
接收(实际上是处理了gen
抛出的异常),所以yf
可以当做是一个充当中介身份的生成器,假如上面的代码不使用yield from
,那么就需要我们自己去捕获异常进行处理,举例:
def gen():
v = yield 1
print("send:", v)
yield 2
return 100
g = gen()
print("yield:", next(g))
print("yield:", g.send(10))
try:
next(g)
except StopIteration as e:
# 自己捕获异常,并获取return的值
print("yield:", e.value)
# yield: 1
# send: 10
# yield: 2
# return: 100
控制生成器启动和退出示例:
res = {}
def count(key):
total = 0
while True:
x = yield
# 计算的值由外部传入
if not x:
break
total += x
return total
def middle(key):
while True:
res[key] = yield from count(key)
def main():
data = {
"a": [1, 2, 3],
"b": [10, 22, 3],
"c": [1, 23, 3],
}
for k, v in data.items():
m = middle(k)
m.send(None)
# 启动生成器
for vv in v:
m.send(vv)
m.send(None)
# 结束生成器
print("result:", res)
main()
# result: {'a': 6, 'b': 35, 'c': 27}
yield from实现
根据yield from
原理,我们可以简单模拟一下其逻辑:
def gen():
v = yield 1
print("send:", v)
yield 2
return 100
def yf(iterator):
i = iter(iterator)
try:
y = next(i)
# 先第一次启动生成器,并将结果存储
except StopIteration as e:
# 如果接收到停止迭代的异常,则直接获取值
res = e.value
else:
while True:
# 生成器循环将值yield出去,并接收外部传入的值
send = yield y
try:
# 将外部传入的值再传入生成器内部,并获取生成器yield的值
y = i.send(send)
except StopIteration as e:
res = e.value
break
yield res
g = yf(gen())
print("yield:", next(g))
print("yield:", g.send(10))
print("yield:", next(g))
# yield: 1
# send: 10
# yield: 2
# yield: 100
生成器场景
计算无穷数据
例如要生成一堆奇数,但不确定要生成几个,假如现在要100个,可能会做一个100个的列表,但如果突然又要1000个,那么就不够用了,所以就可以弄一个生成器,每当需要一个奇数时就生成一个,不要了就先暂停在那,当又要了再从那里继续开始生成。这样不但能够能够满足需求,也减少了加载整个列表耗费的效率和内存,下面是一个无穷产生奇数的生成器:
def odd():
n=1
while True:
yield n #输出产生的数,并停在这
n+=2
odd_num = odd() #生成器对象
count = 0
for i in odd_num: #循环使用生成器
if count >=50:
break
print(i)
count +=1
读取大文件
在文件读取时,read
方法可以指定一次接收的字节数,然后指针指向该位置,之后继续从该位置开始读取内容,因此我们可以利用该方式,来读取大文件,举例:
def read_big_file(file):
with open(file, "r", encoding="utf-8") as f:
while True:
chunk = f.read(1024)
if not chunk:
yield chunk
break
yield chunk
f = read_big_file("./xxx.txt")
for content in f:
print(content)
生成器实现原理
这里需要先说明python中函数的工作原理:
首先python解释器会通过C当中的一个函数PyEval_EvalFramEx
去执行我们编写的函数,并创建栈帧对象,当函数里再调用函数时,会在此基础上再创建一个栈帧对象,而所有的栈帧都分配在堆内存上,这也决定了栈帧可以独立于调用者存在,举例:
import inspect
def a():
def b():
frame = inspect.currentframe()
# 当前栈帧对象
parent_frame = frame.f_back
# 开辟当前栈帧的栈帧对象
print(parent_frame.f_code.co_name)
print(frame.f_code.co_name)
b()
a()
# a
# b
在python底层将生成器对象保存在堆内存当中,而生成器对象里保存了栈帧对象gi_frame
和字节码对象gi_code
,栈帧对象当中记录了上一次执行到的地方f_lasti
,以及栈帧内的局部变量f_locals
,举例:
def gen():
x = 1
yield x
x += 1
y = 2
yield x
x += 2
yield x
g = gen()
next(g)
print(g.gi_frame.f_lasti, g.gi_frame.f_locals)
next(g)
print(g.gi_frame.f_lasti, g.gi_frame.f_locals)
next(g)
print(g.gi_frame.f_lasti, g.gi_frame.f_locals)
# 6 {'x': 1}
# 24 {'x': 2, 'y': 2}
# 38 {'x': 4, 'y': 2}
因此在每次yield的时候,都可以获取到记录最近一次代码执行到的位置,以及对应的局部变量,从而实现了对函数暂停和前进的基础,而不需要将函数内容一次全部执行完
获取生成器状态
import inspect
def gen():
yield 1
yield 2
yield 3
g = gen()
print(inspect.getgeneratorstate(g))
g.send(None)
print(inspect.getgeneratorstate(g))
g.send(None)
g.send(None)
try:
g.send(None)
except StopIteration:
pass
print(inspect.getgeneratorstate(g))
# GEN_CREATED
# GEN_SUSPENDED
# GEN_CLOSED
装饰器
在一般情况下,函数写好后就不要改了,如果因为要加新功能而修改原函数代码,可能会导致一些意外的错误产生,所以就有了装饰器。其作用就是在不修改原函数的情况下添加新功能,相当于高阶函数(将函数作为参数传入)和嵌套函数的结合,所以可以多个函数都使用一个装饰器,而且还能给函数传入参数。假如现在要给一个函数计算运行时间,如果要不修改源代码且不用装饰器的话可以这样写:
import time
def time_abc(func): #高阶函数,将函数作为参数传入
startTime = time.time()
func() #在函数执行前后计算时间
endTime = time.time()
print("The cost time is:", endTime-startTime)
def abc():
print("abc")
time_abc(abc)
上面的方式相当于装饰器的原型,但是要多个函数就得每次都调用time_abc
函数来执行,会影响效率,而且还不能够给func()传参(这里形参只有一个func),使用方式有限,不过可以看出装饰器基本格式为:
def 装饰器函数(传入的函数):
def 执行的嵌套函数(传入函数的参数):
装饰器语句
...
传入的函数(传入函数的参数)
...
装饰器语句
return 返回的嵌套函数
通过该格式可以更清楚的发现:装饰器就是把原来的函数包装在一些其他的功能语句里,然后组成的一个新函数,根据该格式写出最基本的装饰器:
def time_wrapper(func): #接收函数参数
def wrapper(*args, **kwargs):
#嵌套函数,从而可以接收参数,使用*和**传参是为了接收不确定多的参数,也可以根据情况修改
startTime = time.time()
func(*args, **kwargs) #此时函数有参数也能够传进来了
endTime = time.time()
print("The function %s cost time is:" % func.__name__, endTime - startTime)
return wrapper #注意这个是写在最外层的,不是嵌套函数的返回值
@time_wrapper #需要装饰器的函数加上这个
def abc(a, b): #此时abc传入time_wrapper,a和b传入wrapper
print("abc")
@time_wrapper
def xyz(x, y, z):
print(x, y, z)
abc(1, 2) #无需再调用别的函数,直接执行该函数就会执行装饰器
xyz(3, 4, 5)
结果为:
abc
The function abc cost time is: 0.0
3 4 5
The function xyz cost time is: 0.0
由上可见装饰器相当于一个附属执行函数,而原来那种方式是通过在别的函数当中调用该函数,效率上有一定差别。而且装饰器可以装饰多个函数,而一个函数也可以同时有多个装饰器,假如第一行@装饰器1,第二行@装饰器2,则此时其之间的执行关系是:装饰器1(装饰器2(函数)),所以相当于递归执行,比如下面这个:
import time
def time_wrapper(func):
def wrapper(*args, **kwargs):
startTime = time.time()
print("1The start time is:", startTime) #1:这里先执行
func(*args, **kwargs) #2:这里执行会进入装饰器2
endTime = time.time()
print("1The cost time is:", endTime - startTime) #7:装饰器2执行结束到这里
return wrapper
def time_wrapper2(func):
def wrapper(*args, **kwargs):
startTime = time.time()
print("2The start time is:", startTime) #3:这里执行
func(*args, **kwargs) #4:这里执行进入函数
endTime = time.time()
print("2The cost time is:", endTime - startTime) #6:函数执行结束出来到这里
return wrapper
@time_wrapper #0:先执行其,再执行下面那个
@time_wrapper2
def abc(a, b):
print("abc") #5:这里执行
abc(1, 2)
结果为:
1The start time is: 1530674912.77675
2The start time is: 1530674912.77675
abc
2The cost time is: 0.0
1The cost time is: 0.0
有返回的函数+装饰器
前面的示例函数都是没有返回值的,当函数有返回值时,装饰器的格式需要做一点小小的修改,先拿一个有返回值的函数+原来的装饰器举个例子:
def abc(func):
def aaa(*a, **aa):
print(0)
func(*a, *aa)
#原本函数返回的值只能到这,如果要返回给x,需要在这里再用return返回
return aaa
@abc
def ccc(x):
print(x)
return x
#函数返回x
x = ccc(1)
print(x)
#输出为None,即没有返回值
结果为:
0
1
None
可以看出执行ccc函数时最终应该返回个1给x,但是并没有,为什么呢,因为ccc被装饰器包装成一个新函数后,原来的函数内容在新函数里面执行(也就是func(*a, *aa)
),因此原来的返回值也只返回到了外面一层的函数,却没有返回到其外面两层的变量x上,所以为了能够返回给x,需要将返回的结果再返回一次,即把func(*a, *aa)
改成return func(*a, *aa)
就行了,或者中途将函数的返回值赋值给一个变量,后面再返回这个变量即可,举例:
def abc(func):
def aaa(*a, **aa):
print(0)
x = func(*a, *aa)
print(1)
return x
# 返回前面函数的返回值
return aaa
带参数的装饰器
前面的装饰器都是不带参数的,如果想要带参数,那么我们需要做到就是将装饰器的参数和原本函数的参数分开,那么我们就可以在外面再写一个函数用于接收装饰器的参数,即将原来的装饰器嵌套在一个新的函数里,举例:
import time
def args_wrapper(**time_args): # 接收装饰器参数
def time_wrapper(func): # 原来的装饰器
def wrapper(*args, **kwargs):
print(time_args) # 可以看到接收到了装饰器的参数
startTime = time.time()
func(*args, **kwargs)
endTime = time.time()
print("The function %s cost time is:" % func.__name__, endTime - startTime)
return wrapper
return time_wrapper
@args_wrapper(a=1) # 给装饰器传入参数
def abc(a, b):
print("abc")
@args_wrapper()
def xyz(x, y, z):
print(x, y, z)
abc(1, 2)
xyz(3, 4, 5)
结果为:
{'a': 1}
abc
The function abc cost time is: 0.0
{}
3 4 5
The function xyz cost time is: 0.0
可以看出现在新加了一个装饰器args_wrapper
将原来的装饰器time_wrapper
包了起来,那么此时就完成了一个可以接收参数的装饰器
修饰类的装饰器
如果对一个类使用装饰器,那么将会接收到这个类,然后我们可以对其进行改写,举例:
def deco(cls):
def getattr(self, name):
return "new_attr:" + name
cls.__getattr__ = getattr
# 改写类,当获取不存在的属性时,返回对应字符串
return cls
# 返回被改写后的类
@deco
class A:
pass
a = A()
print(a.x)
# new_attr:x
基于类编写装饰器
我们也可以定义一个装饰器类,主要则是用到了__init__
/__call__
的魔法方法,分别在初始化和调用时使用,举例:
class Deco:
def __init__(self, func):
self.func = func
def deco_func(self, *arg, **args):
"""定义的新方法,包装了原方法"""
self.func(*arg, **args)
print("end")
def __call__(self, *arg, **args):
return self.deco_func(*arg, **args)
@Deco
def test(x):
print(x)
test(1)
# 1
# end
带参数的类装饰器
如果希望类装饰器带参数,那么和带参数的函数装饰器同理,第一层__init__
方法里接收的就不在是函数,而是传递的参数,然后在调用__call__
方法时才接收函数,举例:
class Deco:
def __init__(self, x):
# 接收的是参数
self.x = x
def __call__(self, func):
# 在此处才接收到对应函数
def wrapper(*arg, **args):
print(self.x)
func(*arg, **args)
return wrapper
@Deco(x=1)
def test(x):
print(x)
test(2)
# 1
# 2
装饰器的执行机制
装饰器是在加载模块时就会执行,因此只会执行一次,举例:
def deco(func):
print(func)
def wrapper(*arg, **args):
func(*arg, **args)
return wrapper
@deco
def test(x):
print(x)
@deco
def test2(x):
print(x)
test(1)
test(2)
# <function test at 0x00000264B7DB8598>
# <function test2 at 0x00000264B7DB86A8>
# 1
# 2
装饰器原理
常规装饰器
装饰器语法@
只是提供了一个高阶函数的语法糖,例如下面这个:
@wrapper
def test(x): pass
实际上就等价于:
def test(x): pass
test = wrapper(test)
嵌套装饰器
对于多层嵌套的装饰器,等价关系如下:
@wrapper1
@wrapper2
def test(x):
print("test:", x)
# test = wrapper1(wrapper2(test))
# 这两个等价
举例:
from dis import dis
def wrapper1(func):
def new_func(*args, **kwargs):
print("start1...")
func(*args, **kwargs)
print("end1...")
return new_func
def wrapper2(func):
def new_func(*args, **kwargs):
print("start2...")
func(*args, **kwargs)
print("end2...")
return new_func
@wrapper1
@wrapper2
def test(x):
print("test:", x)
# test = wrapper1(wrapper2(test))
# 这两个等价
print(dis(test))
用两种方法也可以看出他们的操作码是一样的,因此也可以证明两种写法是等价的
有参数的装饰器
等价关系如下:
def wrapper(arg):
print("wrapper arg:", arg)
def aaa(func):
def new_func(*args, **kwargs):
print("start1...")
func(*args, **kwargs)
print("end1...")
return new_func
return aaa
# @wrapper("aaa")
def test(x):
print("test:", x)
# 这两个等价
test = wrapper("aaa")(test)
test(1)
其他
装饰器导致的元信息丢失问题
由于装饰器会使用一层函数将原函数包装,因此会导致返回函数的元信息是外面一层函数的,而非原本函数的。举例:
def wrapper(func):
def wrap(*args, **kwargs):
return func
return wrap
@wrapper
def test1():
pass
@wrapper
def test2():
pass
print(test1.__name__)
print(test2.__name__)
# wrap
# wrap
可以看出原有的函数名都被修改成了装饰器返回的函数名。为了能够保持函数的元信息和之前的相同,我们可以使用functools
下的wraps
装饰器用于设置函数的元信息,举例:
import functools
def wrapper(func):
# 传入之前的函数,保存之前函数的元信息
@functools.wraps(func)
def wrap(*args, **kwargs):
return func
return wrap
@wrapper
def test1():
pass
@wrapper
def test2():
pass
print(test1.__name__)
print(test2.__name__)
# test1
# test2