【问题标题】:argument binding for dynamic __init_subclass__ method动态 __init_subclass__ 方法的参数绑定
【发布时间】:2018-10-12 17:12:22
【问题描述】:

我正在尝试让类装饰器工作。装饰器会将__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'

为什么会发生这种情况,我怎样才能做到这一点并让绑定以正确的方式工作?

【问题讨论】:

    标签: python python-3.x metaprogramming


    【解决方案1】:

    __init_subclass__ is an implicit classmethod.

    可能无法使用零参数 super(如果您想了解原因,请阅读 here),但您应该能够在装饰器本身内显式绑定 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
    

    【讨论】:

    • 我想我有一个想法,但是你能解释一下解释器对super()subcls 参数的需求吗?没有它,我会收到运行时错误。参数不应该是C2...吗?
    • 嗯,是的——从技术上讲,它应该是super(C2, subcls),我很草率。当 Cls 尚不存在时,无参数 super 的工作方式(使用 __class__ 单元格)使得这很棘手,但我认为您可以在装饰器中使用闭包,请参阅编辑并让我知道它是如何实现的去(未经测试)。
    • 关闭效果很好。它甚至可以在使用exec 和一串代码构建时工作。
    【解决方案2】:

    只是对那些提倡使用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 ALSO 必须有一个 foo 方法,即使两者之间的类有一个。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-04
      • 1970-01-01
      • 2016-04-02
      相关资源
      最近更新 更多