簡體   English   中英

需要覆蓋方法來調用 super()

[英]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()

謝謝你把我送進了兔子洞。

下面是我對這個問題的解決方案。 它使用metaclassast和一些 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!

筆記

  1. 由於FooMeta是在定義繼承類時執行的,而不是在實例化時執行的,因此在Bad().i_do_things()SecretlyBad().i_do_things()之前會拋出錯誤。 這與 OP 的要求不同,但它確實實現了相同的最終目標。
  2. 要獲取super().i_do_things()的 ast 簽名,我們可以在Analyzer中取消注釋 print 語句,分析Good.i_do_things的源代碼,並從那里進行檢查。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM