【问题标题】:Require overriding method to call super() - python需要覆盖方法来调用 super() - python
【发布时间】:2023-01-07 10:37:44
【问题描述】:

我想强制调用子类中的某些方法来调用它们正在覆盖的方法。

@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()

【问题讨论】:

    标签: python-3.x abstract-class


    【解决方案1】:

    谢谢你把我送进了兔子洞。

    下面是我对这个问题的解决方案。它使用 metaclassast 和一些 hacking 来检测子类是否在它的 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中打印语句的注释,分析Good.i_do_things的源代码,并从那里进行检查。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-05-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-09
      • 2011-11-13
      • 1970-01-01
      相关资源
      最近更新 更多