为何关键字参数无法传入__init_subclass__方法?
问题代码示例
class ExternalMeta(type): def __new__(cls, name, base, dct, **kwargs): dct['district'] = 'Jiading' x = super().__new__(cls, name, base, dct) x.city = 'Shanghai' return x class MyMeta(ExternalMeta): def __new__(cls, name, base, dct, age=0, **kwargs): x = super().__new__(cls, name, base, dct) x.name = 'Jerry' x.age = age return x def __init__(self, name, base, dct, age=0, **kwargs): self.country = 'China' class MyClass(metaclass=MyMeta, age=10): def __init_subclass__(cls, say_hi, **kwargs): print(f'keyword arguments are: {kwargs}') super().__init_subclass__(**kwargs) cls.hello = say_hi class DerivedClass(MyClass, say_hi="hello"): pass
运行报错信息
Traceback (most recent call last): File "app2.py", line 27, in <module> class DerivedClass(MyClass, say_hi="hello"): File "app2.py", line 11, in __new__ x = super().__new__(cls, name, base, dct) File "app2.py", line 4, in __new__ x = super().__new__(cls, name, base, dct) TypeError: __init_subclass__() missing 1 required positional argument: 'say_hi'
Python官方文档相关说明
classmethod object.init_subclass(cls)
当包含该方法的类被继承时,会调用此方法。cls代表新的子类。若将其定义为普通实例方法,会被隐式转换为类方法。传入新类的关键字参数会传递给父类的
__init_subclass__。为与其他使用__init_subclass__的类兼容,应取出所需关键字参数后,将其余参数传递给基类。
疑问
运行时打印kwargs得到空字典{},为什么say_hi参数无法传递到__init_subclass__方法中?
问题原因
创建子类DerivedClass时,元类的__new__方法会被触发。你的MyMeta.__new__和ExternalMeta.__new__在调用父类的__new__时,都没有把收到的**kwargs传递过去。而Python中,类定义时的关键字参数需要通过元类的__new__/__init__传递给type.__new__/type.__init__,之后才会被转发给__init_subclass__执行。
因为元类方法丢弃了kwargs,say_hi参数根本没到达触发__init_subclass__的逻辑里,导致该方法缺少必要参数报错。
修复方案
修改元类的__new__和__init__方法,保证**kwargs在参数传递链中被完整传递:
class ExternalMeta(type): def __new__(cls, name, base, dct, **kwargs): dct['district'] = 'Jiading' # 将**kwargs传递给父类__new__ x = super().__new__(cls, name, base, dct, **kwargs) x.city = 'Shanghai' return x class MyMeta(ExternalMeta): def __new__(cls, name, base, dct, age=0, **kwargs): # 将**kwargs传递给父类__new__ x = super().__new__(cls, name, base, dct, **kwargs) x.name = 'Jerry' x.age = age return x def __init__(self, name, base, dct, age=0, **kwargs): self.country = 'China' # 将剩余**kwargs传递给父类__init__ super().__init__(name, base, dct, **kwargs)
修改后,say_hi参数会被正确传递到MyClass.__init_subclass__中,运行代码会打印keyword arguments are: {},同时DerivedClass.hello会被设置为"hello",不再报错。
内容的提问来源于stack exchange,提问作者shan

