213

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

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

4

11 回答 11

240

基于这个和 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!!!!!!!
于 2011-11-29T15:07:19.627 回答
30

这是一个不需要指定 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
于 2013-01-31T17:13:53.410 回答
18

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

def override(f):
    return f


class MyClass (BaseClass):

    @override
    def method(self):
        pass

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

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

于 2009-07-22T22:33:35.297 回答
7

即兴创作@mkorpela很好的答案,这里有一个版本

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

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更快地发现错误。

于 2019-02-05T01:37:37.490 回答
4

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

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

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

于 2009-07-22T19:32:09.857 回答
2

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

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

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

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

于 2013-08-12T17:43:44.020 回答
2

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

假设A继承自BCB继承自C

模块ipromise检查:

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

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

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

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

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

于 2019-03-14T00:03:47.753 回答
1

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

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

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

现在,这不会涵盖在超类和实现类中都有实现的情况,例如ABC. 但是即使在接口中没有实现,override也可以在 Java中使用。该解决方案涵盖了这种情况。

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)
于 2021-01-22T17:22:19.320 回答
0

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

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

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

于 2019-09-24T15:30:43.243 回答
0

在 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 类继承但需要所有这些foo,你也需要使用 @Override(尽管使用别名 'Overload' 看起来在这种情况下更好)

于 2020-07-05T21:11:49.100 回答
-1

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()
于 2013-11-25T21:02:37.333 回答