【问题标题】:In Python, how do I indicate I'm overriding a method?在 Python 中,我如何表明我正在覆盖一个方法?
【发布时间】:2010-11-13 03:34:15
【问题描述】:

例如,在 Java 中,@Override 注释不仅提供了对覆盖的编译时检查,而且还提供了出色的自记录代码。

我只是在寻找文档(尽管如果它是诸如 pylint 之类的检查器的指标,那是一个奖励)。我可以在某处添加注释或文档字符串,但是在 Python 中指示覆盖的惯用方式是什么?

【问题讨论】:

  • 换句话说,你从来没有表明你正在重写一个方法?留给读者自己解决?
  • 是的,我知道编译语言似乎很容易出错,但你只需要接受它。在实践中,我发现这不是什么大问题(在我的例子中是 Ruby,不是 Python,而是相同的想法)
  • 当然,完成。 Triptych 的答案和 mkorpela 的答案都很简单,我喜欢这样,但后者的显式而不是隐式的精神,以及可理解地防止错误获胜。
  • 不是直接相同,而是abstract base classes检查是否所有抽象方法都被子类覆盖了。当然,如果您要覆盖具体方法,这将无济于事。

标签: python inheritance overriding self-documenting-code


【解决方案1】:

基于@mkorpela's great answer,我编写了一个类似的包(ipromise pypi github),它可以进行更多检查:

假设A继承自BCB继承自C

模块 ipromise 检查:

  • 如果A.f 覆盖B.f,则B.f 必须存在,并且A 必须继承自B。 (这是覆盖包中的检查)。

  • 您没有模式A.f 声明它覆盖B.f,然后声明它覆盖C.fA 应该说它从 C.f 覆盖,因为 B 可能决定停止覆盖此方法,这不应该导致下游更新。

  • 您没有A.f 声明它覆盖C.f 的模式,但B.f 没有声明它的覆盖。

  • 您没有A.f 声明它覆盖C.f 的模式,但B.f 声明它从一些D.f 覆盖。

它还具有用于标记和检查实现抽象方法的各种功能。

【讨论】:

    【解决方案2】:

    您可以使用来自PEP 544 的协议。使用这种方法,接口-实现关系只在使用现场声明。

    假设您已经有一个实现(我们称之为MyFoobar),您定义一个接口(一个协议),它具有您实现的所有方法和字段的签名,我们称之为IFoobar

    然后,在使用站点,您将实现实例绑定声明为具有接口类型,例如myFoobar: IFoobar = MyFoobar()。现在,如果您使用界面中缺少的字段/方法,Mypy 将在使用站点抱怨(即使它可以在运行时工作!)。如果你在实现中没有从接口实现一个方法,Mypy 也会报错。如果你实现了接口中不存在的东西,Mypy 不会抱怨。但这种情况很少见,因为接口定义紧凑且易于查看。您将无法实际使用该代码,因为 Mypy 会抱怨。

    现在,这不会涵盖在超类和实现类中都有实现的情况,例如 ABC 的某些用途。但是override 在Java 中使用,即使no implementation in the interface 也是如此。该解决方案涵盖了这种情况。

    from typing import Protocol
    
    class A(Protocol):
        def b(self):
            ...
        def d(self):  # we forgot to implement this in C
            ...
    
    class C:
        def b(self):
            return 0
    
    bob: A = C()
    

    类型检查结果:

    test.py:13: error: Incompatible types in assignment (expression has type "C", variable has type "A")
    test.py:13: note: 'C' is missing following 'A' protocol member:
    test.py:13: note:     d
    Found 1 error in 1 file (checked 1 source file)
    

    【讨论】:

    • 有例子吗?会通过/失败 mypy 的东西?
    • @Bluu:是的,看看defining a protocol部分
    • 您能否在答案中包含示例源代码和mypy 结果?以及所需的版本号和工具(我认为 mypy 是这个问题线程的新手)?我认为这会使您的答案更加独立。
    • @Bluu 我已经添加了。
    【解决方案3】:

    在 Python 2.6+ 和 Python 3.2+ 中可以做到(实际模拟一下,Python 不支持函数重载,子类会自动覆盖父类的方法)。我们可以为此使用装饰器。但首先,请注意 Python 的 @decorators 和 Java 的 @Annotations 是完全不同的东西。前一个是包含具体代码的包装器,而后一个是编译器的标志。

    为此,先做pip install multipledispatch

    from multipledispatch import dispatch as Override
    # using alias 'Override' just to give you some feel :)
    
    class A:
        def foo(self):
            print('foo in A')
    
        # More methods here
    
    
    class B(A):
        @Override()
        def foo(self):
            print('foo in B')
        
        @Override(int)
        def foo(self,a):
            print('foo in B; arg =',a)
            
        @Override(str,float)
        def foo(self,a,b):
            print('foo in B; arg =',(a,b))
            
    a=A()
    b=B()
    a.foo()
    b.foo()
    b.foo(4)
    b.foo('Wheee',3.14)
    

    输出:

    foo in A
    foo in B
    foo in B; arg = 4
    foo in B; arg = ('Wheee', 3.14)
    

    注意这里必须使用带括号的装饰器

    要记住的一件事是,由于 Python 没有直接重载函数,所以即使 B 类不继承自 A 类但需要所有这些 foos,您也需要使用 @Override(尽管使用在这种情况下,别名 'Overload' 会更好看)

    【讨论】:

      【解决方案4】:

      基于这个和 fwc:s 的回答,我创建了一个 pip 可安装包https://github.com/mkorpela/overrides

      有时我会在这里看到这个问题。 这主要发生在(再次)在我们的代码库中看到相同的错误之后:有人在重命名“接口”中的方法时忘记了一些“接口”实现类..

      好吧,Python 不是 Java,但 Python 具有强大的功能 - 显式优于隐式 - 并且在现实世界中有一些具体的案例可以帮助我。

      所以这里是覆盖装饰器的草图。这将检查作为参数给出的类是否与被装饰的方法具有相同的方法(或其他名称)名称。

      如果您能想到更好的解决方案,请在此处发布!

      def overrides(interface_class):
          def overrider(method):
              assert(method.__name__ in dir(interface_class))
              return method
          return overrider
      

      它的工作原理如下:

      class MySuperInterface(object):
          def my_method(self):
              print 'hello world!'
      
      
      class ConcreteImplementer(MySuperInterface):
          @overrides(MySuperInterface)
          def my_method(self):
              print 'hello kitty!'
      

      如果你做了一个错误的版本,它会在类加载期间引发一个断言错误:

      class ConcreteFaultyImplementer(MySuperInterface):
          @overrides(MySuperInterface)
          def your_method(self):
              print 'bye bye!'
      
      >> AssertionError!!!!!!!
      

      【讨论】:

      • 太棒了。这在我第一次尝试时发现了一个拼写错误的错误。荣誉。
      • mfbutner: 不是每次执行方法时都调用它 - 仅在创建方法时调用。
      • 这对文档字符串也很好! overrides 可以复制覆盖方法的文档字符串,如果覆盖方法没有它自己的一个。
      • @mkorpela,嘿,你的这段代码应该在 python 默认库系统中。为什么你不把它放在 pip 系统中? :P
      • @mkorpela :哦,我建议通知 python 核心开发人员注意这个包,他们可能想考虑在核心 python 系统中添加覆盖装饰器。 :)
      【解决方案5】:

      我制作的装饰器不仅检查了覆盖属性的名称是否是该属性所在类的任何超类,而无需指定超类,此装饰器还检查以确保覆盖属性必须是相同的类型作为被覆盖的属性。类方法被视为方法,静态方法被视为函数。此装饰器适用于可调用对象、类方法、静态方法和属性。

      源码见:https://github.com/fireuser909/override

      此装饰器仅适用于作为 override.OverridesMeta 实例的类,但如果您的类是自定义元类的实例,请使用 create_custom_overrides_meta 函数创建与覆盖装饰器兼容的元类。对于测试,运行 override.__init__ 模块。

      【讨论】:

        【解决方案6】:

        在@mkorpela great answer 上即兴创作,这是一个带有

        的版本

        更精确的检查、命名和引发的错误对象

        def overrides(interface_class):
            """
            Function override annotation.
            Corollary to @abc.abstractmethod where the override is not of an
            abstractmethod.
            Modified from answer https://stackoverflow.com/a/8313042/471376
            """
            def confirm_override(method):
                if method.__name__ not in dir(interface_class):
                    raise NotImplementedError('function "%s" is an @override but that'
                                              ' function is not implemented in base'
                                              ' class %s'
                                              % (method.__name__,
                                                 interface_class)
                                              )
        
                def func():
                    pass
        
                attr = getattr(interface_class, method.__name__)
                if type(attr) is not type(func):
                    raise NotImplementedError('function "%s" is an @override'
                                              ' but that is implemented as type %s'
                                              ' in base class %s, expected implemented'
                                              ' type %s'
                                              % (method.__name__,
                                                 type(attr),
                                                 interface_class,
                                                 type(func))
                                              )
                return method
            return confirm_override
        


        这是它在实践中的样子:

        NotImplementedError "未在基类中实现"

        class A(object):
            # ERROR: `a` is not a implemented!
            pass
        
        class B(A):
            @overrides(A)
            def a(self):
                pass
        

        导致更具描述性的NotImplementedError 错误

        function "a" is an @override but that function is not implemented in base class <class '__main__.A'>
        

        全栈

        Traceback (most recent call last):
          …
          File "C:/Users/user1/project.py", line 135, in <module>
            class B(A):
          File "C:/Users/user1/project.py", line 136, in B
            @overrides(A)
          File "C:/Users/user1/project.py", line 110, in confirm_override
            interface_class)
        NotImplementedError: function "a" is an @override but that function is not implemented in base class <class '__main__.A'>
        


        NotImplementedError "预期的实现类型"

        class A(object):
            # ERROR: `a` is not a function!
            a = ''
        
        class B(A):
            @overrides(A)
            def a(self):
                pass
        

        导致更具描述性的NotImplementedError 错误

        function "a" is an @override but that is implemented as type <class 'str'> in base class <class '__main__.A'>, expected implemented type <class 'function'>
        

        全栈

        Traceback (most recent call last):
          …
          File "C:/Users/user1/project.py", line 135, in <module>
            class B(A):
          File "C:/Users/user1/project.py", line 136, in B
            @overrides(A)
          File "C:/Users/user1/project.py", line 125, in confirm_override
            type(func))
        NotImplementedError: function "a" is an @override but that is implemented as type <class 'str'> in base class <class '__main__.A'>, expected implemented type <class 'function'>
        




        @mkorpela 答案的好处是检查发生在某些初始化阶段。检查不需要“运行”。参考前面的示例,class B 从未初始化(B()),但NotImplementedError 仍将引发。这意味着overrides 错误会被更快地发现。

        【讨论】:

        • 嘿!这看起来很有趣。你能考虑对我的 ipromise 项目提出拉取请求吗?我已经添加了答案。
        • @NeilG 我分叉了 ipromise 项目并编写了一些代码。看起来您基本上已经在overrides.py 中实现了这一点。除了将异常类型从 TypeError 更改为 NotImplementedError 之外,我不确定还有什么可以显着改进的。
        • 嘿!谢谢,我没有检查被覆盖的对象实际上是否具有types.MethodType 类型。你的回答是个好主意。
        【解决方案7】:

        Hear 最简单,在 Jython 下使用 Java 类工作:

        class MyClass(SomeJavaClass):
             def __init__(self):
                 setattr(self, "name_of_method_to_override", __method_override__)
        
             def __method_override__(self, some_args):
                 some_thing_to_do()
        

        【讨论】:

          【解决方案8】:

          就像其他人所说的那样,与 Java 不同,没有 @Overide 标签,但是在上面你可以使用装饰器创建自己的标签,但是我建议使用 getattrib() 全局方法而不是使用内部 dict,这样你会得到如下内容:

          def Override(superClass):
              def method(func)
                  getattr(superClass,method.__name__)
              return method
          

          如果你愿意,你可以在自己的 try catch 中捕获 getattr(),但我认为 getattr 方法在这种情况下更好。

          这也捕获了绑定到一个类的所有项目,包括类方法和变量

          【讨论】:

            【解决方案9】:

            这是一个不需要指定 interface_class 名称的实现。

            import inspect
            import re
            
            def overrides(method):
                # actually can't do this because a method is really just a function while inside a class def'n  
                #assert(inspect.ismethod(method))
            
                stack = inspect.stack()
                base_classes = re.search(r'class.+\((.+)\)\s*\:', stack[2][4][0]).group(1)
            
                # handle multiple inheritance
                base_classes = [s.strip() for s in base_classes.split(',')]
                if not base_classes:
                    raise ValueError('overrides decorator: unable to determine base class') 
            
                # stack[0]=overrides, stack[1]=inside class def'n, stack[2]=outside class def'n
                derived_class_locals = stack[2][0].f_locals
            
                # replace each class name in base_classes with the actual class type
                for i, base_class in enumerate(base_classes):
            
                    if '.' not in base_class:
                        base_classes[i] = derived_class_locals[base_class]
            
                    else:
                        components = base_class.split('.')
            
                        # obj is either a module or a class
                        obj = derived_class_locals[components[0]]
            
                        for c in components[1:]:
                            assert(inspect.ismodule(obj) or inspect.isclass(obj))
                            obj = getattr(obj, c)
            
                        base_classes[i] = obj
            
            
                assert( any( hasattr(cls, method.__name__) for cls in base_classes ) )
                return method
            

            【讨论】:

            • 有点神奇,但使典型用法更容易。你能包括使用例子吗?
            • 使用这个装饰器的平均成本和最坏情况成本是多少,也许可以表示为与@classmethod 或@property 等内置装饰器的比较?
            • @larham1 这个装饰器在分析类定义时执行一次,而不是在每次调用时执行。因此,与程序运行时相比,它的执行成本无关紧要。
            • 感谢PEP 487,这在 Python 3.6 中会更好。
            • 为了得到更好的错误信息:assert any(hasattr(cls, method.__name__) for cls in base_classes), 'Overriden method "{}" was not found in the base class.'.format(方法.__name__)
            【解决方案10】:

            如果您只想将其用于文档目的,您可以定义自己的覆盖装饰器:

            def override(f):
                return f
            
            
            class MyClass (BaseClass):
            
                @override
                def method(self):
                    pass
            

            这实在是令人眼花缭乱,除非您创建 override(f) 的方式实际上是检查覆盖。

            但是,这是 Python,为什么要像 Java 一样写呢?

            【讨论】:

            • 可以通过检查向 override 装饰器添加实际验证。
            • 但是,这是 Python,为什么要把它写成 Java? 因为 Java 中的一些想法很好,值得扩展到其他语言?
            • 因为当您重命名超类中的方法时,很高兴知道某些子类 2 级以下正在覆盖它。当然,这很容易检查,但语言解析器的一点帮助不会有什么坏处。
            • 因为这是个好主意。多种其他语言具有该功能这一事实是没有争议的 - 无论是赞成还是反对。
            【解决方案11】:

            Python 不是 Java。当然,没有真正意义上的编译时检查。

            我认为文档字符串中的注释很多。这允许您的方法的任何用户键入help(obj.method) 并看到该方法是一个覆盖。

            您还可以使用class Foo(Interface) 显式扩展接口,这将允许用户键入help(Interface.method) 以了解您的方法打算提供的功能。

            【讨论】:

            • Java 中@Override 的真正意义不是记录——它是为了在您打算覆盖一个方法但最终定义一个新方法时发现错误(例如,因为您拼错了一个名称; 在 Java 中,也可能因为您使用了错误的签名而发生,但这在 Python 中不是问题 - 但拼写错误仍然是)。
            • @Pavel Minaev:是的,但是拥有文档仍然很方便,特别是如果您使用的 IDE / 文本编辑器没有自动覆盖指示符(Eclipse 的 JDT 巧妙地显示了它们)例如,在行号旁边)。
            • @PavelMinaev 错了。 @Override 的重点之一是除了编译时检查之外的文档。
            • @siamii 我认为对文档的帮助很大,但在我看到的所有官方 Java 文档中,它们仅表明编译时检查的重要性。请证实您声称 Pavel 是“错误的”。
            猜你喜欢
            • 2014-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-06-30
            • 2013-04-11
            • 2020-11-17
            • 2010-11-06
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多