Subclass with class 变量继承

Subclass with class variable inheritance

在 parent class 中,我定义了一个 class 变量和一个 class 方法来修改 class 变量值。我希望每个 child class 使用自己的变量,而不是与其 parent 共享。

但是结果不是我所期望的;在下面的示例中,我有两组 parent class 加上 child classes,以及一些代码来演示出了什么问题:

class P:
    _X = 0

    @classmethod
    def cm(cls):
        print("In p cm")
        cls._X += 1

class C1(P):
    pass

class C2(P):
    pass

class Image:
    _callbacks = {}

    @classmethod
    def registerDataFormat(cls, fmt, loader):
        if fmt in cls._callbacks.keys():
            print("The %s format has already been registered." % (fmt))
            return False

        cls._callbacks[fmt] = {}
        cls._callbacks[fmt]["loader"] = loader

class HSImage(Image):
    pass

class GT(Image):
    pass

if __name__ == '__main__':
    C1.cm()
    print(C1._X)
    print(P._X)
    C2.cm()
    print(C2._X)
    print(P._X)

    HSImage.registerDataFormat("mat", "loader 1")
    print(HSImage._callbacks)
    print(Image._callbacks)
    GT.registerDataFormat("mat", "loader 2")
    print(GT._callbacks)
    print(Image._callbacks)

结果如下:

In p cm
1
0
In p cm
1
0
{'mat': {'loader': 'loader 1'}}
{'mat': {'loader': 'loader 1'}}
The mat format has already been registered.
{'mat': {'loader': 'loader 1'}}
{'mat': {'loader': 'loader 1'}}

第一个例子有预期的结果,但第二个没有,为什么我调用class方法时class变量与parentclass共享child class 第二组 classes?

我的预期结果:

In p cm
1
0
In p cm
1
0
{'mat': {'loader': 'loader 1'}}
{}
{'mat': {'loader': 'loader 2'}}
{}

区别在于您改变了字典。第一个简单的整数示例使用不可变整数 objects。 cls._X += 1_X 的值(如果需要,来自 parent class),之后 old + 1 操作产生一个新的整数 object然后分配回 cls._X。这里的分配很重要,因为它将在 child class.

上进行

但是在第二种情况下,您没有将任何东西分配回 class:

cls._callbacks[fmt] = {}
cls._callbacks[fmt]["loader"] = loader

您在字典中指定了一个键cls._callbacks 属性本身没有改变,它是所有 classes 共享的同一个字典。查找 cls._callbacks 引用,在 Image 基础 class 上找到,之后通过添加 key-value 对更新字典本身。 None 的子class(HSImageGT)本身具有属性。

您需要创建一个副本并将更改后的副本分配回去:

cls._callbacks = {k: dict(v) for k, v in cls._callbacks.items()}
cls._callbacks[fmt] = {'loader': loader}

这不仅会创建外部字典的副本,还会创建所有值的副本,因为在为 fmt 添加新字典之前,这些值也是所有字典。然后将副本分配给 cls._callbacks,有效地在子class 上创建一个新属性(如果它还不存在的话)。

当然效率不高;每次注册加载器时都会创建副本。您最好在每个子 class 上创建一个新的 _callback 字典 object,但这会变得乏味并且很容易被遗忘。您可以使用 自动执行 __init_subclass__ method:

class Image:
    def __init_subclass__(cls):
        cls._callbacks = {}

    @classmethod
    def registerDataFormat(cls, fmt, loader):
        if fmt in cls._callbacks:
            print("The {} format has already been registered.".format(fmt))
            return

        cls._callbacks[fmt] = {'loader': loader}

您创建的每个子class都会调用__init_subclass__方法。