首页 > 解决方案 > 在python中创建一个类实例,其基类由参数动态指定

问题描述

我需要做这样的事情:

class Base1:
   def __init__(self, uniform_params):
       pass

class Base2:
   def __init__(self, uniform_params):
       pass

class DynamicDerive(self, dynamicspec, uniform_params):
     kls = dynamicspec.kls
     kls.__init__self, uniform_params)

spec = SomeSpecificationObject()
spec.kls = Base1

x = DynamicDerive(spec, uniform_params)

spec2 = SomeSpecificationObject()
spec2.kls = Base2

y = DynamicDerive(spec2, some_more_uniform_params)

Base1 和 Base2 的参数一致且一致。要求是在创建实例时传入 DynamicDerive 要从中派生的类。

替代方案是“简单的”:创建 DynamicDerive1(Base1)、DynamicDerive2(Base2),但不幸的是:

  1. DynamicDerive 类在数百个地方使用。
  2. 在这里,无法预测未来用户会传入什么。用户可以创建 Base3、Base4 等。

因此,创建数百个相同类的整个片段的剪切/粘贴选项只是更改基类的名称,这不是一种选择。

假设这可以通过“重定向”API来解决,其中一个特殊的类会这样做:

class Base:
    def __init__(self, redirectorkls, uniform_args):
        self.redir = redirectorkls(uniformargs)
    def fn1(self, *args, **kwargs):
        return self.redir.fn1(*args, **kwargs)
    def fn2(self, *args, **kwargs):
        return self.redir.fn2(*args, **kwargs)
    ...
    ...

然而,虽然它会起作用,但这完全违背了练习的目的。必须有一种涉及元编程(元类)的方法来做到这一点。

不幸的是,查找元类编程教程都显示了如何从构造函数外部创建类,而上面需要的是在构造函数内部创建元类。

有人有任何线索吗?

[更新] - 然后我需要能够进一步从 DynamicDerive派生。GreenCloakGuy 通过提供一个可以完成任务的函数来很好地回答,但是不可能从函数派生类。

class DerivedFromDynamicDerive(DynamicDerive):
    def __init__(self, dynamicspec, nonuniformparams, uniform_params):
        self.nonuniformparams = nonuniformparams
        DynamicDerive.__init__(self, dynamicspec, uniform_params)

(注意:因为这是实际的自由代码,所以需要的地方在这里: https ://git.libre-riscv.org/?p=ieee754fpu.git;a=blob;f=src/ieee754/fpadd/ addstages.py;h=2bc23df0dabf89f8a4e194d5e573a88d5d740d0e;hb=78cbe8c5131a84426a3cad4b0b3ed4ab7da49844#l19

SimpleHandShake 需要在大约 40 个位置进行动态替换,此 IEEE754 兼容 RTL 的用户可以指定他们希望使用的类。这只是需要此功能的 50 多个类中的一个)。

标签: pythonmetaprogrammingmetaclass

解决方案


如果您可以只使用多重继承,并且将参数配置为 Mixin 类,那么这件事会更干净——以至于在创建类时不需要特殊的元类或操作。

而且,当然,如果一个人既不需要issubclass检查也不需要 的子类DynamicDerive,一个工厂函数,它将接受基础,将注册表保留为缓存,并且只实例化新对象也不需要任何特殊的代码。

但是,如果您需要 MRO 中的参数基础比“DerivedClass”更高,正如您所要求的,那么在类实例化时自定义实例类的__call__方法是覆盖元类的方法。(这是 Python 运行的,type.__call__最终将调用类__new____init__方法)。

这件事在这里为我尝试过 - 看看它是否适合你:


import threading

class M(type):
    registry = {} 
    recursing = threading.local()
    recursing.check = False
    mlock = threading.Lock()

    def __call__(cls, *args, **kw):
        mcls = cls.__class__
        if mcls.recursing.check:
            return super().__call__(*args, **kw)
        spec = args[0]
        base = spec.kls


        if (cls, base) not in mcls.registry:
            mcls.registry[cls, base] = type(
                cls.__name__,
                (cls, base) + cls.__bases__[1:],
                {}
            )
        real_cls = mcls.registry[cls, base]

        with mcls.mlock:
            mcls.recursing.check = True
            instance = real_cls.__class__.__call__(real_cls, *args, **kw)
            mcls.recursing.check = False
        return instance 

我导入了这个,并在 Python 会话中运行这个片段:


In [54]: class DynamicDerive(metaclass=M): 
...:     def __init__(self, spec): 
...:         super().__init__(spec) 
...:          
...:  
...: class Base1: 
...:    def __init__(self, uniform_params): 
...:        print("at base 1") 
...:  
...: class Base2: 
...:    def __init__(self, uniform_params): 
...:        print("at base 2") 
...:  
...:  
...: SomeSpec = type("SomeSpec", (), {}) 
...:  
...: spec1 = SomeSpec() 
...: spec1.kls = Base1 
...:  
...: spec2 = SomeSpec() 
...: spec2.kls = Base2 
...:  
...: a1 = DynamicDerive(spec1) 
...: a2 = DynamicDerive(spec2) 


at base 1
at base 2

In [55]: isinstance(a1, DynamicDerive)                                                                                             
Out[55]: True

In [56]: isinstance(a2, DynamicDerive)                                                                                             
Out[56]: True

In [57]: class D2(DynamicDerive): pass                                                                                             

In [58]: b1 = D2(spec1)                                                                                                            
at base 1

In [59]: b1.__class__.__mro__                                                                                                      
Out[59]: (__main__.D2, __main__.D2, __main__.DynamicDerive, __main__.Base1, object)


推荐阅读