以下是插图。我想一般地理解在可以定义生成器函数时是否有理由定义自定义迭代器类。
我需要迭代一个将每个元素转换为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))
或生成器功能:
def int_iter(seq):
return (int(i) for i in seq)
这些解决方案是否总是可以互换的? 它们是否等效(时间和空间)? 在风格上,他们中的任何一个被认为更好吗?
谢谢!
答案 0 :(得分:2)
如果我要将其作为答案编写,那么让我们添加一些示例来演示差异。假设我们有一个简单的迭代:
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'
或者你可以写一个完整的迭代器类:
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,继续使用生成器,如果你这样做 - 迭代器类是一种可行的方法。
答案 1 :(得分:1)
这一切都取决于您对对象的期望功能。如果你只想要一次性迭代作为结果,这是在内存方面优化而不是像列表,元组等容器,最好的方法是使用生成器表达式。如果你希望你的对象可以多次迭代,你应该使用列表推导或其他等价物(设置理解等)。
如果您想要容器或生成器无法满足的更多功能,您应该使用自定义对象并将您期望的功能作为不同的方法添加到类中。