zoukankan      html  css  js  c++  java
  • python死磕八之迭代器与生成器

      迭代是Python最强大的功能之一。初看起来,你可能会简单的认为迭代只不过是处理序列中元素的一种方法。 然而,绝非仅仅就是如此,还有很多你可能不知道的, 比如创建你自己的迭代器对象,在itertools模块中使用有用的迭代模式,构造生成器函数等等。

      一、for循环的内部原理?

      for循环迭代遍历对象首先是给对象添加了一个__iter__方法,使之每次可以调用next去访问下一个元素,直至遍历完遇到了StopIteration异常:

    >>> items = [1, 2, 3]
    >>> # Get the iterator
    >>> it = iter(items) # Invokes items.__iter__()
    >>> # Run the iterator
    >>> next(it) # Invokes it.__next__()
    1
    >>> next(it)
    2
    >>> next(it)
    3
    >>> next(it)
    Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
    StopIteration
    >>>

      二、你想反方向迭代一个序列

      之前思路:调用reverse方法,再for循环遍历

      遗漏点:当我们需要处理的对象有__reverse__方法才行,比如我们给定一个Countdown类,想让他实现reverse方法:

    class Countdown():
        def __init__(self,start):
            self.start = start
    
        def __iter__(self):
            n = self.start
            while n > 0:
                yield n
                n -= 1
    
        def __reversed__(self):
            n = 0
            while n <= self.start:
                yield n
                n += 1

      当定义了__reversed__方法后,我们可以调用reverse方法了:

    for i in Countdown(30):
        print(i)
    
    for i in reversed(Countdown(30)):
        print(i)

      三、你想得到一个由迭代器生成的切片对象,但是标准切片操作并不能做到。

      函数 itertools.islice() 正好适用于在迭代器和生成器上做切片操作。比如:

    >>> def count(n):
    ...     while True:
    ...         yield n
    ...         n += 1
    ...
    >>> c = count(0)
    >>> c[10:20]
    Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
    TypeError: 'generator' object is not subscriptable
    
    >>> # Now using islice()
    >>> import itertools
    >>> for x in itertools.islice(c, 10, 20):
    ...     print(x)

      这里要着重强调的一点是 islice() 会消耗掉传入的迭代器中的数据。 必须考虑到迭代器是不可逆的这个事实。 所以如果你需要之后再次访问这个迭代器的话,那你就得先将它里面的数据放入一个列表中。

      四、跳过可迭代对象的开始部分

      你想遍历一个可迭代对象,但是它开始的某些元素你并不感兴趣,想跳过它们。

      首先介绍的是 itertools.dropwhile() 函数。使用时,你给它传递一个函数对象和一个可迭代对象。 它会返回一个迭代器对象,丢弃原有序列中直到函数返回Flase之前的所有元素,然后返回后面所有元素。

      假定你在读取一个开始部分是几行注释的源文件。比如:

      

    >>> with open('/etc/passwd') as f:
    ... for line in f:
    ...     print(line, end='')
    ...
    ##
    # User Database
    #
    # Note that this file is consulted directly only when the system is running
    # in single-user mode. At other times, this information is provided by
    # Open Directory.
    ...
    ##
    nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false
    root:*:0:0:System Administrator:/var/root:/bin/sh
    ...
    >>>

      如果你想跳过开始部分的注释行的话,可以这样做:

    >>> from itertools import dropwhile
    >>> with open('/etc/passwd') as f:
    ...     for line in dropwhile(lambda line: line.startswith('#'), f):
    ...         print(line, end='')

      这个例子是基于根据某个测试函数跳过开始的元素。 如果你已经明确知道了要跳过的元素的个数的话,那么可以使用 itertools.islice() 来代替。比如:

    >>> from itertools import islice
    >>> items = ['a', 'b', 'c', 1, 4, 10, 15]
    >>> for x in islice(items, 3, None):
    ...     print(x)
    ...
    1
    4
    10
    15
    >>>

      最后需要着重强调的一点是,以上方案适用于所有可迭代对象,包括那些事先不能确定大小的, 比如生成器,文件及其类似的对象。

      五、你想迭代遍历一个集合中元素的所有可能的排列或组合

      1. itertools模块提供了三个函数来解决这类问题。 其中一个是 itertools.permutations() , 它接受一个集合并产生一个元组序列,每个元组由集合中所有元素的一个可能排列组成。 也就是说通过打乱集合中元素排列顺序生成一个元组,比如:

    >>> items = ['a', 'b', 'c']
    >>> from itertools import permutations
    >>> for p in permutations(items):
    ...     print(p)
    ...
    ('a', 'b', 'c')
    ('a', 'c', 'b')
    ('b', 'a', 'c')
    ('b', 'c', 'a')
    ('c', 'a', 'b')
    ('c', 'b', 'a')
    >>>

    如果你想得到指定长度的所有排列,你可以传递一个可选的长度参数。就像这样:

    >>> for p in permutations(items, 2):
    ...     print(p)
    ...
    ('a', 'b')
    ('a', 'c')
    ('b', 'a')
    ('b', 'c')
    ('c', 'a')
    ('c', 'b')
    >>>

      2.使用 itertools.combinations() 可得到输入集合中元素的所有的组合。比如:

    >>> from itertools import combinations
    >>> for c in combinations(items, 3):
    ...     print(c)
    ...
    ('a', 'b', 'c')
    
    >>> for c in combinations(items, 2):
    ...     print(c)
    ...
    ('a', 'b')
    ('a', 'c')
    ('b', 'c')
    
    >>> for c in combinations(items, 1):
    ...     print(c)
    ...
    ('a',)
    ('b',)
    ('c',)
    >>>

      3.而函数 itertools.combinations_with_replacement() 允许同一个元素被选择多次,比如:

    >>> for c in combinations_with_replacement(items, 3):
    ...     print(c)
    ...
    ('a', 'a', 'a')
    ('a', 'a', 'b')
    ('a', 'a', 'c')
    ('a', 'b', 'b')
    ('a', 'b', 'c')
    ('a', 'c', 'c')
    ('b', 'b', 'b')
    ('b', 'b', 'c')
    ('b', 'c', 'c')
    ('c', 'c', 'c')
    >>>
      以上几种是不是非常像我们数学课上所学的排列组合,注意,他们返回的都是可迭代对象。 
     
     
      六、zip,chain函数应用
      比如,假设你头列表和一个值列表,就像下面这样:
    headers = ['name', 'shares', 'price']
    values = ['ACME', 100, 490.1]

      使用zip()可以让你将它们打包并生成一个字典:

    s = dict(zip(headers,values))

      你想在多个对象执行相同的操作,但是这些对象在不同的容器中,你希望代码在不失可读性的情况下避免写重复的循环。

     itertools.chain() 方法可以用来简化这个任务。 它接受一个可迭代对象列表作为输入,并返回一个迭代器,有效的屏蔽掉在多个容器中迭代细节。 为了演示清楚,考虑下面这个例子:

      

    >>> from itertools import chain
    >>> a = [1, 2, 3, 4]
    >>> b = ['x', 'y', 'z']
    >>> for x in chain(a, b):
    ... print(x)
    ...
    1
    2
    3
    4
    x
    y
    z

      itertools.chain() 接受一个或多个可迭代对象作为输入参数。 然后创建一个迭代器,依次连续的返回每个可迭代对象中的元素。 这种方式要比先将序列合并再迭代要高效的多。

      

      七、你想将一个多层嵌套的序列展开成一个单层列表

      可以写一个包含 yield from 语句的递归生成器来轻松解决这个问题。比如:

    from collections import Iterable
    
    def flatten(items, ignore_types=(str, bytes)):
        for x in items:
            if isinstance(x, Iterable) and not isinstance(x, ignore_types):
                yield from flatten(x)
            else:
                yield x
    
    items = [1, 2, [3, 4, [5, 6], 7], 8]
    # Produces 1 2 3 4 5 6 7 8
    for x in flatten(items):
        print(x)

      语句 yield from 在你想在生成器中调用其他生成器作为子例程的时候非常有用。 如果你不使用它的话,那么就必须写额外的 for 循环了。比如:

    def flatten(items, ignore_types=(str, bytes)):
        for x in items:
            if isinstance(x, Iterable) and not isinstance(x, ignore_types):
                for i in flatten(x):
                    yield i
            else:
                yield x

     

      注意:yield from 与 yield区别

    yield from 后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器。

    使用yield

    # 字符串
    astr='ABC'
    # 列表
    alist=[1,2,3]
    # 字典
    adict={"name":"wangbm","age":18}
    # 生成器
    agen=(i for i in range(4,8))
    
    def gen(*args, **kw):
        for item in args:
            for i in item:
                yield i
    
    new_list=gen(astr, alist, adict, agen)
    print(list(new_list))
    # ['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]

      使用yield from

    # 字符串
    astr='ABC'
    # 列表
    alist=[1,2,3]
    # 字典
    adict={"name":"wangbm","age":18}
    # 生成器
    agen=(i for i in range(4,8))
    
    def gen(*args, **kw):
        for item in args:
            yield from item
    
    new_list=gen(astr, alist, adict, agen)
    print(list(new_list))
    # ['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]

      可以看出,yield from后面加上可迭代对象,他可以把可迭代对象里的每个元素一个一个的yield出来,对比yield来说代码更加简洁,结构更加清晰。

  • 相关阅读:
    CF1442E. Black, White and Grey Tree
    CF1442D. Sum
    CF1444D. Rectangular Polyline
    arc107F
    外心与垂心关系
    CF1434E. A Convex Game
    CF1434D. Roads and Ramen
    arc106E
    arc106F
    CF704E. Iron Man
  • 原文地址:https://www.cnblogs.com/jimmyhe/p/10825082.html
Copyright © 2011-2022 走看看