是否有统一的方法来了解迭代是否会使用可迭代对象?
假设您有一个函数crunch
,它要求参数的可迭代对象,并多次使用它。类似的东西:
def crunch (vals):
for v in vals:
chomp(v)
for v in vals:
yum(v)
(注意:将两个for
循环合并在一起不是一种选择。)
如果使用不是列表的iterable调用函数,则会出现问题。在以下调用中,永远不会执行yum
函数:
crunch(iter(range(4))
我们原则上可以通过重新定义crunch
函数来解决此问题,如下所示:
def crunch (vals):
vals = list(vals)
for v in vals:
chomp(v)
for v in vals:
yum(v)
但如果对crunch
的调用是:
hugeList = list(longDataStream)
crunch(hugeList)
我们可以通过这样定义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)
但仍然存在colud,其中调用代码将数据存储在
中例如:
from collections import deque
hugeDeque = deque(longDataStream)
crunch(hugeDeque)
拥有isconsumable
谓词会很不错,这样我们就可以像这样定义crunch
:
def crunch (vals):
if isconsumable(vals):
vals = list(vals)
for v in vals:
chomp(v)
for v in vals:
yum(v)
这个问题有解决方案吗?
答案 0 :(得分:6)
一种可能性是使用isinstance(val, collections.Sequence)
测试该项是否为序列。非消费性仍然没有完全保证,但我认为这是你能得到的最好的。 Python序列必须有一个长度,这意味着至少它不能是一个开放式的迭代器,并且通常意味着必须提前知道元素,这反过来意味着它们可以被迭代没有消耗它们。仍然可以编写符合序列协议但不可重复的病理类,但是你永远无法处理它们。
请注意,Iterable
和Iterator
都不是合适的选择,因为这些类型不保证长度,因此无法保证迭代甚至是有限的,更不用说可重复了。但是,您可以检查Sized
和Iterable
。
重要的是要记录你的函数将迭代它的参数两次,从而警告用户他们必须传入一个支持它的对象。
答案 1 :(得分:5)
另一个附加选项可能是查询iterable是否是它自己的迭代器:
if iter(vals) is vals:
vals = list(vals)
因为在这种情况下,它只是一个迭代器。
这适用于生成器,迭代器,文件和许多其他为“一次运行”设计的对象,换句话说,所有迭代器本身都是迭代器,因为iterator returns self
from its __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)
你称之为
l = [1, 2, 3, 4]
lp = ListPart(l)
for i in lp: process(i)
# now l is empty.
如果我现在将其他数据添加到该列表并再次遍历同一个对象,我将获得breach of the protocol的新数据:
协议的目的是一旦迭代器的
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
在每次迭代时返回一个新的生成器 - 在我们讨论的情况下,这些东西会掉落在mash中。
答案 2 :(得分:4)
def crunch (vals):
vals1, vals2 = itertools.tee(vals, 2)
for v in vals1:
chomp(v)
for v in vals2:
yum(v)
在这种情况下,tee
将在内部存储整个vals
,因为一次迭代在另一次迭代开始之前完成
答案 3 :(得分:3)
许多答案接近这一点但却错过了。
Iterator
是通过迭代消耗的对象。没有其他办法了。迭代器对象的示例是通过调用iter()
返回的对象,或itertools
module中函数返回的对象。
检查对象是否为迭代器的正确方法是调用isinstance(obj, Iterator)
。这基本上检查对象是否实现了next()
方法(Python 3中的__next__()
),但您不需要关心它。
所以,请记住,迭代器总是被消耗掉。例如:
# suppose you have a list
my_list = [10, 20, 30]
# and build an iterator on the list
my_iterator = iter(my_list)
# iterate the first time over the object
for x in my_iterator:
print x
# then again
for x in my_iterator:
print x
这将打印列表内容一次。
然后有Iterable
个对象。当你在一个iterable上调用iter()
时,它将返回一个迭代器。在这个页面评论我犯了一个错误,所以我将在这里澄清。每次调用都不需要可重试对象来返回 new iterator 。许多迭代器本身都是可迭代的(即你可以在它们上面调用iter()
)并且它们将返回对象本身。
一个简单的例子就是列表迭代器。 iter(my_list)
和iter(iter(my_list))
是同一个对象,这基本上是@glglgl的答案。
iterator protocol要求迭代器对象将自己作为自己的迭代器返回(因此可以迭代)。这不是迭代机制工作所必需的,但是你无法遍历迭代器对象。
所有这些都说,你应该做的是检查你是否得到了迭代器,如果是这种情况,请复制迭代结果(list()
)。您的isconsumable(obj)
是(正如有人已经说过的)isinstance(obj, Iterator)
。
请注意,这也适用于xrange()
。 xrange(10)
会返回xrange
个对象。每次你对xrange对象进行操作时,它都会从头开始返回一个新的迭代器,所以你很好,不需要复制。
答案 4 :(得分:1)
__contains__
方法__iter__()
或__getitem__()
方法的对象。 iter()
,它返回对象的迭代器。这个
迭代器适用于一组值的传递。next()
方法的可迭代。__iter__()
方法。__getitem__()
特殊方法定义返回的len()
方法
序列的长度。list
,str
,
tuple
和unicode
。__getitem__()
和
__len__()
,但被认为是映射而不是序列,因为
查找使用任意不可变键而不是整数。现在有很多方法可以测试对象是可迭代的,迭代器还是某种序列。以下是这些方法的摘要,以及它们如何对各种对象进行分类:
Iterable Iterator iter_is_self Sequence MutableSeq
object
[] True False False True True
() True False False True False
set([]) True False False False False
{} True False False False False
deque([]) True False False False False
<listiterator> True True True False False
<generator> True True True False False
string True False False True False
unicode True False False True False
<open> True True True False False
xrange(1) True False False True False
Foo.__iter__ True False False False False
Sized has_len has_iter has_contains
object
[] True True True True
() True True True True
set([]) True True True True
{} True True True True
deque([]) True True True False
<listiterator> False False True False
<generator> False False True False
string True True False True
unicode True True False True
<open> False False True False
xrange(1) True True True False
Foo.__iter__ False False True False
每列引用一种不同的方式来分类迭代,每一行引用一种不同的对象。
import pandas as pd
import collections
import os
def col_iterable(obj):
return isinstance(obj, collections.Iterable)
def col_iterator(obj):
return isinstance(obj, collections.Iterator)
def col_sequence(obj):
return isinstance(obj, collections.Sequence)
def col_mutable_sequence(obj):
return isinstance(obj, collections.MutableSequence)
def col_sized(obj):
return isinstance(obj, collections.Sized)
def has_len(obj):
return hasattr(obj, '__len__')
def listtype(obj):
return isinstance(obj, types.ListType)
def tupletype(obj):
return isinstance(obj, types.TupleType)
def has_iter(obj):
"Could this be a way to distinguish basestrings from other iterables?"
return hasattr(obj, '__iter__')
def has_contains(obj):
return hasattr(obj, '__contains__')
def iter_is_self(obj):
"Seems identical to col_iterator"
return iter(obj) is obj
def gen():
yield
def short_str(obj):
text = str(obj)
if text.startswith('<'):
text = text.split()[0] + '>'
return text
def isiterable():
class Foo(object):
def __init__(self):
self.data = [1, 2, 3]
def __iter__(self):
while True:
try:
yield self.data.pop(0)
except IndexError: # pop from empty list
return
def __repr__(self):
return "Foo.__iter__"
filename = 'mytestfile'
f = open(filename, 'w')
objs = [list(), tuple(), set(), dict(),
collections.deque(), iter([]), gen(), 'string', u'unicode',
f, xrange(1), Foo()]
tests = [
(short_str, 'object'),
(col_iterable, 'Iterable'),
(col_iterator, 'Iterator'),
(iter_is_self, 'iter_is_self'),
(col_sequence, 'Sequence'),
(col_mutable_sequence, 'MutableSeq'),
(col_sized, 'Sized'),
(has_len, 'has_len'),
(has_iter, 'has_iter'),
(has_contains, 'has_contains'),
]
funcs, labels = zip(*tests)
data = [[test(obj) for test in funcs] for obj in objs]
f.close()
os.unlink(filename)
df = pd.DataFrame(data, columns=labels)
df = df.set_index('object')
print(df.ix[:, 'Iterable':'MutableSeq'])
print
print(df.ix[:, 'Sized':])
isiterable()