Мое решение находится в нижней части вопроса, основываясь на примере Мистера Мияги
Я не был уверен, как лучше сформулировать заголовок.Моя идея заключается в следующем.У меня есть абстрактный базовый класс с некоторыми реализациями.Некоторые из этих реализаций ссылаются друг на друга как на часть своей логики, упрощенную следующим образом:
import abc
# the abstract class
class X(abc.ABC):
@abc.abstractmethod
def f(self):
raise NotImplementedError()
# first implementation
class X1(X):
def f(self):
return 'X1'
# second implementation, using instance of first implementation
class X2(X):
def __init__(self):
self.x1 = X1()
def f(self):
return self.x1.f() + 'X2'
# demonstration
x = X2()
print(x.f()) # X1X2
print(x.x1.f()) # X1
Теперь я хочу использовать эти классы где-нибудь, скажем, в другом модуле.Однако я хочу добавить некоторые дополнительные функции (например, функцию g
) ко всем классам в иерархии.Я мог бы сделать это, добавив его в базовый класс X
, но я хочу сохранить функциональность, определенную отдельно.Например, я мог бы хотеть определить новую функциональность как это:
class Y(X):
def g(self):
return self.f() + 'Y1'
Это создает другой базовый класс с новой функциональностью, но, конечно, не добавляет его к существующим реализациям X1
и X2
.Я должен был бы использовать наследование алмазов, чтобы получить это:
class Y1(X1, Y):
pass
class Y2(X2, Y):
pass
# now I can do this:
y = Y2()
print(y.g()) # X1X2Y1
Выше работает правильно, но все еще есть проблема.В X2.__init__
создается экземпляр X1
.Для моей идеи работы это должно было бы стать Y1
в Y2.__init__
.Но это, конечно, не тот случай:
print(y.x1.g()) # AttributeError: 'X1' object has no attribute 'g'
Я думаю, что я мог бы найти способ превратить X
в абстрактный метакласс, чтобы его реализации требовался параметр 'base' длястать классами, которые затем могут быть созданы.Этот параметр затем используется в классе для создания экземпляров других реализаций с правильной базой.
Создание экземпляра с новой функциональностью в базовом классе будет выглядеть примерно так:
class Y:
def g(self):
return self.f() + 'Y1'
X2(Y)()
Что приведет к созданию объекта, эквивалентного экземпляру следующего класса:
class X2_with_Y:
def __init__(self):
self.x1 = X1(Y)()
def f(self):
return self.x1.f() + 'X2'
def g(self):
return self.f() + 'Y1'
Однако я не знаю, как создать метакласс, который бы это делал.Я хотел бы услышать, является ли метакласс правильной идеей и, если да, как это сделать.
Решение
Используя пример Мистера Мияги, я былв состоянии получить то, что, я думаю, сработает.Поведение близко к идее метакласса, которое у меня было.
import abc
class X(abc.ABC):
base = object # default base class
@classmethod
def __class_getitem__(cls, base):
if cls.base == base:
# makes normal use of class possible
return cls
else:
# construct a new type using the given base class and also remember the attribute for future instantiations
name = f'{cls.__name__}[{base.__name__}]'
return type(name, (base, cls), {'base': base})
@abc.abstractmethod
def f(self):
raise NotImplementedError()
class X1(X):
def f(self):
return 'X1'
class X2(X):
def __init__(self):
# use the attribute here to get the right base for the other subclass
self.x1 = X1[self.base]()
def f(self):
return self.x1.f() + 'X2'
# just the wanted new functionality
class Y(X):
def g(self):
return self.f() + 'Y1'
Использование выглядит так:
# demonstration
y = X2[Y]()
print(y.g()) # X1X2Y1
print(y.x1.g()) # X1Y1
print(type(y)) # <class 'abc.X2[Y]'>
# little peeve here: why is it not '__main__.X2[Y]'?
# the existing functionality also still works
x = X2()
print(x.f()) # X1X2
print(x.x1.f()) # X1
print(type(x)) # <class '__main__.X2'>