python3 中的可迭代 class
Iterable class in python3
我正在尝试为 Web 资源(延迟获取的图像)实现可迭代代理。
首先,我做了(返回 id,在生产中这些将是图像缓冲区)
def iter(ids=[1,2,3]):
for id in ids:
yield id
效果很好,但现在我需要保持状态。
我读了the four ways to define iterators。我判断迭代器协议是要走的路。按照我的尝试和失败来实现它。
class Test:
def __init__(self, ids):
self.ids = ids
def __iter__(self):
return self
def __next__(self):
for id in self.ids:
yield id
raise StopIteration
test = Test([1,2,3])
for t in test:
print('new value', t)
输出:
new value <generator object Test.__next__ at 0x7f9c46ed1750>
new value <generator object Test.__next__ at 0x7f9c46ed1660>
new value <generator object Test.__next__ at 0x7f9c46ed1750>
new value <generator object Test.__next__ at 0x7f9c46ed1660>
new value <generator object Test.__next__ at 0x7f9c46ed1750>
永远。
怎么了?
非常感谢大家!这对我来说都是新的,但我正在学习新的很酷的东西。
您的 __next__
方法使用 yield
,这使它成为一个 生成器函数 。生成器函数 return 调用时生成一个新的迭代器。
但是 __next__
方法是 迭代器接口的一部分 。它本身不应该是一个迭代器。 __next__
应该 return 下一个值,而不是 return 所有值 (*).
因为你想创建一个iterable,你可以在这里制作__iter__
生成器:
class Test:
def __init__(self, ids):
self.ids = ids
def __iter__(self):
for id in self.ids:
yield id
请注意,生成器函数 不应使用 raise StopIteration
,只需 return 从函数中为您执行此操作即可。
上面的class是一个可迭代对象。 Iterables 只有 __iter__
方法, 而没有 __next__
方法 。 Iterables 在调用 __iter__
时产生一个 iterator:
Iterable -> (call __iter__
) -> Iterator
在上面的例子中,因为Test.__iter__
是一个生成器函数,我们每次调用它都会创建一个新的对象:
>>> test = Test([1,2,3])
>>> test.__iter__() # create an iterator
<generator object Test.__iter__ at 0x111e85660>
>>> test.__iter__()
<generator object Test.__iter__ at 0x111e85740>
生成器对象 是一种特定的迭代器,它是通过调用生成器函数或使用生成器表达式创建的。请注意,表示中的十六进制值不同,为两次调用创建了两个不同的对象。这是设计使然! Iterables 产生迭代器,并且可以随意创建更多。这让你可以独立地遍历它们:
>>> test_it1 = test.__iter__()
>>> test_it1.__next__()
1
>>> test_it2 = test.__iter__()
>>> test_it2.__next__()
1
>>> test_it1.__next__()
2
请注意,我在迭代器 test.__iter__()
编辑的对象 return 上调用了 __next__()
,而不是在 test
本身上调用了 __next__()
,后者没有该方法,因为它只是一个可迭代对象,不是迭代器。
迭代器也有一个__iter__
方法,它总是必须returnself
,因为它们是它们自己的迭代器。正是 __next__
方法使它们成为迭代器,__next__
的工作将被重复调用,直到引发 StopIteration
。在引发 StopIteration
之前,每次调用都应该 return 下一个值。一旦迭代器完成(引发了 StopIteration
),它就意味着总是引发 StopIteration
。迭代器只能使用一次,除非它们是无限的(永远不要引发 StopIteration
并且每次调用 __next__
时都保持产生值)。
所以这是一个迭代器:
class IteratorTest:
def __init__(self, ids):
self.ids = ids
self.nextpos = 0
def __iter__(self):
return self
def __next__(self):
if self.ids is None or self.nextpos >= len(self.ids):
# we are done
self.ids = None
raise StopIteration
value = self.ids[self.nextpos]
self.nextpos += 1
return value
这需要做更多的工作;它必须跟踪下一个要产生的值是什么,以及我们是否已经筹集到 StopIteration
。这里的其他回答者使用了看似更简单的方法,但实际上涉及让 else 完成所有艰苦的工作。当您使用 iter(self.ids)
或 (i for i in ids)
时,您正在创建一个不同的迭代器来委托 __next__
调用。这有点作弊,将迭代器的状态隐藏在 ready-made 标准库对象中。
您通常不会在 Python 代码中看到任何调用 __iter__
或 __next__
的东西,因为这两个方法只是您可以在 Python classes;如果您要在 C API 中实现迭代器,则挂钩名称会略有不同。相反,您要么使用 iter()
and next()
函数,要么只使用语法中的对象或接受可迭代对象的函数调用。
for
循环就是这样的语法。当您使用 for
循环时,Python 使用(道德等价物)调用对象上的 __iter__()
,然后对生成的迭代器对象调用 __next__()
以获取每个值。如果你 disassemble the Python bytecode:
你可以看到这个
>>> from dis import dis
>>> dis("for t in test: pass")
1 0 LOAD_NAME 0 (test)
2 GET_ITER
>> 4 FOR_ITER 4 (to 10)
6 STORE_NAME 1 (t)
8 JUMP_ABSOLUTE 4
>> 10 LOAD_CONST 0 (None)
12 RETURN_VALUE
位置2的GET_ITER
操作码调用test.__iter__()
,而FOR_ITER
在生成的迭代器上使用__next__
来保持循环(执行STORE_NAME
设置t
到下一个值,然后跳回到位置 4),直到引发 StopIteration
。一旦发生这种情况,它将跳转到位置 10 以结束循环。
如果您想更多地了解迭代器和可迭代对象之间的区别,请查看 Python 标准类型,看看当您使用 iter()
和 next()
时会发生什么他们。喜欢列表或元组:
>>> foo = (42, 81, 17, 111)
>>> next(foo) # foo is a tuple, not an iterator
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object is not an iterator
>>> t_it = iter(foo) # so use iter() to create one from the tuple
>>> t_it # here is an iterator object for our foo tuple
<tuple_iterator object at 0x111e9af70>
>>> iter(t_it) # it returns itself
<tuple_iterator object at 0x111e9af70>
>>> iter(t_it) is t_it # really, it returns itself, not a new object
True
>>> next(t_it) # we can get values from it, one by one
42
>>> next(t_it) # another one
81
>>> next(t_it) # yet another one
17
>>> next(t_it) # this is getting boring..
111
>>> next(t_it) # and now we are done
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> next(t_it) # an *stay* done
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> foo # but foo itself is still there
(42, 81, 17, 111)
您也可以使 Test
、可迭代对象 return 成为自定义迭代器 class 实例(而不是通过让生成器函数为我们创建迭代器来应对):
class Test:
def __init__(self, ids):
self.ids = ids
def __iter__(self):
return TestIterator(self)
class TestIterator:
def __init__(self, test):
self.test = test
def __iter__(self):
return self
def __next__(self):
if self.test is None or self.nextpos >= len(self.test.ids):
# we are done
self.test = None
raise StopIteration
value = self.test.ids[self.nextpos]
self.nextpos += 1
return value
这很像上面的原始 IteratorTest
class,但是 TestIterator
保留对 Test
实例的引用。 tuple_iterator
也是如此。
这里是关于命名约定的最后一个简短说明:我坚持使用 self
作为方法的第一个参数,即绑定实例。为该参数使用不同的名称只会使与其他有经验的 Python 开发人员谈论您的代码变得更加困难。不要使用 me
,无论它看起来多么可爱或简短。
(*) 除非你的目标是创建迭代器的迭代器,当然(这基本上是 itertools.groupby()
iterator 所做的,它是一个迭代器产生 (object, group_iterator)
元组,但我离题了)。
__next__
函数应该 return 迭代器提供的下一个值。由于您在实现中使用了 yield
,因此函数 return 是一个生成器,这就是您所得到的。
你需要明确你希望 Test
是可迭代对象还是迭代器。如果它是一个可迭代对象,它将能够提供一个带有 __iter__
的迭代器。如果它是一个迭代器,它将有能力提供新元素 __next__
。迭代器通常可以通过 return 在 __iter__
中自身作为可迭代对象工作。 显示了您可能想要的内容。但是,如果您想要一个示例来说明如何具体实现 __next__
(通过使 Test
明确成为迭代器),它可能是这样的:
class Test:
def __init__(self, ids):
self.ids = ids
self.idx = 0
def __iter__(self):
return self
def __next__(self):
if self.idx >= len(self.ids):
raise StopIteration
else:
self.idx += 1
return self.ids[self.idx - 1]
test = Test([1,2,3])
for t in test:
print('new value', t)
我不清楚你到底想达到什么目的,但如果你真的想像这样使用你的实例属性,你可以将输入转换为生成器,然后这样迭代它。但是,正如我所说,这感觉很奇怪,我不认为你真的想要这样的设置。
class Test:
def __init__(self, ids):
self.ids = iter(ids)
def __iter__(self):
return self
def __next__(self):
return next(self.ids)
test = Test([1,2,3])
for t in test:
print('new value', t)
最简单的解决方案是使用 __iter__
和 return 主列表的迭代器:
class Test:
def __init__(self, ids):
self.ids = ids
def __iter__(self):
return iter(self.ids)
test = Test([1,2,3])
for t in test:
print('new value', t)
作为更新,对于延迟加载,您可以 return 生成器的迭代器:
def __iter__(self):
return iter(load_file(id) for id in self.ids)
我正在尝试为 Web 资源(延迟获取的图像)实现可迭代代理。
首先,我做了(返回 id,在生产中这些将是图像缓冲区)
def iter(ids=[1,2,3]):
for id in ids:
yield id
效果很好,但现在我需要保持状态。
我读了the four ways to define iterators。我判断迭代器协议是要走的路。按照我的尝试和失败来实现它。
class Test:
def __init__(self, ids):
self.ids = ids
def __iter__(self):
return self
def __next__(self):
for id in self.ids:
yield id
raise StopIteration
test = Test([1,2,3])
for t in test:
print('new value', t)
输出:
new value <generator object Test.__next__ at 0x7f9c46ed1750>
new value <generator object Test.__next__ at 0x7f9c46ed1660>
new value <generator object Test.__next__ at 0x7f9c46ed1750>
new value <generator object Test.__next__ at 0x7f9c46ed1660>
new value <generator object Test.__next__ at 0x7f9c46ed1750>
永远。
怎么了?
非常感谢大家!这对我来说都是新的,但我正在学习新的很酷的东西。
您的 __next__
方法使用 yield
,这使它成为一个 生成器函数 。生成器函数 return 调用时生成一个新的迭代器。
但是 __next__
方法是 迭代器接口的一部分 。它本身不应该是一个迭代器。 __next__
应该 return 下一个值,而不是 return 所有值 (*).
因为你想创建一个iterable,你可以在这里制作__iter__
生成器:
class Test:
def __init__(self, ids):
self.ids = ids
def __iter__(self):
for id in self.ids:
yield id
请注意,生成器函数 不应使用 raise StopIteration
,只需 return 从函数中为您执行此操作即可。
上面的class是一个可迭代对象。 Iterables 只有 __iter__
方法, 而没有 __next__
方法 。 Iterables 在调用 __iter__
时产生一个 iterator:
Iterable -> (call __iter__
) -> Iterator
在上面的例子中,因为Test.__iter__
是一个生成器函数,我们每次调用它都会创建一个新的对象:
>>> test = Test([1,2,3])
>>> test.__iter__() # create an iterator
<generator object Test.__iter__ at 0x111e85660>
>>> test.__iter__()
<generator object Test.__iter__ at 0x111e85740>
生成器对象 是一种特定的迭代器,它是通过调用生成器函数或使用生成器表达式创建的。请注意,表示中的十六进制值不同,为两次调用创建了两个不同的对象。这是设计使然! Iterables 产生迭代器,并且可以随意创建更多。这让你可以独立地遍历它们:
>>> test_it1 = test.__iter__()
>>> test_it1.__next__()
1
>>> test_it2 = test.__iter__()
>>> test_it2.__next__()
1
>>> test_it1.__next__()
2
请注意,我在迭代器 test.__iter__()
编辑的对象 return 上调用了 __next__()
,而不是在 test
本身上调用了 __next__()
,后者没有该方法,因为它只是一个可迭代对象,不是迭代器。
迭代器也有一个__iter__
方法,它总是必须returnself
,因为它们是它们自己的迭代器。正是 __next__
方法使它们成为迭代器,__next__
的工作将被重复调用,直到引发 StopIteration
。在引发 StopIteration
之前,每次调用都应该 return 下一个值。一旦迭代器完成(引发了 StopIteration
),它就意味着总是引发 StopIteration
。迭代器只能使用一次,除非它们是无限的(永远不要引发 StopIteration
并且每次调用 __next__
时都保持产生值)。
所以这是一个迭代器:
class IteratorTest:
def __init__(self, ids):
self.ids = ids
self.nextpos = 0
def __iter__(self):
return self
def __next__(self):
if self.ids is None or self.nextpos >= len(self.ids):
# we are done
self.ids = None
raise StopIteration
value = self.ids[self.nextpos]
self.nextpos += 1
return value
这需要做更多的工作;它必须跟踪下一个要产生的值是什么,以及我们是否已经筹集到 StopIteration
。这里的其他回答者使用了看似更简单的方法,但实际上涉及让 else 完成所有艰苦的工作。当您使用 iter(self.ids)
或 (i for i in ids)
时,您正在创建一个不同的迭代器来委托 __next__
调用。这有点作弊,将迭代器的状态隐藏在 ready-made 标准库对象中。
您通常不会在 Python 代码中看到任何调用 __iter__
或 __next__
的东西,因为这两个方法只是您可以在 Python classes;如果您要在 C API 中实现迭代器,则挂钩名称会略有不同。相反,您要么使用 iter()
and next()
函数,要么只使用语法中的对象或接受可迭代对象的函数调用。
for
循环就是这样的语法。当您使用 for
循环时,Python 使用(道德等价物)调用对象上的 __iter__()
,然后对生成的迭代器对象调用 __next__()
以获取每个值。如果你 disassemble the Python bytecode:
>>> from dis import dis
>>> dis("for t in test: pass")
1 0 LOAD_NAME 0 (test)
2 GET_ITER
>> 4 FOR_ITER 4 (to 10)
6 STORE_NAME 1 (t)
8 JUMP_ABSOLUTE 4
>> 10 LOAD_CONST 0 (None)
12 RETURN_VALUE
位置2的GET_ITER
操作码调用test.__iter__()
,而FOR_ITER
在生成的迭代器上使用__next__
来保持循环(执行STORE_NAME
设置t
到下一个值,然后跳回到位置 4),直到引发 StopIteration
。一旦发生这种情况,它将跳转到位置 10 以结束循环。
如果您想更多地了解迭代器和可迭代对象之间的区别,请查看 Python 标准类型,看看当您使用 iter()
和 next()
时会发生什么他们。喜欢列表或元组:
>>> foo = (42, 81, 17, 111)
>>> next(foo) # foo is a tuple, not an iterator
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object is not an iterator
>>> t_it = iter(foo) # so use iter() to create one from the tuple
>>> t_it # here is an iterator object for our foo tuple
<tuple_iterator object at 0x111e9af70>
>>> iter(t_it) # it returns itself
<tuple_iterator object at 0x111e9af70>
>>> iter(t_it) is t_it # really, it returns itself, not a new object
True
>>> next(t_it) # we can get values from it, one by one
42
>>> next(t_it) # another one
81
>>> next(t_it) # yet another one
17
>>> next(t_it) # this is getting boring..
111
>>> next(t_it) # and now we are done
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> next(t_it) # an *stay* done
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> foo # but foo itself is still there
(42, 81, 17, 111)
您也可以使 Test
、可迭代对象 return 成为自定义迭代器 class 实例(而不是通过让生成器函数为我们创建迭代器来应对):
class Test:
def __init__(self, ids):
self.ids = ids
def __iter__(self):
return TestIterator(self)
class TestIterator:
def __init__(self, test):
self.test = test
def __iter__(self):
return self
def __next__(self):
if self.test is None or self.nextpos >= len(self.test.ids):
# we are done
self.test = None
raise StopIteration
value = self.test.ids[self.nextpos]
self.nextpos += 1
return value
这很像上面的原始 IteratorTest
class,但是 TestIterator
保留对 Test
实例的引用。 tuple_iterator
也是如此。
这里是关于命名约定的最后一个简短说明:我坚持使用 self
作为方法的第一个参数,即绑定实例。为该参数使用不同的名称只会使与其他有经验的 Python 开发人员谈论您的代码变得更加困难。不要使用 me
,无论它看起来多么可爱或简短。
(*) 除非你的目标是创建迭代器的迭代器,当然(这基本上是 itertools.groupby()
iterator 所做的,它是一个迭代器产生 (object, group_iterator)
元组,但我离题了)。
__next__
函数应该 return 迭代器提供的下一个值。由于您在实现中使用了 yield
,因此函数 return 是一个生成器,这就是您所得到的。
你需要明确你希望 Test
是可迭代对象还是迭代器。如果它是一个可迭代对象,它将能够提供一个带有 __iter__
的迭代器。如果它是一个迭代器,它将有能力提供新元素 __next__
。迭代器通常可以通过 return 在 __iter__
中自身作为可迭代对象工作。 __next__
(通过使 Test
明确成为迭代器),它可能是这样的:
class Test:
def __init__(self, ids):
self.ids = ids
self.idx = 0
def __iter__(self):
return self
def __next__(self):
if self.idx >= len(self.ids):
raise StopIteration
else:
self.idx += 1
return self.ids[self.idx - 1]
test = Test([1,2,3])
for t in test:
print('new value', t)
我不清楚你到底想达到什么目的,但如果你真的想像这样使用你的实例属性,你可以将输入转换为生成器,然后这样迭代它。但是,正如我所说,这感觉很奇怪,我不认为你真的想要这样的设置。
class Test:
def __init__(self, ids):
self.ids = iter(ids)
def __iter__(self):
return self
def __next__(self):
return next(self.ids)
test = Test([1,2,3])
for t in test:
print('new value', t)
最简单的解决方案是使用 __iter__
和 return 主列表的迭代器:
class Test:
def __init__(self, ids):
self.ids = ids
def __iter__(self):
return iter(self.ids)
test = Test([1,2,3])
for t in test:
print('new value', t)
作为更新,对于延迟加载,您可以 return 生成器的迭代器:
def __iter__(self):
return iter(load_file(id) for id in self.ids)