Подклассификация и переопределение функции генератора в python

Мне нужно переопределить метод родительского класса, который является генератором, и я задаюсь вопросом, как правильно это сделать. Есть ли что-то неправильное в следующем или более эффективном?

class A:
    def gen(self):
        yield 1
        yield 2

class B(A):
    def gen(self):
        yield 3
        for n in super().gen():
            yield n

Ответы

Ответ 1

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

import itertools

class B(A):
    def gen(self):
        return itertools.chain([3], super().gen())

Лучший подход зависит от того, что вы делаете; вышесказанное выглядит излишне сложным, но я бы не хотел обобщать такой простой пример.

Ответ 2

Для Python 3.3 и выше лучший, самый общий способ сделать это:

class A:
    def gen(self):
        yield 1
        yield 2

class B(A):
    def gen(self):
        yield 3
        yield from super().gen()

Здесь используется новый синтаксис yield from для делегирования подгенератора. Это лучше, чем другие решения, потому что он фактически передает управление генератору, которому он делегирует; если указанный генератор поддерживает .send и .throw для передачи значений и исключений в генератор, то делегирование означает, что он фактически принимает значения; явным образом циклически и yield поочередно получат значения в обертке gen, а не генератор, фактически создающий значения, и та же проблема относится к другим решениям, таким как использование itertools.chain.

Ответ 3

Чтобы вызвать метод из подкласса, вам понадобится ключевое слово super.

Новый исходный код:

class B(A):
    def gen(self):
        yield 3
        for n in super().gen():
            yield n

Это:

b = B()
for i in b.gen():
     print(i)

выводит результат:

   3
   1
   2

В первой итерации ваш генератор останавливается на "3", для следующих итераций он просто продолжается, как обычно будет суперкласс.

Этот вопрос дает действительно хорошее и продолжительное объяснение генераторов, итераторов и ключевого слова yield: Что означает "yield" ключевое слово do в Python?

Ответ 4

Ваш код правильный.
Вернее, я не вижу в этом проблемы, и она, по-видимому, работает правильно.

Единственное, о чем я могу думать, это следующее.

.

Постскриптум

Для классов нового стиля см. другие ответы, в которых используется super()
Но super() работает только для классов нового стиля В любом случае, этот ответ может быть полезен, по крайней мере, но только для классических классов.

.

Когда интерпретатор приходит к команде for n in A.gen(self):, он должен найти функцию A.gen.

Обозначение A.gen не означает, что объект A.gen находится внутри объекта A.
Объект A.gen является SOMEWHERE в памяти, и интерпретатор будет знать, где его найти, получив необходимую информацию (адрес) из A.__dict__['gen'], в которой A.__dict__ - пространство имен А.
Итак, поиск объекта функции A.gen в памяти требует поиска в A.__ dict __

Но для выполнения этого поиска интерпретатор должен сначала найти объект A.
Итак, когда он прибывает в команду for n in A.gen(self):, он сначала ищет, если идентификатор A входит в число локальных идентификаторов, то есть он ищет строку "A" в локальном пространстве имен функции (из которой я не знаю названия).
Поскольку это не так, интерпретатор выходит за пределы функции и ищет этот идентификатор на уровне модуля, в глобальном пространстве имен (который globals())

В этот момент может быть, что глобальное пространство имен будет содержать сотни или тысячи имен атрибутов, среди которых можно выполнить поиск для "A".

Однако A имеет очень мало атрибутов: ключи __dict__ - это только _ _ ​​модуль _ ',' gen 'и' _ doc _ '(чтобы увидеть это, сделайте print A.__dict__)
Поэтому было бы очень жаль, что небольшой поиск строки 'gen' в A._dict _ должен выполняться после поиска среди сотен элементов в пространстве имен словарей globals() уровня модуля.

.

Вот почему я предлагаю другой способ заставить интерпретатора найти функцию A.gen

class A:
    def gen(self):
        yield 1
        yield 2

class BB(A):
    def gen(self):
        yield 3
        for n in self.__class__.__bases__[0].gen(self):
            yield n


bb = BB()
print list(bb.gen())  # prints [3, 1, 2]

self._class _ - это класс, из которого был создан экземпляр, т.е. он Bu

self._class _._ bases _ - это кортеж, содержащий базовые классы Bu
В настоящее время в этом кортеже есть только один элемент, поэтому self._class _._ bases_ [0] A

__class__ и __bases__ - это имена специальных атрибутов, которые не указаны в _dict _;
Фактически _class _, _bases _ и _dict _ являются специальными атрибутами аналогичного характера, они являются атрибутами, предоставляемыми Python, см.:
http://www.cafepy.com/article/python_attributes_and_methods/python_attributes_and_methods.html

.

Ну, я имею в виду, что в конце концов, есть несколько элементов в self._class _ и в self._class _._ bases _, поэтому разумно что последующие поиски в этих объектах, чтобы наконец найти способ доступа к A.gen, будут быстрее, чем поиск в "gen" в глобальном пространстве имен, если этот содержит сотни элементов.

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

.

Изменить

Вы можете получить то же, что и ваш код, с более сжатой инструкцией

class A:
    def gen(self):
        yield 1
        yield 2

class Bu(A):
    def gen(self):
        yield 3
        for n in A.gen(self):
            yield n

b = Bu()
print 'list(b.gen()) ==',list(b.gen())

from itertools import chain
w = chain(iter((3,)),xrange(1,3))
print 'list(w)       ==',list(w)

производит

list(b.gen()) == [3, 1, 2]
list(w)       == [3, 1, 2]