Python-创建迭代器

  • 2018年8月18日 21:15
  • teddy
  • 663阅读
  • 0评论

原文:How to make an iterator in Python

翻译时间:2018-08-18 14:45:14 - 2018-08-18 16:08:35

本文会讨论为何你会需要创建自己的迭代器及演示如何实现自己的迭代器。

什么是迭代器

首先,简单的强调下什么是迭代器,详细的解释可以参考作者的 视频说明Loop Better talk或者阅读the article base on the talk 什么叫可迭代:任何可以循环的对象。

什么是迭代器:就是实际进行迭代的对象。

你可以通过向可迭代对象调用内置的iter函数来获取一个迭代器。

>>> favorite_numbers = [6, 57, 4, 7, 68, 95]
>>> iter(favorite_numbers)
<list_iterator object at 0x7fe8e5623160>

可以对迭代器调用next函数来获取下一个对象(如果碰到StopIteration则表示 没有下一个对象了,就会触发异常)。

>>> favorite_numbers = [6, 57, 4, 7, 68, 95]
>>> my_iterator = iter(favorite_numbers)
>>> next(my_iterator)
6
>>> next(my_iterator)
57

关于迭代器有个有趣规则:迭代器也可以被迭代,即对自己进行迭代。

为什么创建迭代器

迭代器让你能够创建一个可迭代对象,然后根据需要计算迭代对象条目的值。 这意味着,可迭代是懒惰的,除非你要求获取下个条目,否则就不会主动提供。

使用迭代器而非列表、集合或者其他可迭代的数据结构有时候能够节省内存。如下:

# 通过repeat获取一百万次4
>>> from itertools import repeat
>>> lots_of_fours = repeat(4, times=100_000_000)
# 先生成列表,每次从列表获取4
>>> lots_of_fours = [4] * 100_000_000
# 比较占用的内存大小,单位为bytes
>>> import sys
>>> sys.getsizeof(lots_of_fours)
56
>>> sys.getsizeof(lots_of_fours)
800000064

迭代器有时还能够节省时间。此外,迭代器还有其他可迭代对象不具备的功能。 比如,由于迭代器的懒惰性,可以创建长度无限的可迭代对象。比如,

# itertools.count对象是个无限长的可迭代对象,实现方法就是迭代器。
>>> from itertools import count
>>> for n in count():
...     print(n)
...
0
1
2
(this goes on forever)
创建迭代器
面向对象的方式创建

以面向对象的方式重写itertools.count

class Count:

    """Iterator that counts upward forever."""

    def __init__(self, start=0):
        self.num = start

    def __iter__(self):
        return self

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

对一个对象调用iter函数,将会调用该对象的__iter__方法,调用next, 将会调用__next__方法,所以创建迭代对象就要实现这两个方法。因为迭代器 是对自身进行迭代,所以在__iter__方法中返回的是自己self

next方法会返回下一个条目或者在没有更多条目的时候触发StopIteration异常。

可以对自己创建的Count进行迭代,也可以使用for进行循环:

>>> c = Count()
>>> next(c)
0
>>> next(c)
1

>>> for n in Count():
...     print(n)
...
0
1
2
(this goes on forever)

创建迭代器对象的方式很酷,但是并不是常用的创建迭代器的方式,当我们需要 一个迭代器的时候,我们会创建一个生成器。

生成器:创建迭代器的一种简单方法

在Python中,有两种创建生成器的方式。给出一个数字列表,如何来创建生成器?

  • 生成器函数
>>> favorite_numbers = [6, 57, 4, 7, 68, 95]
>>> def square_all(numbers):
...     for n in numbers:
...         yield n**2
...
>>> squares = square_all(favorite_numbers)
  • 生成器表达式

>>> squares = (n**2 for n in favorite_numbers)

两种方式的效果一样,都是generator类型,都是提供数字列表中数字的平方的迭代器。

接下来会讨论用这两种方式创建生成器,在这之前,先介绍几个概念:

generator, 也称 generator object, 是一个生成器类型的迭代器。

generator function 是特殊的语法,通过创建函数的方式在调用时返回一个迭代对象。

generator expression, 是一个类似comprehension的语法,能够以内嵌的方式创建生成器对象。

生成器函数

与普通函数的区别是多了yield语句。通常,调用一个函数的是,函数的代码 都会被执行。但是如果一个函数有yield语句,就成了生成器函数,意味着 调用的时候将会返回一个生成器对象。该生成器对象能够被循环执行(next), 直到碰到yield语句:

>>> def gimme4_later_please():
...     print("Let me go get that number for you.")
...     yield 4
...
>>> get4 = gimme4_later_please()
>>> get4
<generator object gimme4_later_please at 0x7f78b2e7e2b0>
>>> num = next(get4)
Let me go get that number for you.
>>> num
4

下面看一个真实的例子,实现类似itertools.count功能:

def count(start=0):
    num = start
    while True:
        yield num
        num += 1

# 测试, 同样也可以使用for来循环
>>> c = count()
>>> next(c)
0
>>> next(c)
1
>>> for n in count():
...     print(n)
...
0
1
2
(this goes on forever)
生成器表达式

生成器表达式类似列表表达式,能够创建生成器对象。下面举例说明,

# 这是一个列表表达式,过滤空行和非空行的换行符
lines = [
    line.rstrip('\n')
    for line in poem_file
    if line != '\n'
]
# 对应的生成器表达式,返回生成器对象
lines = (
    line.rstrip('\n')
    for line in poem_file
    if line != '\n'
)
# 测试
>>> type(lines)
<class 'generator'>
>>> next(lines)
' This little bag I hope will prove'
>>> next(lines)
'To be not vainly made--'

生成器表达式比较短,但是相应的也不如生成器函数功能强大。如果你的生成器 函数是如下格式,那就能以相应的生成器表达式实现,否则则不能互相转换。

# 生成器函数
def get_a_generator(some_iterable):
    for item in some_iterable:
        if some_condition(item):
            yield item

# 对应的生成器表达式
def get_a_generator(some_iterable):
    return (
        item
        for item in some_iterable
        if some_condition(item)
    )
生成器表达式还是生成器函数

生成器表达式类似列表表达式,只能实现相对简单的生成器,生成器函数能够实现复杂的生成器。

究竟什么才是创建迭代器的最佳方式,是生成器表达式、生成器函数还是生成器对象?

生成器表达式非常简洁,但是比如生成器函数灵活,生成器函数虽然灵活,但是如果你 需要为生成器对象增加额外的方法和属性,则最好是使用生成器对象。

我建议你就像使用列表表达式那样使用生成器表达式。如果你的操作只是简单的映射 或筛选,那么生成器表达式就是最好的选择。如果是复杂点的,则需要用生成器函数。

我建议使用生成器函数就像你使用for循环来append列表。在用到append的地 方,可以考虑使用yield语句来实现。

可以说,几乎很少会用到迭代类。在需要写迭代类的时候,可以先试着用迭代函数来 实现。

生成器是创建迭代器的方式

生成器可帮助创建可迭代对象。在Python中,生成器是创建迭代器的典型方法。

当你在想“在循环的时候创建一个根据需要计算值的可迭代对象会比较好”时,考虑使用迭代器。

当你在想如何创建自己的迭代器时,想想生成器函数和生成器表达式。

    0人参与|共 0 条评论
    还没有评论呢