繁体   English   中英

类装饰器禁用 __init_subclass__

[英]class decorator disables __init_subclass__

我四处寻找这个问题的答案,但找不到任何东西。 如果之前已经问过这个,我很抱歉。

在我知道的 3-4 种从父类强制执行子类上的给定方法的方法中(编辑元类的__new__方法,连接到builtins.__build_class__ ,使用__init_subclass__或使用abc.abstractmethod )我通常最终使用__init_subclass__ ,基本上是因为易于使用,并且与@abc.abstractmethod不同,子类的约束是根据子类定义而不是类实例化来检查的。 例子:

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

    def __init__(self):
        pass

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

这个示例代码显然会抛出一个错误,因为Chi没有foo方法。 尽管如此,我还是偶然发现了这样一个事实,即可以通过使用一个简单的类装饰器来绕过来自上游类的这个约束:

def add_hello_world(Cls):
    class NewCls(object):
        def __init__(self, *args, **kwargs):
            self.instance = Cls(*args, **kwargs)

        def hello_world(self):
            print("hello world")

    return NewCls


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

    def __init__(self):
        pass

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

c = Chi()
c.hello_world()

上面的代码运行没有问题。 现在,不管我装饰的类是Par (当然,如果Par是库代码,我作为用户代码开发人员可能无法访问它),我无法真正解释这种行为。 对我来说很明显,可以使用装饰器向现有类添加方法或功能,但我从未见过不相关的装饰器(只打印hello world ,甚至不干扰类创建)禁用已经存在的方法在课堂里。

  • 这是预期的 Python 行为吗? 或者这是某种错误? 老实说,据我所知,这可能会带来一些安全问题。

  • 这只发生在__init_subclass__数据模型上吗? 还是也给别人?

请记住,装饰器语法只是函数应用程序:

class Par:
    def __init_subclass__(...):
         ...


Par = add_hello_world(Par)

最初绑定到Par定义的类__init_subclass__ add_hello_world中定义的类没有,这就是装饰后名称Par所指的类,以及您正在add_hello_world类。


顺便说一句,您仍然可以通过__init__访问原始类Par

显式调用装饰器:

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

    def __init__(self):
        pass

Foo = Par  # Keep this for confirmation

Par = add_hello_world(Par)

我们可以确认闭包保留了对原始类的引用:

>>> Par.__init__.__closure__[0].cell_contents
<class '__main__.Par'>
>>> Par.__init__.__closure__[0].cell_contents is Par
False
>>> Par.__init__.__closure__[0].cell_contents is Foo
True

如果您确实尝试将其子类化,则会得到预期的错误:

>>> class Bar(Par.__init__.__closure__[0].cell_contents):
...   pass
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "tmp.py", line 16, in __init_subclass__
    raise AttributeError(f"Must have {must_have}")
AttributeError: Must have foo

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM