обновление
Хотя приведенное ниже решение будет работать именно так, как вы описали, мне пришло в голову, что с множественным наследованием то, что вы просите, может произойти естественным образом.
Просто наследуйте класс, который вы хотите изменить, используйте обычные механизмы наследования, чтобы переопределить поведение унаследованного класса.Это означает, что установка атрибута класса k=10
и жесткое кодирование супер-вызовов для родительского элемента Mean
вместо использования super
, если это необходимо.
Затем просто создайте нового дочернего элемента MyClass
и добавьте переопределенный дочерний элемент Mean
в Дерево наследования.Обратите внимание, что этому подклассу MyClass не требуется ни одного оператора в его теле, и он будет вести себя точно так же, как MyClass
, за исключением того, что измененный Mean
теперь находится в нужном месте в mro.
Непосредственно в интерпретаторе (мне пришлось прибегнуть к exec
, чтобы можно было набрать всю иерархию классов в одной строке)
In [623]: exec("class GreatGrandParent1: pass\nclass GreatGrandParent2: pass\nclass GrandParent1(GreatGrandParent1, Gre
...: atGrandParent2): pass\nclass Mean: pass\nclass Parent1(GrandParent1, Mean): pass\nclass Parent2: pass\nclass
...: MyClass(Parent1, Parent2): pass")
In [624]: MyClass.__mro__
Out[624]:
(__main__.MyClass,
__main__.Parent1,
__main__.GrandParent1,
__main__.GreatGrandParent1,
__main__.GreatGrandParent2,
__main__.Mean,
__main__.Parent2,
object)
In [625]: class MeanMod(Mean):
...: k = 10
...:
In [626]: class MyClassMod(MyClass, MeanMod): pass
In [627]: MyClassMod.__mro__
Out[627]:
(__main__.MyClassMod,
__main__.MyClass,
__main__.Parent1,
__main__.GrandParent1,
__main__.GreatGrandParent1,
__main__.GreatGrandParent2,
__main__.MeanMod,
__main__.Mean,
__main__.Parent2,
object)
Обратите внимание, что есть случай, когда это не сработает просто: еслиsuper
вызов в Mean
должен был вызвать метод в Parent2
в вашем примере.В этом случае либо прибегните к исходному решению, либо используйте некоторые умные __mro__
манипуляции, чтобы пропустить метод в Mean
.
оригинальный ответ
Это выглядиткак это будет работать с классом-декоратором (который также может использоваться с этим синтаксисом «оболочки», который вы хотите).
Есть определенные угловые случаи и вещи, которые могут пойти не так - но если ваше дерево несколько хорошоМы должны рекурсивно взять все баз в классе, который вы хотите обернуть, и произвести подстановку в этих базах - мы не можем просто взять финальную базу, развернуть все классы-предки в ее __mro__
и просто замените нужный класс там: линия наследства будет разорвана ниже.
То есть, давайте предположим, что у вас есть классы A, B(A), C(B), D(C)
, и вы хотите клон D2
из D
,замена B
на B2(A)
- D.__bases__
равна C
.D.__mro__
is (D, C, B, A, object)
Если мы попытаемся создать новый D2
, в котором __mro__
будет равен (D, C, B2, A, object)
, класс C
сломается, поскольку он больше не увидит B
.(И код в предыдущей версии этого ответа оставил бы и B и B2 в строке наследования, что привело бы к дальнейшему посредничеству).Приведенное ниже решение воссоздает не только новый класс B
, но и новый C
.
Просто имейте в виду, что если B2
не будет наследоваться от A
, в этом же примере A
Сам по себе будет удален из __mro__
для нового, замененный B2 не нуждается в этом.Если D
обладает какой-либо функциональностью, которая зависит от A
, он сломается.Это нелегко исправить, но для установки проверочных механизмов, обеспечивающих, чтобы заменяемый класс содержал тех же предков, которые имел замененный класс, и, в противном случае, выдает ошибку - это было бы легко сделать.Но выяснить, как выбрать и включить «пропавших без вести» предков, нелегко, так как невозможно понять, нужны ли они вообще, просто для начала.
Что касается части конфигурирования, без какого-либо примера того, как ваш класс RollingMean
"сконфигурирован", трудно привести надлежащий конкретный пример - но давайте сделаем его подкласс, обновив его dict с переданнымпараметры - это должно быть для любой необходимой конфигурации.
from types import new_class
def norm_name(config):
return "_" + "__".join(f"{key}_{value}" for key, value in config.items())
def ancestor_replace(oldclass: type, newclass: type, config: dict):
substitutions = {}
configured_newclass = type(newclass.__name__ + norm_name(config), (newclass,), config)
def replaced_factory(cls):
if cls in substitutions:
return substitutions[cls]
bases = cls.__bases__
new_bases = []
for base in bases:
if base is oldclass:
new_bases.append(configured_newclass)
else:
new_bases.append(replaced_factory(base))
if new_bases != bases:
new_cls = new_class(cls.__name__, tuple(new_bases), exec_body=lambda ns: ns.update(cls.__dict__.copy()))
substitutions[cls] = new_cls
return new_cls
return cls
return replaced_factory
# canbe used as:
#MyNewClass = ancestor_replace(Mean, RollingMean, {"ks": 10})(MyClass)
Я позабочусь о том, чтобы получить правильный метакласс - если ни один класс в вашем дереве наследования не использует метакласс, отличный от type
(обычно это ABCили модели ORM имеют разные метаклассы), вы можете использовать type
вместо types.new_class
в вызове.
И, наконец, пример использования этого в интерактивной подсказке:
In [152]: class A:
...: pass
...:
...: class B(A):
...: pass
...:
...: class B2(A):
...: pass
...:
...: class C(B):
...: pass
...:
...: class D(B):
...: pass
...:
...: class E(D):
...: pass
...:
In [153]: E2 = ancestor_replace(B, B2, {"k": 20})(E)
In [154]: E.__mro__
Out[154]: (__main__.E, __main__.D, __main__.B, __main__.A, object)
In [155]: E2.__mro__
Out[155]:
(__main__.E_modified,
__main__.D_modified,
__main__.B2_k_20,
__main__.B2,
__main__.A,
object)
In [156]: E2.k
Out[156]: 20