CP学习笔记(9) - 迭代

Python中自定义迭代类型,需要包含自定义的__next__方法。__next__方法能够返回迭代类型的下一项,并在迭代结束时提示编译器:StopIteration

# define a new iteration class
>>> class LetterIter:
        """An iterator over letters of the alphabet in ASCII order."""
        def __init__(self, start='a', end='e'):
            self.next_letter = start
            self.end = end
        def __next__(self):
            if self.next_letter == self.end:
                raise StopIteration
            letter = self.next_letter
            self.next_letter = chr(ord(letter)+1)
            return letter
           
# init a object of defined iteration class
>>> letter_iter = LetterIter()
>>> letter_iter.__next__()
'a'
>>> letter_iter.__next__()
'b'
>>> next(letter_iter)
'c'
>>> letter_iter.__next__()
'd'
>>> letter_iter.__next__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 12, in next
StopIteration

如果一个对象的__iter__方法被调用后能够返回一个迭代对象,那么这个对象就是可迭代(iterable)。

>>> class Letters:
        def __init__(self, start='a', end='e'):
            self.start = start
            self.end = end
        def __iter__(self):
            return LetterIter(self.start, self.end)

>>> b_to_k = Letters('b', 'k')
>>> first_iterator = b_to_k.__iter__()
>>> next(first_iterator)
'b'
>>> next(first_iterator)
'c'
>>> second_iterator = iter(b_to_k)
>>> second_iterator.__next__()
'b'
>>> first_iterator.__next__()
'd'
>>> first_iterator.__next__()
'e'
>>> second_iterator.__next__()
'c'
>>> second_iterator.__next__()
'd'

for语句也可以用于列举。

for <name> in <expression>:
    <suite>

编译器首先会检查<expression>是否是可迭代对象,然后调用__iter__方法。编译器会反复调用__next__方法直至遇到StopIteration。每次调用__next__方法,编译器都会把得到的值绑定在<name>上,然后执行<suite>语句。下面两个示例是等价的。

>>> counts = [1, 2, 3]
>>> for item in counts:
        print(item)
1
2
3
>>> items = counts.__iter__()
>>> try:
        while True:
            item = items.__next__()
            print(item)
    except StopIteration:
        pass
1
2
3

Python官网文档Iterator types一章建议迭代对象的__iter__方法最好返回迭代对象本身,这样所有迭代对象都是可迭代的。

__next__方法只能用于列举简单的迭代对象,对于复杂的迭代对象需要用到generator迭代类型。不同的是,generator迭代类型不使用return返回值,而是用yield语句。

如何理解Python关键字yield:

当函数被调用时,函数体中的代码是不会运行的,函数仅仅是返回一个生成器对象。这里理解起来可能稍微有点复杂。函数中的代码每次会在for循环中被执行,接下来是最难的一部分:
for第一次调用生成器对象时,代码将会从函数的开始处运行直到遇到yield为止,然后返回此次循环的第一个值,接着循环地执行函数体,返回下一个值,直到没有值返回为止。
一旦函数运行再也没有遇到yield时,生成器就被认为是空的。

>>> def letters_generator():
        current = 'a'
        while current <= 'd':
            yield current
            current = chr(ord(current)+1)
>>> for letter in letters_generator():
        print(letter)
a
b
c
d

>>> letters = letters_generator()
>>> type(letters)
<class 'generator'>
>>> letters.__next__()
'a'
>>> letters.__next__()
'b'
>>> letters.__next__()
'c'
>>> letters.__next__()
'd'
>>> letters.__next__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Python中还有Streams类可以返回数据序列。Streams实例是懒惰计算(Lazyily Computed)的。

>>> class Stream:
        """A lazily computed linked list."""
        class empty:
            def __repr__(self):
                return 'Stream.empty'
        empty = empty()
        def __init__(self, first, compute_rest=lambda: empty):
            assert callable(compute_rest), 'compute_rest must be callable.'
            self.first = first
            self._compute_rest = compute_rest
        @property
        def rest(self):
            """Return the rest of the stream, computing it if necessary."""
            if self._compute_rest is not None:
                self._rest = self._compute_rest()
                self._compute_rest = None
            return self._rest
        def __repr__(self):
            return 'Stream({0}, <...>)'.format(repr(self.first))

Streams实例返回两项:first 和 rest。每次返回值时只计算first,不计算rest。

>>> r = Link(1, Link(2+3, Link(9)))

>>> s = Stream(1, lambda: Stream(2+3, lambda: Stream(9)))

>>> r.first
1
>>> s.first
1
>>> r.rest.first
5
>>> s.rest.first
5
>>> r.rest
Link(5, Link(9))
>>> s.rest
Stream(5, <...>) # rest项为None,并没有被计算出来

posted on 2015-12-19 11:59  Rim99  阅读(174)  评论(0编辑  收藏  举报

导航