动态 __init_subclass__ 方法的参数绑定

argument binding for dynamic __init_subclass__ method

我正在尝试让 class 装饰器工作。装饰器将添加一个 __init_subclass__ 方法到它所应用的 class。

但是,当该方法动态添加到 class 时,第一个参数未绑定到子 class 对象。为什么会这样?

举个例子:这行得通,下面的静态代码是我试图最终得到的示例:

class C1:
    def __init_subclass__(subcls, *args, **kwargs):
        super().__init_subclass__(*args, **kwargs)
        print(f"init_subclass -> {subcls.__name__}, {args!r}, {kwargs!r}")

测试:

>>> D = type("D", (C1,), {})
init_subclass -> D, (), {}

但是,如果我动态添加 __init__subclass__ 方法,子 class 不会绑定到第一个参数:

def init_subclass(subcls, **kwargs):
    super().__init_subclass__(**kwargs)
    print(f"init_subclass -> {subcls.__name__}, {args!r}, {kwargs!r}")

def decorator(Cls):
    Cls.__init_subclass__ = init_subclass
    return Cls

@decorator
class C2:
    pass

测试:

>>> D = type("D", (C2,), {})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: init_subclass() missing 1 required positional argument: 'subcls'

为什么会发生这种情况,我该如何做到这一点并使绑定以正确的方式工作?

__init_subclass__ is an implicit classmethod.

可能无法使用零参数 super(如果您想了解原因,请阅读 ),但您应该能够在装饰器本身内部显式绑定 super。

def decorator(Cls):
    def __init_subclass__(subcls, **kwargs):
        print(f'init subclass {Cls!r}, {subcls!r}, {kwargs!r}')
        super(Cls, subcls).__init_subclass__(**kwargs)
    Cls.__init_subclass__ = classmethod(__init_subclass__)
    return Cls

@decorator
class C:
    pass

class D(C):
    pass

只是对那些提倡使用 abc 的人的评论。虽然 abc 也可以解决关键问题,但值得一提的是,这两种方法之间有两个区别(据我所知):

Class 定义与实例化。

abc.abstractmethod 装饰器在 class 实例化时对子 class 强制执行约束,而 __init_subclass__ 数据模型已经在 class 定义中执行了.示例:

class Foo(abc.ABC):  
    def init(self):
        pass
    @abc.abstractmethod
    def foo():
        pass

class Bar(Foo):
    pass

这段代码可以毫无问题地编译。当您通过例如 class 调用子 class 的构造函数时,该错误将首先出现x = Bar()。如果这是库代码,这意味着错误直到运行时才会出现。另一方面,以下代码:

class Par():
    def __init_subclass__(cls, *args, **kwargs):
        must_have = 'foo'
        if must_have not in list(cls.__dict__.keys()):
            raise AttributeError(f"Must have {must_have}")
    def __init__(self):
        pass

class Chi(Par):
    def __init__(self):
        super().__init__()

会抛出错误,因为检查是在 class 定义时执行的。

通过继承级别强制实施

另一个区别是 abstractmethod 希望修饰的方法被覆盖一次,但是 __init_subclass__ 数据模型也会对子 class 的子强制执行约束。示例:

class Foo(abc.ABC):
    def __init__(self):
        pass

    @abc.abstractmethod
    def foo():
        pass

class Bar(Foo):
    def __init__(self):
        super().__init__()
    def foo(self):
        pass

class Mai(Bar):
    pass

x = Mai()

这段代码可以工作。 Mai 不需要 foo 方法,因为抽象方法已在 Bar 中被覆盖。另一方面:

class Par():
    def __init_subclass__(cls, *args, **kwargs):
        must_have = 'foo'
        if must_have not in list(cls.__dict__.keys()):
            raise AttributeError(f"Must have {must_have}")
    def __init__(self):
        pass

class Chi(Par):
    def __init__(self):
        super().__init__()

    def foo(self):
        pass

class Chichi(Chi):
    def __init__(self):
        super().__init__()

这将引发错误,因为 Chichi 还必须有一个 foo 方法,即使中间的 class 有一个。