如何使自定义对象可迭代?

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]          

【讨论】:

以上是关于如何使自定义对象可迭代?的主要内容,如果未能解决你的问题,请参考以下文章

使自定义 .NET 异常可序列化的正确方法是啥?

如何使自定义 UIView 可访问?

如何使用 configureWebpack 使自定义变量可用于组件?

如何使自定义部分可执行(.text 除外)

Nativescript-vue:如何使自定义组件可点击?

如何在 swift 中使自定义 UITableViewCell 中的链接可点击