Мое решение находится в конце вопроса, на основе примера Мистера Мияги
Я не знал, как лучше сформулировать название. Моя идея следующая. У меня есть абстрактный базовый класс с некоторыми реализациями. Некоторые из этих реализаций ссылаются друг на друга как на часть своей логики, упрощенной следующим образом:
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
в абстрактный метакласс, чтобы его реализациям требовался «базовый» параметр, чтобы стать классами, которые затем могут быть созданы. Затем этот параметр используется в классе для создания экземпляров других реализаций с правильной базой.
Создание экземпляра с новой функциональностью в базовом классе будет выглядеть примерно так:
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'>
X2
наследовать отX1
вместо того, чтобы создавать его экземпляр (используяsuper().f() + 'X2'
)? Таким образом можно было просто увеличитьX1
и все. - person a_guest   schedule 25.09.2018Xi
в действительности сильно различается, так что наследование их друг от друга, а не от базового класса, я думаю, не имеет смысла. Кроме того, как я уже сказал, я не хочу добавлять эту новую функциональность в семействоX
. Это новая функциональность, которую я хочу добавить к ней как к семействуY
, сохранив при этом исходное семействоX
. Я хочу сделать это для разделения функций, а также для того, чтобы иметь возможность иметь семействоZ
, которое также основано наX
, но не имеет ничего общего сY
. - person   schedule 25.09.2018class Y(X, family='Y')
. Затем вы можете соответствующим образом настроить переменные в метаклассе__new__
. - person a_guest   schedule 25.09.2018family
или почему'Y'
- это строка? - person   schedule 25.09.2018class Y2(Y, X2[Y1])
, а не украшения, то естьclass Y2(Y(X2(Y1)))
? - person MisterMiyagi   schedule 25.09.2018X
завершена и не полагается наY
, ноY
строится наX
. Я думаю, что украшение - это действительно то, что я ищу: я хочу добавить ту же функциональность к существующей группе классов (и когда классы создают экземпляры друг друга, я также хочу, чтобы эти новые экземпляры обладали новой функциональностью). Хотя я не понимаю обозначение классов в вашем комментарии? Я использую Python 3.7. - person   schedule 25.09.2018