为了账号安全,请及时绑定邮箱和手机立即绑定

类装饰器禁用 __init_subclass__

类装饰器禁用 __init_subclass__

三国纷争 2022-05-19 15:41:47
我已经四处寻找这个问题的答案,但找不到任何东西。如果之前已经问过这个问题,我深表歉意。在我所知道的 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):        passclass 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_worldclass 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):        passclass Chi(Par):    def __init__(self):        super().__init__()c = Chi()c.hello_world()上面的代码运行没有问题。现在,不管我装饰的类是Par(当然,如果Par是库代码,作为用户代码开发人员我什至可能无法访问它),我无法真正解释这种行为。对我来说很明显可以使用装饰器向现有类添加方法或功能,但我从未见过不相关的装饰器(只是打印hello world,甚至不会与类创建混淆)禁用已经存在的方法班级。这是预期的 Python 行为吗?或者这是某种错误?老实说,据我了解,这可能会带来一些安全问题。这只发生在__init_subclass__数据模型上吗?还是也给别人?
查看完整描述

1 回答

?
炎炎设计

TA贡献1808条经验 获得超4个赞

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


class Par:

    def __init_subclass__(...):

         ...



Par = add_hello_world(Par)

最初绑定到Par定义的类__init_subclass__;内部定义的新类add_hello_world没有,这就是装饰后名称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

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


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


查看完整回答
反对 回复 2022-05-19
  • 1 回答
  • 0 关注
  • 92 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信