一文读懂python迭代器与生成器の拾遗

Iterators and Generators

在这里插入图片描述

1、迭代器 Iterators

何谓可迭代对象,何谓迭代器,它们之间关系如何?先谈谈这几个概念

  • 可迭代对象(Iterables)
    • 可迭代对象可以是任何对象,不一定是可以返回迭代器的数据结构,其主要目的是返回其所有元素
    • 可迭代可表示有限和无限的数据源
    • 一个可迭代的对象将直接或间接定义两个方法:
      • __iter __()方法,该方法必须返回迭代器对象
      • 而 __next()__方法,则借助它调用的迭代器
  • 迭代器(Iterators)
    • python中的Iterator是一个对象,用于迭代列表,元组,字典和集合之类的可迭代对象
    • 使用 iter() 方法初始化 Iterator 对象。 它使用 next()方法进行迭代
    • __iter(iterable)__ 方法 用于初始化迭代器,返回一个迭代器对象
    • next ( __next__ in Python 3)方法, next() 返回可迭代对象的下一个值,当我们使用for循环遍历任何可迭代对象时,在内部它会使用 iter() 方法获取一个迭代器对象,该对象进一步使用 next() 方法进行迭代。 此方法会在迭代结束引发 StopIteration表示迭代结束

1.1 可迭代对象(Iterables)

我们已经知道,可以直接作用于 for循环 的数据类型有以下几种:

  • 一类是集合数据类型,如 list、tuple、dict、set、str 等

  • 一类是 generator,包括 生成器 和 带 yield 的 generator function

  • 这些可以直接作用于for循环的对象统称为可迭代对象:Iterable

  • 可以使用isinstance()判断一个对象是否是Iterable对象

如下,常见的数据类型列表、字典、元组、集合、字符串都是可迭代的对象,但整数 666 不可迭代

In [90]: from collections.abc import Iterable

In [91]: isinstance([],Iterable)
Out[91]: True

In [92]: isinstance({},Iterable)
Out[92]: True

In [93]: isinstance('eva',Iterable)
Out[93]: True

In [94]: isinstance((1,),Iterable)
Out[94]: True

In [95]: isinstance(666,Iterable)
Out[95]: False

In [96]: isinstance({3},Iterable)
Out[96]: True

In [97]: isinstance({'a':1},Iterable)
Out[97]: True

1.2 迭代器(Iterators)

  • 迭代器(节省内存)
  • 生成器都是 Iterator 对象,但 list、dict、str 虽然是 Iterable,却不是 Iterator
  • 把 list、dict、str 等 Iterable 变成 Iterator 可以使用 iter() 函数:

迭代器遵守迭代器协议,迭代器协议定义如下:

A Python iterator object must implement two special methods, __iter__() and __next__(), collectively called the iterator protocol.

Python迭代器对象必须实现__iter __()和__next __()这两个特殊方法,统称为迭代器协议。

通过 dir(obj) 或 obj.__dir__() 方法来查看 该对象的属性方法

  • 可迭代对象 只有 __iter__() 属性方法
In [106]: x = [1,2,3]

In [107]: y = iter(x)

In [108]: dir(x)                    # dir(obj) is same as obj.__dir__()
Out[108]:
['__add__',
 '__class__',
 '__contains__',
 '__delattr__',
 '__delitem__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getitem__',
 '__gt__',
 '__hash__',
 '__iadd__',
 '__imul__',
 '__init__',
 '__init_subclass__',
 '__iter__',
 '__le__',
 '__len__',
 '__lt__',
 '__mul__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__reversed__',
 '__rmul__',
 '__setattr__',
 '__setitem__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 'append',
 'clear',
 'copy',
 'count',
 'extend',
 'index',
 'insert',
 'pop',
 'remove',
 'reverse',
 'sort']

In [109]:
  • 迭代器 必须 同时 具有 __iter__() 和 __next__() 方法
In [109]: y.__dir__()         # dir(obj) is same as obj.__dir__()
Out[109]:
['__getattribute__',
 '__iter__',
 '__next__',
 '__length_hint__',
 '__reduce__',
 '__setstate__',
 '__doc__',
 '__repr__',
 '__hash__',
 '__str__',
 '__setattr__',
 '__delattr__',
 '__lt__',
 '__le__',
 '__eq__',
 '__ne__',
 '__gt__',
 '__ge__',
 '__init__',
 '__new__',
 '__reduce_ex__',
 '__subclasshook__',
 '__init_subclass__',
 '__format__',
 '__sizeof__',
 '__dir__',
 '__class__']

In [110]:

通过迭代器进行迭代
使用 next() 函数手动遍历迭代器的所有项目。当我们到达末尾并且没有更多数据要返回时,它将引发StopIteration Exception。示例如下:
注意:next(obj) is same as obj.__next__()

# define a list
my_list = [4, 7, 0, 3]

# get an iterator using iter()
my_iter = iter(my_list)

# iterate through it using next()

# Output: 4
print(next(my_iter))

# Output: 7
print(next(my_iter))

# next(obj) is same as obj.__next__()

# Output: 0
print(my_iter.__next__())

# Output: 3
print(my_iter.__next__())

# This will raise error, no items left
next(my_iter)

输出:

4
7
0
3
Traceback (most recent call last):
  File "<string>", line 24, in <module>
    next(my_iter)
StopIteration

再如下所示,x 是可迭代的,而 y 是迭代器单独的一个实例,y 可以迭代 x 产生值,在迭代结束引发 StopIteration表示迭代结束

In [98]: x = [1, 2, 3]

In [99]: y = iter(x)

In [100]: next(y)
Out[100]: 1

In [101]: next(y)
Out[101]: 2

In [102]: next(y)
Out[102]: 3

In [103]: next(y)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-103-81b9d2f0f16a> in <module>
----> 1 next(y)

StopIteration:

In [104]: type(x)
Out[104]: list

In [105]: type(y)
Out[105]: list_iterator

In [106]:   

coding 实际中,列表写 for 列表 遍历,一般如下:

x = [1, 2, 3]
for elem in x:
    ...

其实际过程是这样的

# create an iterator object from that iterable
iter_obj = iter(iterable)

# infinite loop
while True:
    try:
        # get the next item
        element = next(iter_obj)
        # do something with element
    except StopIteration:
        # if StopIteration is raised, break from loop
        break

在这里插入图片描述

  • list 这个可迭代的类型 通过 iter() -转换–> 迭代器
  • 其中 for 循环语法 替我们 完成了 __iter__() 这个转化过程
  • 通过迭代器的 next() 方法 来完成 for 循环中取值这个步骤

1.3 迭代器作用于for循环 Working of for loop for Iterators

iterable_value = 'iterables'                  # 可迭代的字符串
iterable_obj = iter(iterable_value)           # 初始化迭代器

while True:
    try:
        # Iterate by calling next
        item = next(iterable_obj)
        print(item)
    except StopIteration:
        # exception will happen when iteration will over
        break

输出:

i
t
e
r
a
b
l
e
s

1.4 构建自定义迭代器 Building Custom Iterators

python中从头开始构建迭代器还是很容易的,只需要实现__iter __() 和__next __() 方法即可。 __iter __() 方法返回迭代器对象本身,如果需要的话,也可以执行一些初始化操作。 __next __() 方法必须返回序列中的下一项,在到达末尾以及随后的调用中,它必须引发StopIteration

示例如下:
将在每次迭代中提供下一个2的幂,幂指数从零开始一直到用户设置的数字
注意:next(obj) is same as obj.__next__()

class PowTwo:
    """Class to implement an iterator
    of powers of two"""

    def __init__(self, max=0):
        self.max = max

    def __iter__(self):
        self.n = 0
        return self

    def __next__(self):
        if self.n <= self.max:
            result = 2 ** self.n
            self.n += 1
            return result
        else:
            raise StopIteration


# create an object
numbers = PowTwo(3)

# create an iterable from the object
i = iter(numbers)

# Using next to get to the next iterator element
print(next(i))
print(next(i))
print(i.__next__())
print(next(i))
print(i.__next__())

输出:

1
2
4
8
Traceback (most recent call last):
  File "/home/xxx/Desktop/Untitled-1.py", line 32, in <module>
    print(next(i))
  File "<string>", line 18, in __next__
    raise StopIteration
StopIteration

同样可以使用for循环来迭代 这个迭代器类


>>> for i in PowTwo(5):
...     print(i)
...     
1
2
4
8
16
32

1.5 Python无限迭代器 Python Infinite Iterators

不必耗尽迭代器对象中的项目,可以有无限迭代器(永无止境)。处理此类迭代器时,我们必须小心。下边演示一个无限迭代器的简单示例:
可以使用两个参数调用内置函数iter()函数,其中第一个参数必须是可调用对象(函数),第二个参数是前哨,迭代器将调用此函数,直到返回的值等于哨兵。

It is not necessary that the item in an iterator object has to be exhausted. There can be infinite iterators (which never ends). We must be careful when handling such iterators.
Here is a simple example to demonstrate infinite iterators.

The built-in function iter() function can be called with two arguments where the first argument must be a callable object (function) and second is the sentinel. The iterator calls this function until the returned value is equal to the sentinel.

>>> int()
0
>>> int()
0
>>> int()
0

>>> inf = iter(int,1)
>>> next(inf)
0
>>> next(inf)
0

我们可以看到 int() 函数始终返回0。因此,将其作为 iter(int,1)传递将返回一个迭代器,该迭代器调用 int() 直到返回值等于1。然鹅这永远不会发生,并且我们得到一个无限迭代器。
We can see that the int() function always returns 0. So passing it as iter(int,1) will return an iterator that calls int() until the returned value equals 1. This never happens and we get an infinite iterator.

我们还可以构建自己的无限迭代器。 理论上,以下迭代器将返回所有奇数。
We can also build our own infinite iterators. The following iterator will, theoretically, return all the odd numbers.

class InfIter:
    """Infinite iterator to return all
        odd numbers"""

    def __iter__(self):
        self.num = 1
        return self

    def __next__(self):
        num = self.num
        self.num += 2
        return num

运行结果举例如下:

>>> a = iter(InfIter())
>>> next(a)
1
>>> next(a)
3
>>> next(a)
5
>>> next(a)
7

依此类推…
And so on…

在迭代这些类型的无限迭代器时,请小心包含终止条件。
Be careful to include a terminating condition, when iterating over these types of infinite iterators.

使用迭代器的优点是节省了资源。 如上所示,我们无需将整个数字系统存储在内存中就可以获得所有奇数。 从理论上讲,我们可以在有限内存中包含无限项。
The advantage of using iterators is that they save resources. Like shown above, we could get all the odd numbers without storing the entire number system in memory. We can have infinite items (theoretically) in finite memory.

2、生成器 Generators

生成器是一种优雅的迭代器实现,并且自动包含了__iter__()和__next__()方法,可对其直接进行迭代元素。换句话说,生成器本质上是一个迭代器,具有惰性运算的特性,不取值不运算,且只能一次性取值
在这里插入图片描述

2.1 生成器定义

Python中创建一个generator,有很多种方法,常用的两种生成器类型分别是:生成器函数和生成器表达式。

  • 生成器函数,任何函数中包含有关键字 yield 函数,称该函数为生成器函数
  • 生成器表达式,类似于列表推导等效的生成器,一种有限元素用例生成器的优雅语法

2.2 生成器的取值

  • 逐个取值
In [1]: g = (x * x for x in range(6))

In [2]: g.__next__()
Out[2]: 0

In [3]: next(g)
Out[3]: 1

In [4]: g.__next__()
Out[4]: 4

In [5]: next(g)
Out[5]: 9

In [6]:
  • 一次性全部取值
In [6]: g = (x * x for x in range(6))

In [7]: list(g)
Out[7]: [0, 1, 4, 9, 16, 25]

In [8]:

2.3 生成器的执行过程分析

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。比如我要循环100万次,按python2的语法,for i in range(1000000)会先生成100万个值的列表。但是循环到第50次时,我就不想继续了,就退出了。但是90多万的列表元素就白为你提前生成了。

for i in range(1000000):
    if i == 50: 
        break
    print(i)

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?

像上面这个循环,每次循环只是+1而已,我们完全可以写一个算法,让他执行一次就自动+1,这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算后面元素的机制,称为生成器:generator。

  • 生成器是一个函数,返回一个对象(迭代器),可以对其进行迭代(一次一个值)
  • 生成器函数包含一个或多个yield语句
  • 调用时,它返回一个对象(迭代器),但不会立即开始执行
  • 一边循环一边计算后面元素的机制
  • __iter __() 和__next __() 等方法被自动创建,可以直接使用 next() 方法遍历所有元素
  • 遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行
  • 最后,当函数终止时,在下一步的调用中,将会自动引发StopIteration

2.4 生成器表达式

生成器表达式的标准方式是以圆括号的形式,括号内可以是一个列表推导式。

generator_expression ::= "(" expression comp_for ")"

生成器表达式生语法和列表推导式相同,列表推导式是以大括号 ‘[ ]’ 的形式存在。列表推导式是直接创建一个列表,但是由于受到内存的限制,列表的容量有上限,而生成器则不需要一下子创建完整的列表,而是一边循环一边计算。

生成器表达式生成了一个生成器对象,但其中的变量是以延迟方式获取,直到调用生成器的__next__()方法
注意:next(obj) is same as obj.__next__()

In [33]: ge = (2 * i for i in range(3))

In [34]: ge             # ge  为生成器
Out[34]: <generator object <genexpr> at 0x0000024ACC501150>

In [35]: print(next(ge))
0

In [36]: print(ge.__next__())
2

In [37]: print(ge.__next__())
4

In [38]: print(next(ge))
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-38-e8f75000d438> in <module>
----> 1 print(next(ge))

StopIteration:

In [39]:

直到生成器中没有其他元素时,抛出StopIteration异常

for循环可以迭代获取生成器对象中的每个元素,并且不会产生StopIteration异常

    for g in ge:
        print(g)

for循环其实隐含调用了生成器对象的__next__()方法。

2.5 生成器函数(yield表达式)

2.5.1 yield 语句暂停执行

yield_atom       ::=  "(" yield_expression ")"
yield_expression ::=  "yield" [expression_list | "from" expression]

yield是定义生成器的另一种方式,包含yield语句的函数就是一个生成器对象。

调用一个生成器函数,返回的是一个迭代器对象。迭代器Iterator表示的是一个数据流,迭代器可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。迭代器控制生成器函数的执行,当函数开始运行,执行到第一个yield语句时暂停,将yield表达式后的表达式的值返回给调用者。

def wine():
    print('first yield...')
    yield 1
    print('second yield...')
    yield 2

ww = wine()
print(next(ww))

**输出:**
first yield...
1

在生成器函数暂停时,其现阶段的状态都被保存下来,包括生成器函数局部变量当前绑定的值、指令指针、函数内部执行堆栈以及任何异常状态的处理。当生成器函数再次被调用时则直接从上次暂停的yield表达式处接着运行,直到遇到下一个yield语句,或者没有遇到yield语句则运行结束。

print(next(ww))

**输出:**
second yield...
2

需要说明的是,在函数重新运行时,其实上次暂停处的yield表达式会先接收一个值作为结果,然后才接着运行直到碰到下一个yield表达式。

如果调用者使用next函数或者__next__()方法,则默认返回给yield表达式None值

2.5.2 send() 方法 传参

使用send()方法则传递一个值作为yield表达式的结果

此时 仅输出 yield表达式返回的值

def wine():
    print('first yield...')
    x = yield 1
    print(x)
    print('second yield...')
    yield 2

ww = wine()
print(next(ww))

**输出:**
first yield...
1

使用send()方法继续调用:

print(ww.send('the result of first yield...'))

**输出:**
the result of first yield...
second yield...
2

可以看出,首先输出send()方法传回yield表达式的值,然后再继续执行后续的内容直到下一个yield表达式。

注意:在实例化生成器后如果直接调用send()方法启动函数,应该使用send(None)方法,因为此时没有yield表达式可以接收其返回的值。或者在实例化生成器后首先使用next()函数。

3、生成器总结

1、写一个生成器函数:包含yield
  • 一个yield代表在函数中的一次返回和暂定
  • 每执行一个next方法就推动函数从一个yield指定到下一个yield
  • 生成器yield语法还支持向生成器中传递参数,.send()方法完成的这个过程
2、写一个生成器表达式:
  • 以小括号包含的一个推导式
3、一些概念和关系
  • 无论是调用生成器函数还是引用生成器表达式,都不是具体取值的过程
  • 生成器的本质就是迭代器(所有迭代器具有的功能、特点生成器都有)
4、什么时候用:假设某一个场景要返回一个列表,但是这个列表是需要加工处理的,那么加工之前的过程就没必要是一个列表
  • 从数据库中要获取的数量非常大,并且要进行整理(1w+条数据以上就可以注意一下),就可以使用生成器
  • 处理文件的时候

4、interview questions

1)读代码,写出输出内容,并解释为什么

def demo():
    for i in range(4):
        yield i

    g=demo()              # 生成器

    g1=(i for i in g)     # 生成器
    g2=(i for i in g1)    # 生成器

    print(list(g1))       # [0, 1, 2, 3],列表类型转化,g -->g1 生成器转为列表
    print(list(g2))       # [],g1 与 g2生成器本质都是从生成器 g 而来,所以 g1 取完值后,g2 则拿不到值了

输出:

[0, 1, 2, 3]
[]

2)读代码,写出输出内容,并解释为什么

def add(n,i):
    return n+i

def test():
    for i in range(5):
        yield i

g=test()
for n in [1, 20, 30]:
    g=(add(n,i) for i in g)

print(list(g))

输出:

[90, 91, 92, 93, 94]

详解如下,带for循环的生成器,一般直接带值进去计算,即数值代入法~

# 思路如下, 采用代入法即可
# n = 1
# g = (add(n, i) for i in test())
#
# n = 20
# g = (add(n, i) for i in g)
# g = (add(n, i) for i in (add(n, i) for i in test()))
#
# n = 30
# g = (add(n, i) for i in (add(n, i) for i in (add(n, i) for i in test())))
# g = (add(30, i) for i in (add(30, i) for i in (add(30, i) for i in test())))
# g = (add(30, i) for i in (add(30, i) for i in (30, 31, 32, 33, 34)))
# g = (add(30, i) for i in (60, 61, 62, 63, 64))
print([90, 91, 92, 93, 94])
print(list(g))

输出:

[90, 91, 92, 93, 94]
[90, 91, 92, 93, 94]

猜你喜欢

转载自blog.csdn.net/Sunny_Future/article/details/108162156