在父类中,我定义了一个类变量和一个用于修改类变量值的类方法。我希望每个子类都使用自己的变量,而不是与其父类共享。
但结果不是我所期望的;在下面的示例中,我有两组父类和子类,以及一些代码来演示出了什么问题:
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 方法时,类变量与父类共享?
我的预期结果:
In p cm
1
0
In p cm
1
0
{'mat': {'loader': 'loader 1'}}
{}
{'mat': {'loader': 'loader 2'}}
{}
不同之处在于你改变了字典。第一个包含整数的简单示例适用于不可变的整数对象。cls._X += 1
取_X
的值(如有必要,从父类获取(,之后old + 1
操作生成一个新的整数对象,然后将其赋cls._X
。作业在这里很重要,因为这将在子班上进行。
但是您没有在第二种情况下将任何内容分配给类:
cls._callbacks[fmt] = {}
cls._callbacks[fmt]["loader"] = loader
您分配给字典中的键。cls._callbacks
属性本身不会改变,它是所有类之间共享的同一字典。查找cls._callbacks
引用,在Image
基类中找到,之后通过添加键值对来更新字典本身。没有一个子类(HSImage
或GT
(本身具有该属性。
您需要创建一个副本并重新分配更改的副本:
cls._callbacks = {k: dict(v) for k, v in cls._callbacks.items()}
cls._callbacks[fmt] = {'loader': loader}
这不仅会创建外部字典的副本,还会创建所有值的副本,因为这些也是字典,然后再为fmt
添加新字典。然后将副本分配给cls._callbacks
,有效地在子类上创建一个新属性(如果它还没有的话(。
当然,这不是那么有效;每次注册加载器时都会创建副本。最好在每个子类上创建一个新的_callback
字典对象,但这会变得乏味并且很容易被遗忘。您可以改为使用__init_subclass__
方法自动执行此操作:
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__
方法。