Python class 继承:动态 subclass 使用自己的和父的默认值和 init 方法初始化

Python class inheritance: dynamic subclass initialization with own and parent defaults and init methods

我正在尝试创建一个 classes 系统,它可以用 **kwargs 初始化并且每个字段都有默认值。这是一些代表我正在尝试做的事情的代码:

class Parent(object): #should only have p_var fields
    DEFAULTS = {
    "p_var_1" : "my name",
    "p_var_2" : 2
    }

    def __init__(self, **kwargs):
        for key in DEFAULTS:
            #Also include check for list type and copy, not assign
            if key in kwargs:
                setattr(self, key, kwargs[key])
            else:
                setattr(self, key, self.DEFAULTS[key])
        #Check for super being 'object' - problems?
        #if not super() == object:
        #   super().__init__(**kwargs)


class Gen1(Parent): #should have p_var and G1_var fields
    DEFAULTS = {
    "G1_var_1" : "Generation 1",
    "G1_var_2" : []
    }

    #redefining only in case if Parent can't check for super
    def __init__(self, **kwargs):
        for key in DEFAULTS:
            #Also include check for list type and copy, not assign
            if key in kwargs:
                setattr(self, key, kwargs[key])
            else:
                setattr(self, key, self.DEFAULTS[key])

        #check whether super() is object
        super().__init__(**kwargs) #pass all args to parent init 

class Gen2(Gen1): #should have p_var, G1_var and G2_var fields
    DEFAULTS = {
    "G2_var_1" : 1337,
    "G2_var_2" : (10,4)
    }

    #After Gen1, redefining __init__ shouldn't be necessary

所以在设置了几个实例之后:

Gen2item1 = Gen2()
Gen2item2 = Gen2(p_var_1 = "different name", G2_var_1 = 666)

Gen2item1 将被初始化为所有默认值:

但 Gen2item1 会有不同的 p_var_1 和 G2_var_1 值,其余相同。 但是,由于 'DEFAULT' 在子 classes 中被覆盖,因此 Gen2item1 值:

设置三次,每一代(级别),G1_varp_var值未设置。对于 Gen2item2,__init__ 的所有三代都将 G2_var_1 设置为 666,将 G2_var_2 设置为 (10,4)。


所以底线问题是关于为每个 class 代制作一些 'defaults' 字段(dict)的方法,以便:

  1. 通过 item = subclass() 调用子 class 的 __init__ 时,它会根据 'defaults' 字典的键从 kwargs 获取参数,
  2. 通过 setattrib(self, key, value) 设置自己的字段,如果存在来自 defaults 的键,则从 kwargs 获取值,否则也从 defaults 获取值,并且
  3. 在通过 super()__init__(**kwargs) 将 kwargs 传递给它的同时调用其父级的 init。

然后,父级以相同的方式为子级的同一个实例设置由其生成的字典定义的默认值class,调用并传递给它自己的父级的 __init__,依此类推,直到parent 是 object,在这种情况下 super().__init__ 根本不会得到 called/passed 参数。
我读过关于 metaclasses 的文章,这似乎有点矫枉过正,还有 got my hopes high on this,它有一个单独的父实例和子实例class。
有没有办法在没有 metaclasses 的情况下实现这样的目标?将默认值存储在外部全局字典中会更好吗(在这种情况下,将不胜感激一些寻址帮助),或者有没有办法建立一个工厂?


已解决编辑:
__init_subclass__ 可以工作并且几乎可以满足我的要求,前提是它还为要初始化的 class 定义了 __init__(我的项目的具体细节)。这可以回答我的问题,并提供了一些有用的信息来说明如何在我的项目中实现我想要的设置。我相信定义一个 class 方法来设置一个适当的 __init__ 并在定义 class 时让它 运行 就足够了(这是非常新的 __init_subclass__ 似乎这样做很有帮助),但至少要有一些向后兼容性,metaclass 似乎是做到这一点的方法。

我会跳过 DEFAULTS 指令,因为它是不必要的膨胀。如果你真的需要访问这些参数的默认值,你可以通过内省 __init__ 方法来获取它们。否则,使用 super 的一般规则是去除 class 独有的参数,并将所有其他参数传递给下一次调用 __init__.

class 设计者有责任确保生成的 MRO 中的任何 classes 在 object.__init__ 被调用之前删除 __init__ 的所有参数。在下面的示例中,Parent.__init__ 将使用实际参数调用 object.__init__ 的唯一方法是 [=20= 的 MRO 中的某些 other class ] 无法删除其参数。

class Parent(object):
    def __init__(self, p_var_1="my name", p_var_2=2, **kwargs):
        super().__init__(**kwargs)
        self.p_var_1 = p_var_1
        self.p_var_2 = p_var_2

class Gen1(Parent): #should have p_var and G1_var fields
    def __init__(self, g1_var_1="Generation 1", g1_var_2=None, **kwargs):
        super().__init__(**kwargs)
        if g1_var_2 is None:
            g1_var_2 = []
        self.g1_var_1 = g1_var_1
        self.g1_var_2 = g1_var_2

class Gen2(Gen1): #should have p_var, G1_var and G2_var fields
    def __init__(self, g2_var_1=1337, g2_var_2=(10,4), **kwargs):
        super().__init__(**kwargs)
        self.g2_var_1 = g2_var_1
        self.g2_var_2 = g2_var_2

考虑像

这样的电话
Gen2(p_var_1=...,
     p_var_2=...,
     g1_var_1=...,
     g1_var_2=...,
     g2_var_1=...,
     g2_var_2=...)

调用的第一个方法是 Gen2.__init__,它只会将 pg1 变量传递给 super.__init__。该调用导致调用 Gen1.__init__,它仅传递 p 变量。调用Parent.__init__时,**kwargs为空。

您可以使用 Python 3.6 中添加到 object() 的新 class 方法:__init_subclass__

此方法在 class 被子classed 时被调用,并允许您在不使用元class 的情况下自定义子class 的创建。

您的示例如下所示:

class Base:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__()
        for key, value in kwargs.items():
            setattr(cls, key, value)

    def __init__(self, *args, **kwargs):
        super().__init__()
        for key, value in kwargs.items():
            setattr(self, key, value)     

class Parent(Base, p_var_1='my_name', p_var_2=2):
    pass

class Gen1(Parent, G1_var_1='Generation 1', G1_var_2=[]):
    pass

class Gen2(Gen1, G2_var_1=1337, G2_var_2=(10,4)):
    pass