类装饰器禁用__init_subclass__

时间:2019-09-05 19:22:55

标签: python python-3.x inheritance decorator subclass

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

在3-4种方法中,我知道从父类对子类强制执行给定的方法(编辑元类的__new__方法,加入builtins.__build_class__,使用{{1 }}或使用__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,甚至不与类创建混为一谈)都可以禁用该类中已经存在的方法。

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

  • 这仅发生在hello world数据模型上吗?还是别人?

1 个答案:

答案 0 :(得分:4)

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

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


Par = add_hello_world(Par)

最初绑定到Par的类 定义为__init_subclass__;在add_hello_world中定义的 new 类没有,这是装饰后名称Par所引用的类,以及您要子类化的类。


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

显式调用装饰器:

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

如果您 did 尝试对其进行子类化,则会得到预期的错误:

>>> 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