Python 自定义迭代器类与生成器?
下面是一个例子。我想大致了解,当可以定义生成器函数时,是否有理由定义自定义迭代器类 我需要迭代一个序列,将每个元素转换为int,例如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)
# 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
循环中,并使用它完成,而如果您使用了迭代器,则必须执行更复杂的检查。事实上,我计划在打开/关闭循环后再循环一段时间,作为一个额外的控件来显示自定义迭代器类对生成器的能力,但为了简洁起见,我决定不使用它。