python - 以不同的方式实现 `__iter__()` 和 `__next__()`
问题描述
我正在阅读一本关于 Python 的书,它说明了如何实现迭代器协议。
class Fibbs:
def __init__(self):
self.a = 0
self.b = 1
def __next__(self):
self.a, self.b = self.b, self.a + self.b
return self.a
def __iter__(self):
return self
在这里,self
我相信它本身就是可迭代和迭代器?但是,下面的段落说:
请注意,迭代器实现了该
__iter__
方法,实际上,该方法将返回迭代器本身。在许多情况下,您会将__iter__
方法放在另一个对象中,您将在 for 循环中使用该对象。然后将返回您的迭代器。建议迭代器__iter__
另外实现自己的方法(返回self,就像我这里做的那样),这样就可以直接在for循环中使用它们自己。
这是否意味着您可以放入两个不同__iter__()
的__next__()
对象?可以为属于不同类的对象完成吗?只能对属于不同类的对象进行吗?实现迭代器协议的方式可能有点奇怪。但我只是想看看如何,只要它实际上可以这样实现。
解决方案
如何制作迭代器和可迭代对象
有两种方法可以做到这一点:
- 实现
__iter__
,仅此return self
而已,__next__
在同一个类上实现。你写了一个迭代器。 - 实现
__iter__
以返回遵循 #1 规则的其他对象(一种廉价的方法是将其编写为生成器函数,这样您就不必手动实现其他类)。不要实施__next__
. 你写了一个不是迭代器的迭代器。
对于每个协议的正确实现版本,区分它们的方式就是__iter__
方法。如果主体只是return self
(可能带有日志语句或其他东西,但没有其他副作用),那么它要么是迭代器,要么写得不正确。如果主体是其他任何东西,那么它要么是非迭代器可迭代的,要么写得不正确。其他任何事情都违反了协议的要求。
在 #2 的情况下,根据定义,另一个对象将属于另一个类(因为您要么具有幂等性__iter__
并实现__next__
,要么只有__iter__
,没有__next__
,这会产生一个新的迭代器)。
为什么协议是这样设计的
您__iter__
甚至需要迭代器的原因是支持以下模式:
iterable = MyIterable(...)
iterator = iter(iterable) # Invokes MyIterable.__iter__
next(iterator, None) # Throw away first item
for x in iterator: # for implicitly calls iterator's __iter__; dies if you don't provide __iter__
您总是为可迭代对象返回一个新的迭代器,而不是仅仅使它们成为迭代器并在__iter__
调用时重置状态的原因是为了处理上述情况(如果MyIterable
只是返回自身并重置迭代,for
循环的隐式调用__iter__
将再次重置它并撤消第一个元素的预期跳过)并支持这样的模式:
for x in iterable:
for y in iterable: # Operating over product of all elements in iterable
如果__iter__
将自身重置到开头并且只有一个状态,这将:
- 获取第一个项目并将其放入
x
- 重置,然后遍历
iterable
将每个值放入的整个过程y
- 尝试继续外循环,发现它已经用尽了,永远不要给任何其他价值
x
它也是必需的,因为 Python 假定这iter(x) is x
是一种安全、无副作用的方法来测试可迭代对象是否是迭代器。如果你__iter__
修改了自己的状态,它不是没有副作用的。在最坏的情况下,对于可迭代对象,它应该浪费一点时间来创建一个立即被丢弃的迭代器。对于迭代器,它实际上应该是免费的(因为它只是返回自己)。
直接回答您的问题:
这是否意味着您可以放入两个不同
__iter__()
的__next__()
对象?
对于 itera s,你不能(它必须有两种方法,虽然__iter__
很简单)。对于非迭代器的迭代器,您必须(它必须只有,__iter__
并返回一些其他迭代器对象)。没有“可以”。
可以为属于不同类的对象完成吗?
是的。
只能对属于不同类的对象进行吗?
是的。
例子
可迭代的示例:
class MyRange:
def __init__(self, start, stop):
self.start = start
self.stop = stop
def __iter__(self):
return MyRangeIterator(self) # Returns new iterator, as this is a non-iterator iterable
# Likely to have other methods (because iterables are often collections of
# some sort and support many other behaviors)
# Does *not* have __next__, as this is not an iterator
迭代器示例:
class MyRangeIterator: # Class is often non-public and or defined inside the iterable as
# nested class; it exists solely to store state for iterator
def __init__(self, rangeobj): # Constructed from iterable; could pass raw values if you preferred
self.current = rangeobj.start
self.stop = rangeobj.stop
def __iter__(self):
return self # Returns self, because this is an iterator
def __next__(self): # Has __next__ because this is an iterator
retval = self.current # Must cache current because we need to modify it before we return
if retval >= self.stop:
raise StopIteration # Indicates iterator exhausted
self.current += 1 # Ensure state updated for next call
return retval # Return cached value
# Unlikely to have other methods; iterators are generally iterated and that's it
通过创建生成器函数,您不实现自己的迭代器类的“简单可迭代”示例__iter__
:
class MyEasyRange:
def __init__(self, start, stop): ... # Same as for MyRange
def __iter__(self): # Generator function is simpler (and faster)
# than writing your own iterator class
current = self.start # Can't mutate attributes, because multiple iterators might rely on this one iterable
while current < self.stop:
yield current # Produces value and freezes generator until iteration resumes
current += 1
# reaching the end of the function acts as implicit StopIteration for a generator
推荐阅读
- jdbc - phoenix 查询服务器客户端太多 close_wait
- spring - Beans 可以在一个应用程序 IOC 之间共享到另一个应用程序 IOC 在 JVM 中运行吗?
- ios - 当应用程序为背景时如何添加图像
- python - pygame.sprite.Group.draw 不绘制图像
- java - 如何为打包安装程序(使用 Jpackage 创建)安装的 java 应用程序设置自定义安装目录?
- spring-boot - Spring REST:在 RestTemplate 与 RequestEntity 中指定 http 方法和 url
- azure - 基于读取 Odata 的 Azure 警报
- android - 为回收站视图的项目视图设置可访问性操作
- google-cloud-platform - GCP 警报过滤器不影响公开事件
- pygame - 如何返回类中立方体的位置值?