是否有统一的方法来了解迭代是否会使用可迭代对象?
假设您有一个特定的函数crunch,它要求参数的可迭代对象,并多次使用它.就像是:
def crunch (vals):
for v in vals:
chomp(v)
for v in vals:
yum(v)
Run Code Online (Sandbox Code Playgroud)
(注意:将两个for循环合并在一起不是一个选项).
如果使用不是列表的iterable调用函数,则会出现问题.在以下调用中,yum函数永远不会执行:
crunch(iter(range(4))
Run Code Online (Sandbox Code Playgroud)
我们原则上可以通过重新定义crunch函数来解决这个问题,如下所示:
def crunch (vals):
vals = list(vals)
for v in vals:
chomp(v)
for v in vals:
yum(v)
Run Code Online (Sandbox Code Playgroud)
但如果调用的话,这将导致使用两倍的内存crunch:
hugeList = list(longDataStream)
crunch(hugeList)
Run Code Online (Sandbox Code Playgroud)
我们可以通过这样定义来解决这个crunch问题:
def crunch (vals):
if type(vals) is not list:
vals = list(vals)
for v in vals:
chomp(v)
for v in vals:
yum(v)
Run Code Online (Sandbox Code Playgroud)
但仍然存在colud,其中调用代码将数据存储在某些内容中
例如:
from collections import deque
hugeDeque = deque(longDataStream)
crunch(hugeDeque)
Run Code Online (Sandbox Code Playgroud)
有一个isconsumable谓词会很好,所以我们可以crunch像这样定义:
def crunch (vals):
if isconsumable(vals):
vals = list(vals)
for v in vals:
chomp(v)
for v in vals:
yum(v)
Run Code Online (Sandbox Code Playgroud)
有这个问题的解决方案吗?
一种可能性是测试该项是否是序列,使用isinstance(val, collections.Sequence).非消费性仍然没有完全保证,但我认为这是你能得到的最好的.Python序列必须有一个长度,这意味着至少它不能是一个开放式的迭代器,并且通常意味着必须提前知道元素,这反过来意味着它们可以被迭代没有消耗它们.仍然可以编写适合序列协议但不可重复的病理类,但是你永远无法处理它们.
请注意,既不是Iterable也不Iterator是合适的选择,因为这些类型不保证长度,因此不能保证迭代甚至是有限的,更不用说可重复了.但是,您可以检查两者Sized和Iterable.
重要的是要记录你的函数将迭代它的参数两次,从而警告用户必须传入一个支持它的对象.
另一个附加选项可能是查询iterable是否是它自己的迭代器:
if iter(vals) is vals:
vals = list(vals)
Run Code Online (Sandbox Code Playgroud)
因为在这种情况下,它只是一个迭代器.
这适用于生成器,迭代器,文件和许多其他为"一次运行"设计的对象,换句话说,所有迭代器本身都是迭代器,因为迭代器self从它返回__iter__().
但这可能还不够,因为有些对象在迭代时自行清空而不是自己的迭代器.
通常,一个自耗对象将是它自己的迭代器,但有些情况下可能不允许这样做.
想象一个包装列表并在迭代中清空此列表的类,例如
class ListPart(object):
"""Liste stückweise zerlegen."""
def __init__(self, data=None):
if data is None: data = []
self.data = data
def next(self):
try:
return self.data.pop(0)
except IndexError:
raise StopIteration
def __iter__(self):
return self
def __len__(self): # doesn't work with __getattr__...
return len(self.data)
Run Code Online (Sandbox Code Playgroud)
你称之为
l = [1, 2, 3, 4]
lp = ListPart(l)
for i in lp: process(i)
# now l is empty.
Run Code Online (Sandbox Code Playgroud)
如果我现在将其他数据添加到该列表并再次遍历同一个对象,我将获得违反协议的新数据:
该协议的目的是一旦迭代器的
next()方法提出StopIteration,它将继续在后续调用中这样做.不遵守此属性的实现被视为已损坏.(这个约束是在Python 2.3中添加的;在Python 2.2中,根据此规则会破坏各种迭代器.)
因此,在这种情况下,对象必须返回与自身不同的迭代器,尽管它是自耗的.在这种情况下,可以这样做
def __iter__(self):
while True:
try:
yield l.pop(0)
except IndexError: # pop from empty list
return
Run Code Online (Sandbox Code Playgroud)
它会在每次迭代时返回一个新的生成器 - 在我们讨论的情况下,它会通过mash掉落.