Как родительский класс «перечисляет» доступ к аргументу, не заданному в качестве переменной экземпляра? - PullRequest
0 голосов
/ 18 мая 2018

Вчера я наткнулся на вопрос , который включал в себя перечисление по итерируемому типу и получение нисходящих индексов, сопровождаемых восходящими элементами в итерируемом.

In:

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

Out:

2, a
1, b
0, c 

Вместо того, чтобы писать быстрый и надежный ответ, дважды используйте встроенный reverse или просто 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

Теперь кажется, что итеративный аргумент, переданный в revenumerate, не предназначен ни для чего, кроме определения целого числа self._len.

Мой вопрос - , где iterable хранится и как super().__next__ получает к нему доступ?

Быстрый просмотр builtins.py с помощью отладчика PyCharm не поможет в выяснении этого (или мне так кажется наэтот этап), и я не очень хорошо знаком с хранилищем исходного кода Python.Я думаю, что-то делать с методом __new__ или __init__ родительского класса enumerate, или его родитель object.

Ответы [ 4 ]

0 голосов
/ 18 мая 2018

Другие ответили на ваш конкретный вопрос о том, как работает ваш код, так что вот еще один способ реализовать обратный перечислитель, используя 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()

Что мешает переводчику войти в бесконечный цикл. Вы можете обработать исключение и вызвать одно из них, если это подходит для вашего приложения.

0 голосов
/ 18 мая 2018

builtins.py это ложь.PyCharm сделал это.Если вы хотите взглянуть на реальный исходный код для модуля builtins, это Python/bltinmodule.c в репозитории Python Git.Сам enumerate реализован в Objects/enumobject.c.

enumerate итераторы хранят итератор над своим базовым объектом в слоте структуры en_sit уровня C:

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, становится все яснее и яснее.

0 голосов
/ 18 мая 2018

В моем предыдущем ответе я написал, как я это сделаю, но вот несколько ответов на то, что на самом деле спросили о __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__, который возвращает self (как я делал в примерах ниже).Это требование можно прочитать в PEP-0234 и в Документах Python :

Класс, который хочет быть итератором, должен реализовывать два метода: a *Метод 1041 *, который ведет себя так, как описано выше, и метод __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__ здесь, который использует итератор, который он сохранил в конструкторе.

0 голосов
/ 18 мая 2018

То, что делает 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__.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...