文章目录
Python 的可迭代对象我们是很熟悉的,比如 list 、 str、 tuple、 dict 、 file object,它们都支持进行 for 循环操作,每次返回一个项。
可迭代对象
现在有这样一个类,用于记录平时的事项清单
            
            
              python
              
              
            
          
          class Things:
    def __init__(self, owner):
        self.checklist = []
        self.owner = owner
    def add(self, task_name):
        self.checklist.append(task_name)
        return self
things = Things("simon")
things.add("shopping").add("running").add("sleep")如果要遍历这个 checklist 清单,你应该会这样实现:
            
            
              python
              
              
            
          
          for item in things.checklist:
    print(item)output >>>
shopping
running
sleep但如果将上面的语句修改成下面的方式就会报错
            
            
              python
              
              
            
          
          for item in things:
    print(item)
            
            
              shell
              
              
            
          
          TypeError: 'Things' object is not iterable想要知道为什么抛出这个异常,我们需要先知道关于 for 在官方文档的定义:The for-loop is always used in combination with an iterable object, like a list or a range. ,即 for 只能用于 iterable object(可迭代对象)上。上面的代码示例中 things 并不是可迭代对象,而 things.checklist 是一个列表即可迭代的对象。
而关于可迭代对象的定义是:
An object capable of returning its members one at a time.
...
objects of any classes you define with an
__iter__()method or with a__getitem__()method that implements sequence semantics.
即对于常见的 list、tuple之外,那些实现了 __iter__() 或者 __getitem__() 方法的类对象也为可迭代对象。我们可以根据这条定义将 things 变成一个可迭代对象。
版本1:可迭代对象的 __getitem__() 实现
- refer 文档关于 __getitem__()的定义
            
            
              python
              
              
            
          
          class Things:
    def __init__(self, owner):
        self.checklist = []
        self.owner = owner
    def add(self, task_name):
        self.checklist.append(task_name)
    def __getitem__(self, index):
        return self.checklist[index]
things = Things("simon")
things.add("shopping").add("running").add("sleep")
for item in things:
    print(item)此时还可以对 things 进行类似数组的操作
            
            
              shell
              
              
            
          
          > things[0]
>> "shopping"版本2:可迭代对象的 __iter__() 实现
这个版本的实现较为繁琐,根据官方文档的定义,开发者需要定义一个 container object(容器对象) 和 iterator object(迭代器对象):
- 对于 container obj需要实现__iter__()方法,该方法返回一个iterator object
- 对于 iterator obj需要实现iterator.__iter__()和iterator.__next__()两个方法
这个版本的实现和设计模式中的迭代器模式的实现结构一致,这点会在另一篇 blog 提及
下面我们可以实现 __iter__() 的版本:
            
            
              python
              
              
            
          
          class TaskIterator:
    def __init__(self, checklist):
        self.checklist = checklist
        self.index = 0 
    def __iter__(self):
        return self
    def __next__(self):
        try:
            result = self.checklist[self.index]
            self.index += 1
        except IndexError:
            raise StopIteration
            
        return result
        
class Things:
    def __init__(self, owner):
        self.checklist = []
        self.owner = owner
    def add(self, task_name):
        self.checklist.append(task_name)
    def __iter__(self):
        return TaskIterator(self.checklist)
things = Things("simon")
things.add("shopping").add("running").add("sleep")
for item in things:
    print(item)可迭代对象不一定是迭代器
这里有一个值得注意的细节,对于 TaskIterator 实例我们还可以用 next()
            
            
              shell
              
              
            
          
          > ti = TaskIterator(["a","b","c"])
> next(ti)
>> "a"但假如我们对 Things 实例用 next() 就会报错提示不是迭代器
            
            
              shell
              
              
            
          
          > next(things)
>> TypeError: 'Things' object is not an iterator查阅 next() 函数在官方文档的定义:
Retrieve the next item from the iterator by calling its
__next__()method. If default is given, it is returned if the iterator is exhausted, otherwise StopIteration is raised.
该函数服务于 iterator 并调用其 __next__() 方法,这里可以看出:
可迭代的 iterable != 迭代对象 iterator
那为什么 for i in things 时,可迭代对象 Things 实例就可以迭代呢?在这里我们需要知道的是,当使用 for 的时候,其内部实际上调用了 python 的内置函数 iter(),该函数返回一个迭代器对象。
Let me start with a brief description of what a for loop does:
- The for loop creates an iterator from the iterable using the built-in iter() function.
- It calls the iterator's
__next__()method and assigns the return value to the variable in the for loop statement.- The program executes the code within the loop. Then, the loop repeats itself and calls the iterator's
__next__()method again. This process keeps repeating.- If the iterator's
__next__()method raises a StopIteration, the for loop terminates.
换句话说,当我们执行 for i in things 时,实际上是 iter() 函数创建了 TaskIterator 的实例对象,而该实例对象由于实现了迭代器协议(__next__()),所以可以被迭代.
同样的,如果我们将上面的 things 实例用 iter() 函数包一下,则运行正常:
            
            
              shell
              
              
            
          
          > next(iter(things))
>> "shopping"引入生成器知识
现在我们短暂的跳转到另一个知识点 生成器 generator, 在 python 中有两种便捷的实现方式:生成器函数和生成器表达式
生成器函数
            
            
              python
              
              
            
          
          def numbers(count):
    for n in range(count):
        yield n
number_iterator = numbers(10)
            
            
              shell
              
              
            
          
          > next(number_iterator)
>> 0
> number_iterator
>> <generator object numbers at 0x00000293D4854AD0>生成器表达式
我们应该对列表生成式非常熟悉,比如 [i+3 for i in range(10)]。而生成器表达式实际上只是将 []改成 (),不同的是后者属于惰性的方式,它不会一下子将所有的数据都创建而占用内存,这在读取超大数据时非常有用。
            
            
              shell
              
              
            
          
          > [i for i in range(10)]
>> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
> (i for i in range(10))
>> <generator object <genexpr> at 0x00000293D48545F0>为啥要提及这个知识点呢?在 python 文档中关于 generator 有这样一段描述:
Generator Types
Python's generators provide a convenient way to implement the iterator protocol. If a container object's
__iter__()method is implemented as a generator, it will automatically return an iterator object (technically, a generator object) supplying the__iter__()and__next__()methods. More information about generators can be found in the documentation for the yield expression.
我们回过头来看 版本2 的实现,Things.__iter__() 的目的是返回可迭代对象:
            
            
              python
              
              
            
          
          class Things:
	...
    def __iter__(self):
        return TaskIterator(self.checklist)而根据这段引用,我们知道可以用生成器对象替代迭代器对象,据此实现更为简洁的版本。
版本3: 可迭代对象的生成器函数实现
            
            
              python
              
              
            
          
          class Things:
    def __init__(self, owner):
        self.checklist = []
        self.owner = owner
    def add(self, task_name):
        self.checklist.append(task_name)
    def __iter__(self):
        for task in self.checklist:
            yield task版本4: 可迭代对象的生成器表达式实现
            
            
              python
              
              
            
          
          class Things:
    def __init__(self, owner):
        self.checklist = []
        self.owner = owner
    def add(self, task_name):
        self.checklist.append(task_name)
    def __iter__(self):
        return (task for task in self.checklist)此时,版本3和版本4不再需要额外实现一个 TaskIterator 类。