Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/316.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 自定义迭代器类与生成器?_Python_Python 3.x_Iterator_Generator - Fatal编程技术网

Python 自定义迭代器类与生成器?

Python 自定义迭代器类与生成器?,python,python-3.x,iterator,generator,Python,Python 3.x,Iterator,Generator,下面是一个例子。我想大致了解,当可以定义生成器函数时,是否有理由定义自定义迭代器类 我需要迭代一个序列,将每个元素转换为int,例如 # seq is a sequence of strings or in general anything convertible to int def f(seq): # ... g(int_iter(seq)) # iseq is a numeric sequence def g(iseq): it = iter(iseq)

下面是一个例子。我想大致了解,当可以定义生成器函数时,是否有理由定义自定义迭代器类

我需要迭代一个序列,将每个元素转换为int,例如

# seq is a sequence of strings or in general anything convertible to int
def f(seq):
    # ...
    g(int_iter(seq))

# iseq is a numeric sequence
def g(iseq):
    it = iter(iseq)
    # ...
我可以使用自定义迭代器类:

# iterator converting elements it iterates over to int
class int_iter:
    def __init__(self, iterable):
        self.it = iter(iterable)

    def __iter__(self):
        return self

    def __next__(self):
        return int(next(self.it))
class iter_str(object):

    def __init__(self, iterable):
        self._iterable = iterable
        self._iterator = self._get_iter(self._iterable)

    def __iter__(self):
        return self

    def __next__(self):
        return str(next(self._iterator))

    @staticmethod
    def _get_iter(iterable):  # a generator for forward iteration
        for element in iterable:
            yield element

test_iter = iter_str(source_list)
for element in test_iter:
    print(repr(element), end=" ")
# '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' 
或发电机功能:

def int_iter(seq):
    return (int(i) for i in seq)
这些解决方案总是可以互换的吗?
它们是否等效有效(时间和空间方面)?
从风格上看,它们中有哪一个被认为更好


谢谢

这完全取决于您对对象的期望功能。如果您只需要一个一次性iterable作为结果,它在内存方面进行了优化,而不是列表、元组等容器,那么最好的方法是使用生成器表达式。如果你想让你的对象可以多次使用,你应该使用列表理解或其他等价物(集合理解等)


如果您想要容器或生成器无法满足的更多功能,则应使用自定义对象,并将预期的功能作为不同的方法添加到类中。

如果我要将其作为答案来编写,那么让我们添加一些示例来演示其差异。假设我们有一个简单的iterable,比如:

source_list = list(range(10))  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
如果您想将其转换为字符串列表,除了前面提到的
map()
之外,还有很多其他方法可以用于此目的-您可以使用一个简单的生成器:

def gen_str(iterable):  # this is equivalent to returning an in-line generator
    for element in iterable:
        yield str(element)

test_gen = gen_str(source_list)
for element in test_gen:
    print(repr(element), end=" ")
# '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' 
也可以编写完整的迭代器类:

# iterator converting elements it iterates over to int
class int_iter:
    def __init__(self, iterable):
        self.it = iter(iterable)

    def __iter__(self):
        return self

    def __next__(self):
        return int(next(self.it))
class iter_str(object):

    def __init__(self, iterable):
        self._iterable = iterable
        self._iterator = self._get_iter(self._iterable)

    def __iter__(self):
        return self

    def __next__(self):
        return str(next(self._iterator))

    @staticmethod
    def _get_iter(iterable):  # a generator for forward iteration
        for element in iterable:
            yield element

test_iter = iter_str(source_list)
for element in test_iter:
    print(repr(element), end=" ")
# '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' 
到目前为止,它们是相同的-但是如果您想在迭代时跳过几个元素,会发生什么呢?您不能指示生成器这样做,为了跳过元素,您需要在迭代代码本身中添加按穷举法跳过逻辑,即:

test_gen = gen_str(source_list)
for element in test_gen:
    if element == "5":
        for _ in range(3):
            next(test_gen)
        continue
    print(repr(element), end=" ")
# '0' '1' '2' '3' '4' '9' 
另一方面,使用迭代器类,您可以通过添加一个简单的
skip()
方法来封装控件,如:

def skip(self, elements=1):
    for _ in range(elements):
        next(self._iterator)
然后您可以优雅地执行以下操作:

test_iter = iter_str(source_list)
for element in test_iter:
    if element == "5":
        element = test_iter.skip(3)
        continue
    print(repr(element), end=" ")
# '0' '1' '2' '3' '4' '9' 
但这只是冰山一角——如果您想在迭代中途停止生成字符串并使用原始数据,该怎么办?无法通知生成器这样做(除非您通过传递一些外部控制变量来构建它),而对迭代器类的简单更改允许您准确地执行以下操作:

class iter_str(object):

    def __init__(self, iterable, string_mode=True):
        self._iterable = iterable
        self.string_mode = string_mode
        self._iterator = self._get_iter(self._iterable)

    def __iter__(self):
        return self

    def __next__(self):
        element = next(self._iterator)
        if self.string_mode:
            return str(element)
        return element

    @staticmethod
    def _get_iter(iterable):  # a generator for forward iteration
        for element in iterable:
            yield element

test_iter = iter_str(source_list)
for element in test_iter:
    if element == "4":
        test_iter.string_mode = False
    print(repr(element), end=" ")
# '0' '1' '2' '3' '4' 5 6 7 8 9 
通过这种方式,您可以在迭代中添加任意控制,包括反转、重复迭代,甚至在迭代中途切换迭代器源等等。简单的生成器不允许您在没有重大麻烦的情况下执行任何操作

至于效率,从这个例子中可以明显看出,发电机的效率更高,因为我们依赖于内部发电机,但是,如果您需要控制iterable的生命周期,那么性能损失将很快消失,因为您必须添加更复杂的检查,并且如果试图绕过发电机限制,通常会使您的生活变得悲惨


我不会对样式发表评论,但我认为,一般来说,最好使用最好的工具来完成一项工作-如果您不需要iterable的生命周期控制,请继续使用生成器,如果需要,可以使用迭代器类。

。。。或者你也可以,你知道,使用一个专门为此设计的函数:
map(int,seq)
@zwer简单而酷。我需要检查
map
是否预先创建了完整的序列,我想避免在Python3.x中生成迭代器。在Python2.x中,它创建了一个完整的序列。但即使在Python2.3+中也有
itertools.imap
,它生成了一个映射迭代器。例如,
map()
是一种方法,一般来说,自定义迭代器类允许您控制iterable的整个生命周期,包括反转,跳过并通常添加自定义控件。生成器在这方面受到了更多的限制,因为您无法真正指示它们中途更改其行为(除非您在创建过程中向它们传递一个“控制”变量,但这有点不妥)。回答得很好,谢谢!一个小问题-为什么要使用自定义的
\u get\u iter
函数而不是内置的
self.\u iterator=iter(self.\u iterable)
?@davka-在这个特殊的例子中,实际上没有什么区别,更多的是对自我控制迭代的偏好,以及随之而来的一些额外功能。例如,如果您想要添加循环迭代,那么您所要做的就是将
\u get\u iter()
内部放入
while True
循环中,并使用它完成,而如果您使用了迭代器,则必须执行更复杂的检查。事实上,我计划在打开/关闭循环后再循环一段时间,作为一个额外的控件来显示自定义迭代器类对生成器的能力,但为了简洁起见,我决定不使用它。