Динамически измените данный класс в дереве наследия, замените его другим и инициализируйте его - PullRequest
1 голос
/ 08 мая 2019

Я сейчас работаю над библиотекой.

Я хотел бы написать оболочку, которая изменяет класс (Mean в примере) в дереве наследования новым классом (предположим, WindowedMean)и я хотел бы инициализировать этот класс (например, k = 10 ).

Класс Mean может находиться в любом месте дерева наследия, это всего одинпример.

Эта ссылка показывает пример

Я знаю, что это не рекомендуется.У вас есть изящный способ сделать это?

Я представляю себе, как использовать эту обертку следующим образом:


metric = Wrapper(MyClass, k=10)

Спасибо за ваше внимание к моей проблеме.

Ответы [ 2 ]

0 голосов
/ 09 мая 2019

обновление

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

Просто наследуйте класс, который вы хотите изменить, используйте обычные механизмы наследования, чтобы переопределить поведение унаследованного класса.Это означает, что установка атрибута класса 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
0 голосов
/ 08 мая 2019

Может как то так?

class Parent1(object):
    def __init__(self):
        super(Parent1, self).__init__()

    def speak(self):
        print('Iam parent 1')

class Parent2(object):
    def __init__(self):
        super(Parent2, self).__init__()

    def speak(self):
        print('Iam parent 2')

class Child():
    """docstring for Child."""
    def __init__(self):
        pass

    def child_method(self):
        print('child method')

def make_child(inhert):
    child = Child()

    class ChildInhert(Child, inhert):

        def __init__(self):
            inhert.__init__(self)

    return ChildInhert()

if __name__ == '__main__':
    child = make_child(Parent1)
    child.speak()
    child.child_method()
...