【问题标题】:Subclass with class variable inheritance具有类变量继承的子类
【发布时间】:2019-03-28 17:50:24
【问题描述】:

在父类中,我定义了一个类变量和一个用于修改类变量值的类方法。我希望每个子类都使用自己的变量,而不是与其父类共享。

但结果不是我所期望的;在下面的示例中,我有两组父类和子类,以及一些代码来演示出了什么问题:

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'}}

第一个例子有预期的结果,但第二个没有,为什么我在第二组类中的一个子类上调用类方法时,类变量与父类共享?

我的预期结果:

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

【问题讨论】:

  • 顺便说一句,感谢您提供完整的 MCVE,并感谢您对它进行了更多清理以收紧它。这在新问题中并不常见。

标签: python class inheritance class-variables


【解决方案1】:

不同之处在于您修改了字典。第一个简单的整数示例适用于不可变整数对象。 cls._X += 1 获取_X 的值(如果需要,来自父类),之后old + 1 操作生成一个新的整数对象,然后将其分配回cls._X。这里的分配很重要,因为这将发生在子类上。

但是您没有将任何东西分配给第二种情况的类:

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

您在字典中分配了一个键incls._callbacks 属性本身没有改变,它是所有类之间共享的同一个字典。在Image 基类上查找cls._callbacks 引用,然后通过添加键值对来更新字典本身。没有一个子类(HSImageGT)本身具有该属性。

您需要创建一个副本并重新分配更改后的副本:

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

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

当然效率不高; 每次注册加载程序时都会创建副本。您最好在每个子类上创建一个新的 _callback 字典对象,但这会变得乏味并且很容易被遗忘。您可以改为自动化使用__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}

为您创建的每个子类调用__init_subclass__ 方法。

【讨论】:

    猜你喜欢
    • 2021-10-13
    • 2014-01-25
    • 1970-01-01
    • 2021-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多