动态__init_subclass__方法

时间:2018-05-02 14:34:19

标签: python python-3.x metaprogramming

我正在尝试让类装饰工作。装饰器将向其应用的类添加__init_subclass__方法。

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

作为一个例子:这是有效的,下面的静态代码是我想要最终得到的一个例子:

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__方法,则子类不会绑定到第一个参数:

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'

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

2 个答案:

答案 0 :(得分:1)

__init_subclass__ is an implicit classmethod

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

 head: {
    title: 'mynuxt',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: 'Nuxt.js project' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
      { rel: 'stylesheet', href: 'https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css' },
      { rel: 'stylesheet', href: '/css/bootstrap.min.css' },
      { rel: 'stylesheet', href: '/css/mdb.min.css' },
      { rel: 'stylesheet', href: '/css/style.min.css' },
    ],
    script: [
          { src: '/js/bootstrap.min.js' },
          { src: '/js/popper.min.js' },
          { src: '/js/mdb.min.js' }
    ],
  },

答案 1 :(得分:0)

对那些主张使用abc的人来说只是一个评论。尽管abc也可以解决问题,但值得一提的是两种方法之间有两个区别(我知道):

类定义与实例化。

abc.abstractmethod装饰器在类实例化时对子类施加约束,而__init_subclass__数据模型已经在类定义时进行了约束。示例:

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

class Bar(Foo):
    pass

此代码将毫无问题地进行编译。当您通过以下方式调用子类的构造函数时,该错误将首先显示。 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__()

将引发错误,因为检查是在类定义时执行的。

强制执行继承级别

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

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方法,即使介于两者之间的类具有foo方法。