父母班级如何"列举"访问未设置为实例变量的参数?

时间:2018-05-17 22:39:15

标签: python python-3.x inheritance iterator

昨天我偶然发现了一个question,它涉及枚举一个可迭代的类型,并且在迭代中提升了下降的索引。

在:

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

输出:

2, a
1, b
0, c 

我决定尝试创建一个重新定义reverse的{​​{1}}子类,而不是编写一个快速可靠的答案,而不是两次应用内置i = len(letters) - i - 1,或只是enumerate。 }和__iter__方法。

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

__next__

但是,我现在意识到此代码具有冗余,因为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 的iterable参数不是除了确定整数revenumerate之外的任何东西。

我的问题是 - 存储self._len的位置以及iterable如何访问它?

使用PyCharm调试器快速查看super().__next__并没有提供很多帮助来解决这个问题(在这个阶段我似乎也是这样),而且我没有很好地遍历Python源代码库。我的猜测与父类builtins.py的{​​{1}}或__new__方法或其父__init__有关。

4 个答案:

答案 0 :(得分:4)

builtins.py是个谎言。 PyCharm成功了。如果你想查看builtins模块的真实源代码,那就是Python Git存储库中的Python/bltinmodule.cenumerate本身已在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那样将您的功能实现为生成器,更清晰,更清晰。

答案 1 :(得分:3)

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实际上是一个类,但这是它的行为。请参阅my other answer,了解其工作原理以及__next__的作用。

答案 2 :(得分:1)

在我的previous answer中,我写了我将如何做,但这里有一些关于__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]

编辑:正如评论中提到的其他人一样,迭代器应该始终实现返回__iter__的{​​{1}}(正如我在下面的示例中所做的那样)。可以在PEP-0234Python docs中阅读此要求:

  

想要成为迭代器的类应该实现两个方法:   行为与上述行为相同的self方法和next()   返回__iter__()的方法。

Iterable iterator

可迭代的迭代器?好吧,如果一个类同时实现了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__(),它使用它存储在构造函数中的迭代器。

答案 3 :(得分:1)

其他人已回答您关于代码如何工作的具体问题,因此这是使用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()返回zip对象,该对象与enumerate返回的enumerate()对象非常相似。

默认情况下,项目将从可迭代的长度开始枚举,少于一个,这要求长度是有限的。您可以提供一个起始值,从中可以倒计时,如果您只想从任意值开始计数,或者处理无限可迭代的数据,那么这将非常有用。

>>> 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()

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