具有类型和超级的动态继承

dynamic inheritance with type and super

我正在寻找一种动态继承父 class 及其属性和方法的方法,方法是使用 type 创建 class 并使用 super 继承,像这样:

class A:
    def __init__(self,a,b):
        self.a = a
        self.b = b
    
    def some_method(self,q):
        return (self.a + self.b)**q

def B_init(self,**kwargs):
    super().__init__(**kwargs)

def another_method(self,):
    return 1

def class_B_factory(parent_class):
    return type(
        'B',
        (parent_class, some_other_parent_class),
        {'__init__':B_init,
         'another_method':another_method
        }
               )

然后可以调用...

model = class_B_factory(A)(a = 1, b = 5)
print(model.some_method(2)) # outputs to (1 + 5)**2 = 36

我不确定如何进行。我认为我不需要自定义 metaclass 因为我很确定你不能在创建 self 的同时调用父 class' __init__ 方法进行中。我还尝试覆盖 class_B_factory 范围之外的默认 __init__ 方法,如下所示:


def class_B_factory(parent_class):
    return type(
        'B',
        (parent_class, some_other_parent_class),
        {'another_method':another_method
        }
               )

B = class_B_factory(A)

def B_init(self,**kwargs):
    super(B,self).__init__(**kwargs)

B.__init__ = B_init

model = B(a = 1, b = 5)

因为我认为 type 不需要 __init__ 马上,因为它只在实例化期间需要。但是后来我得到 TypeError: __init__() got an unexpected keyword argument 错误,这似乎没有用,而且它也不干净。

编辑:我尝试通过以下方法在工厂外部定义方法,但仍然没有成功。不知道如何解决它。 Python 实例化可能有问题?

class A:
    ...

def B_init(self, produced_class = None, **kwargs):
    super(produced_class,self).__init__(**kwargs)

def another_method(self, q, parent_class = None):
    if parent_class is not None:
        return 3 * parent_class.some_method(self,q) # I expect any parent_class passed to have a method called some_method
    return 1

def class_B_factory(parent_class, additional_methods):
    methods = {}
    for name, method in additional_methods.items():
        if "parent_class" in signature(method).parameters:
            method = partial(method, parent_class = parent_class) # freeze the parent_class argument, which is a cool feature
        methods[name] = method
    
    newcls = type(
            'B',
            (parent_class,),
            methods # would not contain B_init
                 )
    
    newcls.__init__ = partial(B_init, produced_class = newcls) # freeze the produced class that I am trying to fabricate into B_init here
    
    return newcls


model = class_B_factory(parent_class = A, additional_methods = {"another_method": another_method})

print(signature(model.__init__).parameters) # displays OrderedDict([('self', <Parameter "self">),...]) so it contains self!

some_instance_of_model = model(a = 1, b = 5) # throws TypeError: B_init() missing 1 required positional argument: 'self'

super() 的无参数形式依赖于将其物理放置在 class 主体内 - Python 机器将在引擎盖下创建一个 __class__引用“物理”class(大致相当于非局部变量)的单元格变量,并将其作为 super() 调用中的第一个参数。

对于未在 class 语句中编写的方法,必须求助于显式将参数放置到 super,这些是子 class 和实例 (self) .

在代码中更简单的方法是在工厂函数中定义方法,这样它们就可以共享一个非局部变量,其中包含在超级调用中新创建的 class:



def class_B_factory(parent_class):

    def B_init(self,**kwargs):
        nonlocal newcls  # <- a bit redundant, but shows how it is used here
        ​super(newcls, self).__init__(**kwargs)

    def another_method(self,):
        ​​return 1

   ​ newcls = type(
       ​'B',
       ​(parent_class, some_other_parent_class),
       ​{'__init__':B_init,
        ​'another_method':another_method
       ​}
    return newcls

如果您必须在工厂函数之外定义方法(这很可能),您必须以某种形式将父 class 传递给它们。最直接的方法是添加命名参数(例如 __class__ 或“parent_class”),并在工厂内部使用 functools.partial 将 parent_class 传递给所有方法懒惰的方式:


from functools import partial
from inspect import signature

class A:
    ...


# the "parent_class" argument name is given a special treatement in the factory function:
def B_init(self, *, parent_class=None, **kwargs):
    nonlocal newcls  # <- a bit redundant, but shows how it is used here
    ​super([parent_class, self).__init__(**kwargs)

def another_method(self,):
    ​​return 1


def class_B_factory(parent_class, additional_methods, ...):
    methods = {}
    for name, method in additional_methods.items():
        if "parent_class" in signature(method).parameters:
            method = partial(method, parent_class=parent_class)
        # we populate another dict instead of replacing methods
        # so that we create a copy and don't modify the dict at the calling place.
        methods[name] = method
    

   ​ newcls = type(
       ​'B',
       ​(parent_class, some_other_parent_class),
       methods
    )
    return newcls

new_cls = class_B_factory(B, {"__init__": B_init, "another_method": another_method})