[英]Does call to super(A, A()).__init__ depends on first parameter of super method?
[英]Require overriding method to call super()
我想強制調用子類中的某些方法來調用它們正在覆蓋的方法。
@abstractmethod 可以要求實現某些方法; 我想要一個與此類似的行為(即,如果覆蓋方法不調用 super(),則不執行並向用戶抱怨)。
例子:
class Foo:
@must_call_super
def i_do_things(self):
print('called')
class Good(Foo):
def i_do_things(self):
# super().i_do_things() is called; will run.
super().i_do_things()
print('called as well')
class Bad(Foo):
def i_do_things(self):
# should complain that super().i_do_things isn't called here
print('called as well')
# should work fine
good = Good()
# should error
bad = Bad()
謝謝你把我送進了兔子洞。
下面是我對這個問題的解決方案。 它使用metaclass 、 ast
和一些 hacking 來檢測孩子 class 是否在其some_func
方法版本中調用super().some_func()
。
這些應該由開發人員控制。
import inspect
import ast
import textwrap
class Analyzer(ast.NodeVisitor):
def __init__(self, ast_sig: str):
self.func_exists = False
self.sig = ast_sig
def visit_Call(self, node):
"""Traverse the ast tree. Once a node's signature matches the given
method call's signature, we consider that the method call exists.
"""
# print(ast.dump(node))
if ast.dump(node) == self.sig:
self.func_exists |= True
self.generic_visit(node)
class FooMeta(type):
# _ast_sig_super_methods stores the ast signature of any method that
# a `super().method()` call must be made in its overridden version in an
# inherited child. One can add more method and its associted ast sig in
# this dict.
_ast_sig_super_methods = {
'i_do_things': "Call(func=Attribute(value=Call(func=Name(id='super', ctx=Load()), args=[], keywords=[]), attr='i_do_things', ctx=Load()), args=[], keywords=[])",
}
def __new__(cls, name, bases, dct):
# cls = FooMeta
# name = current class name
# bases = any parents of the current class
# dct = namespace dict of the current class
for method, ast_sig in FooMeta._ast_sig_super_methods.items():
if name != 'Foo' and method in dct: # desired method in subclass
source = inspect.getsource(dct[method]) # get source code
formatted_source = textwrap.dedent(source) # correct indentation
tree = ast.parse(formatted_source) # obtain ast tree
analyzer = Analyzer(ast_sig)
analyzer.visit(tree)
if not analyzer.func_exists:
raise RuntimeError(f'super().{method} is not called in {name}.{method}!')
return super().__new__(cls, name, bases, dct)
class Foo(metaclass=FooMeta):
def i_do_things(self):
print('called')
這是由其他人完成的,我們希望從他們那里規定super().i_do_things
必須在其繼承類的覆蓋版本中調用。
class Good(Foo):
def i_do_things(self):
# super().i_do_things() is called; will run.
super().i_do_things()
print('called as well')
good = Good()
good.i_do_things()
# output:
# called
# called as well
class Bad(Foo):
def i_do_things(self):
# should complain that super().i_do_things isn't called here
print('called as well')
# Error output:
# RuntimeError: super().i_do_things is not called in Bad.i_do_things!
class Good(Foo):
def i_do_things(self):
# super().i_do_things() is called; will run.
super().i_do_things()
print('called as well')
class SecretlyBad(Good):
def i_do_things(self):
# also shall complain super().i_do_things isn't called
print('called as well')
# Error output:
# RuntimeError: super().i_do_things is not called in SecretlyBad.i_do_things!
FooMeta
是在定義繼承類時執行的,而不是在實例化時執行的,因此在Bad().i_do_things()
或SecretlyBad().i_do_things()
之前會拋出錯誤。 這與 OP 的要求不同,但它確實實現了相同的最終目標。super().i_do_things()
的 ast 簽名,我們可以在Analyzer
中取消注釋 print 語句,分析Good.i_do_things
的源代碼,並從那里進行檢查。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.