有没有办法从 class 作用域引用 object 的基础 __class__?

Is there a way to reference an object's base __class__ from class scope?

我有一系列 classes,继承自几个可能的 parents 中的一个,它们都共享一个基础 class。每个 class 都有一个 class-scope Dict[str, object] parameters 是基于 class parameters

from copy import deepcopy


class BaseClass:
    parameters = {
        'example_param1': ParamObject(name='example_param1', editable=True),
    }


class MiddleClass(Baseclass):
    parameters = {
        **deepcopy(Baseclass.parameters),
        'example_param2': ParamObject(name='example_param2', editable=False),
    }


class ChildClass(MiddleClass):
    parameters = {
        **deepcopy(MiddleClass.parameters),
        'example_param3': ParamObject(name='example_param3', editable=True),
    }

这个实现完成了工作,但我发现 **deepcopy(Baseclass.parameters), 行不令人满意。随着时间的推移,这些 child class 将由对编码只有基本了解的人进行编辑,因此我想让代码尽可能简单 cut-and-pasteable。

有什么我可以在 class 范围内调用以获得 super().__class__ 的等价物吗?我希望用户能够将基数 class 从 MiddleClass 更改为 MiddleClass2,而无需记住在多个位置更改基数 class。

如果您的 class 仅继承自一个 class,您可以使用 metaclass:

class Meta(type):
    def __new__(cls, name, parents, namespace):
        namespace['parameters'] = {
            **deepcopy(parents[0].parameters),
            **namespace['parameters']
        }

        return super().__new__(cls, name, parents, namespace)

class MiddleClass(Baseclass, metaclass=Meta):
    parameters = {
        'example_param2': ParamObject(name='example_param2', editable=False),
    }

这当然是一个简化的版本,例如不处理多个父 classes。

你可以用元class来做到这一点。检查继承树中的所有 classes 以查看它们是否具有 parameters 字段。如果是,则将它们合并在一起并在 class 实例上设置 属性。

class ParamMeta(type):
    def __init__(cls, name, bases, dct):
        class_types = [cls] + list(bases)
        parameters = {}
        for class_type in class_types:
            if hasattr(class_type, "parameters"):
                parameters.update(class_type.parameters)
        cls.parameters = parameters
        super().__init__(name, bases, dct)

示例:

class Foo(metaclass=ParamMeta):
    parameters = {"a": "b"}


class Bar(Foo):
    pass


class Fizz(Bar):
    parameters = {"c": "d"}


print(Foo.parameters)
print(Bar.parameters)
print(Fizz.parameters)

输出:

{'a': 'b'}
{'a': 'b'}
{'c': 'd', 'a': 'b'}

您可以使用 __init_subclass__ 将来自父 类 的参数直接添加到显式定义的 parameters 属性,代价是插入顺序被颠倒(如果这很重要)。

class ParameterBase:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        for pc in cls.__bases__:
            d = getattr(pc, 'parameters', {})
            cls.parameters.update(d)

class BaseClass(ParameterBase):
    parameters = {
        'example_param1': ParamObject(name='example_param1', editable=True),
    }


class MiddleClass(BaseClass):
    parameters = {
        'example_param2': ParamObject(name='example_param2', editable=False),
    }


class ChildClass(MiddleClass):
    parameters = {
        'example_param3': ParamObject(name='example_param3', editable=True),
    }

您可以使用 Metaclasses,例如建议的其他答案,但我不确定这对新程序员来说有多容易理解。

您可以通过在 class 上调用 __bases__ 来访问 class 继承自的 classes。遗憾的是,在 python 上你不能引用你在 class 主体上创建的 class,这意味着你不能这样做:

class A:
   bases = A.__bases__  # Invalid code, A is not defined yet

如果你想这样做,同时保持参数静态,你可以在 class 定义之后这样做,它会有你正在寻找的确切行为,虽然有点难看:

class BaseClass:
    parameters = {
        'example_param1': ParamObject(name='example_param1', editable=True),
    }


class MiddleClass(Baseclass):
    pass

MidleClass.parameters = {
    **deepcopy(MidleClass.__bases__[0].parameters),
    'example_param2': ParamObject(name='example_param2', editable=False),
}

现在,要稍微改进一下,如果您的 parameters 确实始终是 'Dict[str, object]',则不需要进行深度复制,因为使用 **一种功能性的方式。您还可以使用 __bases__ 值处理多个父 classes,以确保捕获每个值。所以,如果我要这样做,我会这样做:

def base_parameters(cls):
    parameters = {}
    for base in cls.__bases__:
        parameters.update(base.parameters)

    return parameters


class BaseClass:
    parameters = {
        'example_param1': ParamObject(name='example_param1', editable=True),
    }


class MiddleClass(BaseClass):
    pass

MiddleClass.parameters = {
    **base_parameters(MiddleClass),
    'example_param2': ParamObject(name='example_param2', editable=False),
}


class ChildClass(MiddleClass):
    pass

ChildClass.parameters = {
    **base_parameters(ChildClass),
    'example_param3': ParamObject(name='example_param3', editable=True),
}