父类"enumerate"如何访问未设置为实例变量的参数?



我昨天偶然发现了一个问题,它涉及枚举可迭代类型,并随着可迭代中的升序项目而大声下降索引。

在:

letters = ['a', 'b', 'c']
for i, letter in revenumerate(letters):
print('{}, {}'.format(i, letter))

外:

2, a
1, b
0, c 

与其两次应用内置reverse编写快速可靠的答案,或者简单地i = len(letters) - i - 1,我决定尝试创建一个重新定义__iter____next__方法的子enumerate类。

我原始工作解决方案的代码如下:

class revenumerate(enumerate):
def __init__(self, iterable, start=0):
self._len = len(iterable)
self._start = start
if isinstance(iterable, dict):
self._data = iterable.keys()
else:
self._data = iterable
def __iter__(self):
_i = self._len
for _item in self._data:
_i -= 1
_ind = _i + self._start
yield _ind, _item
def __next__(self):
_i, _item = super().__next__()
_ind = self._len +  2 * self._start - _i - 1
return _ind, _item

但是,我现在意识到这段代码具有冗余性,因为enumerate.__iter__似乎会产生__next__的结果,这是有道理的。 删除重新定义的__iter__后,我意识到self._data没有在任何地方使用,所以我从__init__中删除了最后四行,留下了以下代码,它仍然提供了所需的行为。

class revenumerate(enumerate):
def __init__(self, iterable, start=0):
self._len = len(iterable)
self._start = start
def __next__(self):
_i, _item = super().__next__()
_ind = self._len +  2 * self._start - _i - 1
return _ind, _item

现在看来,传递给revenumerate的可迭代参数除了确定整数self._len之外没有任何用处。

我的问题是-iterable存储在哪里,super().__next__如何访问它?

快速浏览一下 PyCharm 调试器builtins.py并不能为解决这个问题提供很多帮助(或者在这个阶段在我看来是这样),而且我对 Python 源代码存储库没有很好的了解。 我的猜测与父类enumerate__new____init__方法有关,或者它的父object

builtins.py是谎言。PyCharm编造了它。如果你想查看builtins模块的真实源代码,那Python/bltinmodule.c在Python Git存储库中。enumerate本身是在Objects/enumobject.c中实现的。

enumerate迭代器将其基础对象上的迭代器存储在 C 级en_sit结构槽中:

typedef struct {
PyObject_HEAD
Py_ssize_t en_index;           /* current index of enumeration */
PyObject* en_sit;          /* secondary iterator of enumeration */
PyObject* en_result;           /* result tuple  */
PyObject* en_longindex;        /* index for sequences >= PY_SSIZE_T_MAX */
} enumobject;

设置在enumerate.__new__

static PyObject *
enum_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
...
en->en_sit = PyObject_GetIter(seq);

它设置在__new__的事实就是为什么即使您忘记打电话super().__init__,它仍然有效。


为此enumerate子类化没有多大意义。enumerate仅记录为可调用对象;它是一个类并支持子类化的事实是一个实现细节。此外,你并没有从enumerate中得到很多用处,你的迭代器和enumerate迭代器之间的关系听起来并不像"is-a"。像 zvone 那样将功能实现为生成器更干净、更清晰。

enumerate所做的或多或少是*这个:

def enumerate(iterable):
counter = 0
for item in iterable:
counter += 1
yield counter, item

您可以注意到的一件事是,它不知道可迭代对象有多长。它甚至可以无限长,但枚举仍然有效。

revenumerate的问题在于,您首先必须计算有多少项才能生成第一个项,因此实际上必须创建所有枚举项的列表,然后向后生成它们(至少如果您希望revenumerate与任何可迭代对象一起使用,例如enumerate)。

一旦你接受这个限制是不可避免的,剩下的就很简单了:

def revenumerate(iterable):
all_items = list(iterable)
counter = len(all_items)
for item in reversed(all_items):
counter -= 1
yield counter, item

(*)enumerate实际上是一个类,但这是它的行为。请参阅我关于其工作原理以及__next__的作用的另一个答案。

其他人已经回答了您关于代码如何工作的具体问题,因此这是另一种使用zip()实现反向枚举器的方法:

def revenumerate(iterable, start=None):
if start is None:
start = len(iterable) - 1
return zip(range(start, -1, -1), iterable)
>>> revenumerate('abcdefg')
<zip object at 0x7f9a5746ec48>
>>> list(revenumerate('abcdefg'))
[(6, 'a'), (5, 'b'), (4, 'c'), (3, 'd'), (2, 'e'), (1, 'f'), (0, 'g')]
>>> list(revenumerate('abcdefg', 100))
[(100, 'a'), (99, 'b'), (98, 'c'), (97, 'd'), (96, 'e'), (95, 'f'), (94, 'g')]

revenumerate()返回一个与enumerate()返回的enumerate对象非常相似的zip对象。

默认情况下,将从可迭代的 less one 的长度开始枚举项,这要求长度是有限的。您可以提供一个开始倒计时的值,如果您只想从任意值开始计数,或者处理无限迭代对象,这将很有用。

>>> from itertools import count
>>> g = revenumerate(count(), 1000)
>>> next(g)
(1000, 0)
>>> next(g)
(999, 1)
>>> next(g)
(998, 2)
>>> next(g)
(997, 3)
>>> next(g)
(996, 4)

如果您尝试在不指定起始值的情况下处理无限迭代对象:

>>>> revenumerate(count())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in revenumerate
TypeError: object of type 'itertools.count' has no len()

这可以防止解释器进入无限循环。您可以处理异常,如果适合您的应用程序,您可以提出自己的异常。

在我之前的回答中,我写了我将如何做,但这里有一些关于__iter____next__实际被问到的答案......

可迭代

为了使对象可迭代,它必须实现方法__iter__,该方法必须返回一个迭代器。

以下是一些简单的例子:

class A:
def __iter__(self):
return iter([1, 2, 3])
class B:
def __iter__(self):
yield 'a'
yield 'b'

这些可以迭代:

>>> A().__iter__()
<list_iterator object at 0x00000000029EFD30>
>>> iter(A())  # calls A().__iter__()
<list_iterator object at 0x00000000029EFF28>
>>> list(A())  # calls iter(A) and iterates over it
[1, 2, 3]
>>> list(B())  # calls iter(B) and iterates over it
['a', 'b']

迭 代

__iter__返回的对象是一个迭代器。迭代器必须实现__next__方法。

例如:

>>> it = iter(B())  # iterator
>>> it.__next__()
'a'
>>> next(it)  # calls it.__next__()
'b'
>>> next(it)  # raises StopIteration because there is nothing more
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

自定义迭代器

class MyIterator:
def __init__(self):
self.value = 5
def __next__(self):
if self.value > 0:
self.value -= 1
return self.value
else:
raise StopIteration()
class MyIterable:
def __iter__(self):
return MyIterator()
>>> list(MyIterable())
[4, 3, 2, 1, 0]

编辑:正如评论中提到的其他人,迭代器应该始终实现返回self__iter__(就像我在下面的示例中所做的那样)。此要求可以在 PEP-0234 和 Python 文档中阅读:

想要成为迭代器的类应该实现两个方法:一个next()如上所述的方法,以及一个__iter__()返回self的方法。

可迭代迭代器

可迭代迭代器?好吧,如果一个类同时实现了__iter____next__,那么它两者都是:

class IterableIterator:
def __init__(self):
self.value = 11
def __next__(self):
if self.value < 17:
self.value += 1
return self.value
else:
raise StopIteration()
def __iter__(self):
return self
>>> list(IterableIterator())
[12, 13, 14, 15, 16, 17]

enumerate

enumerate实际上做了这样的事情:

class enumerate:
def __init__(self, iterable, start=0):
self.iterator = iter(iterable)
self.n = start - 1
def __iter__(self):
return self
def __next__(self):
self.n += 1
next_item = next(self.iterator)
return self.n, next_item

因此,为了回答您的问题,在您的super().__next__()中,您在此处调用此__next__,它使用它存储在构造函数中的迭代器。

最新更新