mos*_*gui 2 python inheritance subclass decorator python-3.x
我四处寻找这个问题的答案,但找不到任何东西。如果之前已经问过这个,我很抱歉。
在我知道的 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__()
Run Code Online (Sandbox Code Playgroud)
这个示例代码显然会抛出一个错误,因为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()
Run Code Online (Sandbox Code Playgroud)
上面的代码运行没有问题。现在,不管我装饰的类是Par(当然,如果Par是库代码,我什至作为用户代码开发人员可能无法访问它),我无法真正解释这种行为。对我来说很明显,可以使用装饰器向现有类添加方法或功能,但我从未见过不相关的装饰器(只是打印hello world,甚至不会干扰类创建)禁用已经存在于类中的方法班级。
这是预期的 Python 行为吗?或者这是某种错误?老实说,据我所知,这可能会带来一些安全问题。
这只发生在__init_subclass__数据模型上吗?还是也给别人?
请记住,装饰器语法只是函数应用程序:
class Par:
def __init_subclass__(...):
...
Par = add_hello_world(Par)
Run Code Online (Sandbox Code Playgroud)
最初绑定到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)
Run Code Online (Sandbox Code Playgroud)
我们可以确认闭包保留了对原始类的引用:
>>> 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
Run Code Online (Sandbox Code Playgroud)
如果您确实尝试将其子类化,则会得到预期的错误:
>>> 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
Run Code Online (Sandbox Code Playgroud)