如何使自定义对象可迭代?
Posted
技术标签:
【中文标题】如何使自定义对象可迭代?【英文标题】:How to make a custom object iterable? 【发布时间】:2014-03-07 02:13:01 【问题描述】:我有一个list
的自定义类对象(示例如下)。
使用:list(itertools.chain.from_iterable(myBigList))
我想将所有stations
子列表“合并”成一个大列表。所以我想我需要让我的自定义类成为可迭代的。
这是我的自定义类的示例。
class direction(object) :
def __init__(self, id) :
self.id = id
self.__stations = list()
def __iter__(self):
self.__i = 0 # iterable current item
return iter(self.__stations)
def __next__(self):
if self.__i<len(self.__stations)-1:
self.__i += 1
return self.__stations[self.__i]
else:
raise StopIteration
我实现了__iter__
和__next__
,但它似乎不起作用。他们甚至没有被调用。
知道我做错了什么吗?
注意:使用 Python 3.3
【问题讨论】:
你在继承list
吗?
不,List 只是我班级的成员。
这是实际的缩进吗? __iter__
和 __next__
在您的类定义之外。
修复缩进后它对我有用:list(chain.from_iterable([direction(10), direction(20)]))
,返回[]
@jonrsharpe 抱歉复制/粘贴错误。我的缩进很好。
【参考方案1】:
__iter__
是您尝试迭代类实例时调用的名称:
>>> class Foo(object):
... def __iter__(self):
... return (x for x in range(4))
...
>>> list(Foo())
[0, 1, 2, 3]
__next__
是在从 __iter__
返回的对象上调用的内容(在 python2.x 上,它是 next
,而不是 __next__
——我通常给它们都取别名,这样代码就可以使用...):
class Bar(object):
def __init__(self):
self.idx = 0
self.data = range(4)
def __iter__(self):
return self
def __next__(self):
self.idx += 1
try:
return self.data[self.idx-1]
except IndexError:
self.idx = 0
raise StopIteration # Done iterating.
next = __next__ # python2.x compatibility.
在 cmets 中,有人询问您将如何构造可以多次迭代的对象。在这种情况下,我建议采用与 Python 相同的方法,将迭代器从数据容器中分离出来:
class BarIterator(object):
def __init__(self, data_sequence):
self.idx = 0
self.data = data_sequence
def __iter__(self):
return self
def __next__(self):
self.idx += 1
try:
return self.data[self.idx-1]
except IndexError:
self.idx = 0
raise StopIteration # Done iterating.
class Bar(object):
def __init__(self, data_sequence):
self.data_sequence = data_sequence
def __iter__(self):
return BarIterator(self.data_sequence)
【讨论】:
好吧,我真的很困惑,因为我的代码是错误的。但是您的回答确实让我更清楚地了解了可迭代的工作原理! 为什么不直接返回self.data
的迭代器呢?虽然对于 OP 的目的来说很好,但这不是线程安全的。如果您想跳过第一个元素,可以在返回之前在迭代器上调用 next()
。
@MadPhysicist -- 这是一个公平的观点。我回答这个问题已经 2 年了,但我认为我这样做的原因是为了向 OP 演示如何使用带有 __iter__
和 __next__
的迭代器协议。跨度>
@mgilson。说得通。无论哪种方式都很有价值。
在这种情况下,我会从 stdlib 对内置容器的作用中取出一页——例如list._iter__
返回一个 listiterator
实例。 listiterator
仍然只能迭代一次。它实际上只是一个索引和一个指向原始数据的指针。这样一来,调用者就可以随意调用iter(some_list)
,并且每次都获得独立的迭代器。【参考方案2】:
只需实现__iter__
就足够了。
class direction(object) :
def __init__(self, id) :
self.id = id
self.__stations = list()
def __iter__(self):
#return iter(self.__stations[1:]) #uncomment this if you wanted to skip the first element.
return iter(self.__stations)
a = direction(1)
a._direction__stations= range(5)
b = direction(1)
b._direction__stations = range(10)
import itertools
print list(itertools.chain.from_iterable([a,b]))
print list(itertools.chain.from_iterable([range(5),range(10)]))
输出:
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
请参阅 here 了解为什么是 _direction__stations
__spam 形式的任何标识符(至少两个前导下划线, 最多一个尾随下划线)在文本上替换为 类名_spam,其中类名是当前类名,去掉了前导下划线。
【讨论】:
您需要使用iter(self.__stations[1:])
,根据 OP 的代码,他们正在跳过第一项。
我会选择取消注释代码。因为他没有明确说那是他想要的。我会假设给出所有元素的更自然的实现。而且他对next
的原始实现有一个小错误。
或者,x = iter(self.__stations); next(x); return x
,如果您不想制作不必要的副本;-)【参考方案3】:
你也可以继承list
:
class Direction(list):
def __init__(self, seq=[], id_=None):
list.__init__(self,seq)
self.id = id_ if id_ else id(self)
def __iter__(self):
it=list.__iter__(self)
next(it) # skip the first...
return it
d=Direction(range(10))
print(d) # all the data, no iteration
# [0, 1, 2, 3, 4]
print (', '.join(str(e) for e in d)) # 'for e in d' is an iterator
# 1, 2, 3, 4
即,跳过第一个。
也适用于嵌套列表:
>>> d1=Direction([range(5), range(10,15), range(20,25)])
>>> d1
[range(0, 5), range(10, 15), range(20, 25)]
print(list(itertools.chain.from_iterable(d1)))
[10, 11, 12, 13, 14, 20, 21, 22, 23, 24]
【讨论】:
以上是关于如何使自定义对象可迭代?的主要内容,如果未能解决你的问题,请参考以下文章