Python迭代器、生成器、装饰器的使用

  本文介绍python中常常会用到的迭代器,生成器和装饰器的使用与解读。

1、迭代器

  首先先要知道什么是可迭代对象(Iterable),在python中常见的可迭代对象是字典,字符串,数组,元组等,这些都是可以迭代的对象。如何创建自己的可以迭代对象呢?只要在你定义的类中包含 __iter__ 方法,那你所创建的对象就是可迭代对象(Iterable)。更进一步的,如果你的定义的类中除了 __iter__ 方法还包含有 __next__ 方法,那么你创建的这个类就是一个迭代器(Iterator)。我们可以使用以下代码来确定一个实例是不是可迭代对象迭代器

from collections.abc import Iterable
from collections.abc import Iterator
a = [1,2,3]

print('是否为可迭代对象', isinstance(a,Iterable))
print("是否为迭代器", isinstance(a,Iterator))

返回结果:

   我们可以通过dir(a),来查看列表a所具有那些方法,可以很容易的发现其只有__iter__方法而并没有__next__方法,因此一个列表只是一个可迭代对象,而不是一个迭代器。

  接下来我们需要了解到到底__iter__和__next__需要完成什么工作呢?

  • __iter__(): 需要返回一个对象,这个对象必须具有__next__方法(如果类自己本身就是具有__next__那就直接返回self就可以了)
  • __next__(): 在被调用next()的时候进行处理的操作,需要注意一般这个方法里包含了何时停止即返回StopIteration异常

   看下面的代码就理解这两个方法的作用了。

from collections.abc import Iterable
from collections.abc import Iterator

class MyIterNext(object):
    def __next__(self):
        return "我被调用next了"

class MyIter(object):
    def __init__(self, end):
        self.start = 0
        self.end = end

    def __iter__(self):
        return MyIterNext()

a = MyIter(10)
print('a是否为可迭代对象:',isinstance(a, Iterable))
print('a是否为迭代器:',isinstance(a, Iterator))

b = iter(a)
print('b属于哪个类:', type(b))

  以上代码中执行结果如下:

   这里需要介绍的是代码第20行,当我在使用iter(a)的时候,其实是在执行a.__iter__(),此时会返回一个具有__next__方法的实例,这里用b来接收这个实例,可以从最后一行的打印信息中可以看到这里的b就是我自己定义的只有__next__方法的MyIterNext类的实例。此时如果执行next(b),即等于在执行b.__next__(),我们接着看,

print(next(b))
print(next(b))
print(next(b))

  返回结果:

   这就是__iter__和__next__方法的用法了,有些人可能会问?这有什么用呢?我们来看最后一个例子,该例子来自于参考网站2

from collections.abc import Iterable
from collections.abc import Iterator

class MyRange(object):
    def __init__(self, end):
        self.start = 0
        self.end = end

    def __iter__(self):
        return self

    def __next__(self):
        if self.start < self.end:
            ret = self.start
            self.start += 1
            return ret
        else:
            raise StopIteration

a = MyRange(5)
print(id(a))
print(isinstance(a, Iterable))
print(isinstance(a, Iterator))

b = iter(a)
print(id(b))

输出结果:

  由于我定义的MyRange类已经自带__next__和__iter__方法,因此a是既是一个可迭代对象又是一个迭代器,并且__iter__方法返回的就是自己本身,我也通过id()打印a和b的内存地址可以看到a,b是同一个内存的。接下来我们使用next()会发生什么呢?

print(next(b))
print(next(b))
print(next(b))

print("调用next之后,a的属性值为:",a.start)

输出结果:

   当我们每次对b调用next,其实就是在执行a.__next__()方法,在不断的更新a的属性值,这就是迭代器的用处。

  其实,我们平时使用的 for i in A: ....,其内在都是先执行a = iter(A),再将next(a)得到的返回值赋值给i。此时就要注意了,如果一个对象并没有实现__iter__方法, 也就是说该对象并不是可迭代对象(Iterable),那么如果对该对象进行for循环的操作,就会爆出TypeError: 'xxx' object is not iterable的异常信息(需要注意的是如果该对象没有实现__iter__方法但实现了__getitem__,那么是可以被for循环遍历的,不过此时就是从0下标开始遍历,直到出现IndexError为止,不过这是旧的迭代协议,现在已经不推荐使用了)。还需要注意的是,for循环执行完iter(A)之后会对返回类型进行判断,我们知道__iter__是需要返回一个具有__next__方法的对象(注意并不要求返回的对象是Iterator),如果iter(A)返回的对象并不具有__next__方法,也会报TypeError: iter() returned non-iterator of type 'xxx'的错误。

  根据分析,那我们是不是可以根据上面的例子,通过for来执行下我们自定义的MyRange呢?看代码。

class MyRange(object):
    def __init__(self, end):
        self.start = 0
        self.end = end

    def __iter__(self):
        return self

    def __next__(self):
        if self.start < self.end:
            ret = self.start
            self.start += 1
            return ret
        else:
            raise StopIteration

A = MyRange(5)
for i in A:
    print(i)

  输出结果:

   可以看到其实和使用next(b)没有差别吧。

  接下来继续讲解为什么需要在__next__()方法中加入StopIteration异常呢?其实这个异常是加给for循环看的,当我们需要停止迭代的时候就需要返回StopIteration异常,此时for循环接收到该异常之后就会停止循环了(因此本质上for会不断的调用我们的next(),无穷无尽),我们看一看如果我们把这个异常删掉再来for一下这个A会发生什么。

import time

class MyRange(object):
    def __init__(self, end):
        self.start = 0
        self.end = end

    def __iter__(self):
        return self

    def __next__(self):
        if self.start < self.end:
            ret = self.start
            self.start += 1
            return ret
        else:
            return "我还能继续"

A = MyRange(5)
for i in A:
    print(i)
    time.sleep(1)

返回结果

   我这里加入time.sleep是为了不让for循环得太快了。以上截图只是一部分,如果你不手动停止他将会一致打印下去。

  现在我们已经完整了解了迭代器的工作原理了,接下来说下为什么会出现迭代器这样看起来比较复杂的东西呢?其实从MyRange的例子可以看出如果我要打印【0,1,2,3】,其实可以不用先把这些内容储存起来而是当你需要使用的时候再来制造,那就要求你的这些数据之间是存在关系的,这样用的时候根据现有的"线索"就能制造下一个“数据”,这样就不用占用太大的内存了。

2、生成器

  生成器(generator)其实和迭代器的作用差不多,而且生成器就是迭代器(具有__iter__和__next__方法),最常见的生成器就是使用(x for x in rang(10)) 这种方式生成的,看代码。

from collections.abc import Iterable
from collections.abc import Iterator

a = (x for x in range(100))
print(type(a))
print(isinstance(a,Iterable))
print(isinstance(a,Iterator))

  执行结果:

   可以看到我们这里通过(x for x in rang(10)) 得到的a,就是一个生成器,并且它还是可迭代对象和迭代器,意味着我们可以使用next()对其进行操作。根据迭代器含义,我们知道得到a的时候其实并没有把所有的0到10的数储存在内存中,而是当我们调用next(a)的时候才会一个接一个生成出来(这就是迭代器的思想)。

  根据上面的介绍,当我们需要按照某种规则来产生数据的时候,并不需要一下子就能产生所有的数据(或者说一下子没法产生这么多数据)时,我们就可以采用创建一个生成器的方式。那我们如何创建一个生成器呢?其实只要当你的定义的函数中有yield保留字的时候,就创建了一个生成器了。关于yield其实我的这篇博客也有着介绍:Python中yield的使用方法

  下面看一个例子:

from collections.abc import Iterable
from collections.abc import Iterator

def myfun(number):
    while number:
        yield number
        number -= 1
    
a = myfun(10)
print(type(a))
print(isinstance(a,Iterable))
print(isinstance(a,Iterator))

  返回结果:

   介绍下代码第9行,当我们函数中含有yield保留字那么该在使用时不会立刻执行内部程序,而是得到一个generator对象(比如这里的a就是一个generator对象),当我们调用next(a)时就会才会开始执行myfun内部语句,并且执行到yield语句的地方停止,返会yield跟随的值(比如这里就是返回number),当下一次调用next(a),才会接着上一次的yield的地方开始执行。例如我们执行多次next(a),并打印

print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a))

  输出结果:

   通过这种方式就可以得到一个复杂的生成器,并不会一下子完全执行函数,而是当你调用next()的时候才会执行。(注意如果你多次执行next(),当所有的yield执行完成,将会返回StopIteration异常,比如我们这个例子中如果我执行11次next(a),由于函数内部已经跳出循环,无yield,此时将会触发异常)

3、装饰器

   我之前的一篇博客其实也稍微讲了一下装饰器的作用:Python@函数装饰器以及super()父类继承,我就接着它后面部分讲一下。首先我们知道了一个函数如果不加(),那么该函数不会被执行,并被视为一个function对象,可以被传递,并用另一个变量接收(如果不理解可以参看参考网站的前3点)。只要我们时刻记住一个函数如果不加()就是一个对象,当这个function对象后面跟着()时,意味着执行了该函数。这样就能理解什么叫做装饰器了。

  直接上例子

def decorator(a_func):
    print("我准备开始执行传进decorator的函数了")
    a_func()
    print("我已经执行完了传进decorator的函数了")
    return "装饰函数执行完毕"

@decorator
def myfunction():
    print("myfunction已经被执行")

  如果你直接运行这个例子你会发现,会直接输出:

  并且需要注意的是,此时myfunction这个引用指向的是“装饰函数执行完毕”这个字符串了,我们可以使用print(myfucntion)打印一下结果。

  很奇怪为什么我仅仅在定义函数,怎么就直接被输出了呢?其实是当程序执行到@decorator的时候,就开始启动decorator函数,并且把myfunction函数作为参数(前面已经提到了函数不加()可以作为参数进行传递的)传入到decorator函数。其实上面的代码就是等价于下面的代码:

def decorator(a_func):
    print("我准备开始执行传进decorator的函数了")
    a_func()
    print("我已经执行完了传进decorator的函数了")
    return "装饰函数执行完毕"


def myfunction():
    print("myfunction已经被执行")

myfunction = decorator(myfunction)

  那有没有办法不让这个事情发生呢?我自己来决定是否启动decorator函数呢?其实是有的,可以改成如下代码。

def decorator(a_func):
    def fun1():
        print("我准备开始执行传进decorator的函数了")
        a_func()
        print("我已经执行完了传进decorator的函数了")
    return fun1

@decorator
def myfunction():
    print("myfunction已经被执行")

  其实当程序执行到第8行@decoraror的时候依然会马上启动decorator函数,不过这个时候decorator函数是返回了一个函数对象fun1(注意到此处的fun1并没有加()因此这意味着fun1并没有执行,因此执行上面程序将什么都打印不出来)。并且此时我们myfunction的引用指向的就已经是fun1了

a = myfunction
print(type(a))
print(a.__name__)

  大家猜猜此时的这个a是哪个函数?myfunction?decorator?

   此时a是函数fun1,没想到吧,其实在只要myfunction被装饰了,就意味着,只要是使用myfunction其实就是已经是执行过decorartor(myfunction)之后的结果了返回的是fun1这个函数,并使用myfunction这个引用进行接收。不信的话可以执行a(),此时就会执行fun1的内容,打印东西了。装饰器的后续理解请参看这篇文章Python装饰器深入理解

 

 

参考网站:

Python 迭代器(Iterator)_X工作室-CSDN博客_python迭代器

【Python魔术方法】迭代器(__iter__和__next__) - 简书 (jianshu.com)

 Python 函数装饰器 | 菜鸟教程 (runoob.com)

posted @ 2021-12-28 19:07  Circle_Wang  阅读(238)  评论(0编辑  收藏  举报