Как динамически наследовать во время инициализации? - PullRequest
0 голосов
/ 26 апреля 2018

У меня следующая структура класса:

class Base:
  def z(self):
    raise NotImplementedError()

class A(Base):
  def z(self):
    self._x()
    return self._z()

  def _x(self):
    # do stuff

  def _a(self):
    raise NotImplementedError()

class B(Base)
  def z(self):
    self._x()
    return self._z()

  def _x(self):
    # do stuff

  def _z(self):
    raise NotImplementedError()


class C(A):
  def _z(self):
    print(5)

class D(B):
  def _z(self):
    print(5)

Реализация C(A) и D(B) точно такая же, и на самом деле все равно, от какого класса она наследуется. Концептуальное различие заключается только в A и B (и их необходимо хранить как отдельные классы). Вместо того, чтобы писать отдельные определения для C и D, я хочу иметь возможность динамически наследовать от A или B на основе аргумента, предоставленного во время создания экземпляра из C / D (в конечном итоге C и D должны совпадать).

Кажется, что метаклассы могут работать, но я не уверен, как передать аргумент __init__ метаклассу __new__ (и действительно ли это будет работать). Я действительно предпочел бы решение, которое решает проблему внутри класса.

1 Ответ

0 голосов
/ 26 апреля 2018

Рассматривали ли вы использование композиции вместо наследования? Кажется, что это гораздо больше подходит для этого варианта использования. Подробности смотрите в нижней части ответа.

В любом случае,

  • class C(A): ......... class C(B): ..... даже недействительно, и в результате будет определено только class C(B).

  • Я не уверен, что метакласс сможет вам здесь помочь. Я считаю, что лучшим способом было бы использовать type, но я бы хотел, чтобы его исправили.

Решение, использующее type (и, вероятно, неправильное использование locals(), но здесь дело не в этом)

class A:
    def __init__(self):
        print('Inherited from A')

class B:
    def __init__(self):
        print('Inherited from B')

class_to_inherit = input()  # 'A' or 'B"

C = type('C', (locals()[class_to_inherit],), {})

C()

'A' or 'B'
>> A
Inherited from A

'A' or 'B'
>> B
Inherited from B


Состав

Возвращаясь к вопросу в начале моего ответа, вы заявляете, что реализации как "C(A)", так и "C(B)" идентичны, и на самом деле им нет дела до A или * 1033. *. Мне кажется более правильным использовать композицию. Тогда вы можете сделать что-то вроде:

class A: pass

class B: pass

class C:
    def __init__(self, obj):  # obj is either A or B instance, or A or B themselves
        self.obj = obj  # or self.obj = obj() if obj is A or B themselves

c = C(A())  #  or c = C(A) 

В случае, если C должен предоставлять тот же API, что и A или B, C может перезаписать __getattr__:

class A:
    def foo(self):
        print('foo')

class C:
    def __init__(self, obj):
        self.obj = obj

    def __getattr__(self, item):
        return getattr(self.obj, item)

C(A()).foo()
# foo
...