文章目录
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
类。