Python 迭代器/生成器/装饰器

迭代器

  • 迭代器是一种能够遍历访问集合内元素的数据类型
  • 普通的集合能够通过[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
...
  • sendnext区别:
    next可以理解成send(None),因此send可以说包含nextnext能做的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'>,)

可以看到GeneratorExitException都是继承自BaseExceptionBaseException才是所有异常的基类

生成器相关语法
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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。