例如,在Java中,@Override注释不仅提供了重写的编译时检查,而且可以生成优秀的自文档代码。

我只是在寻找文档(尽管如果它是一些检查器(如pylint)的指示器,那是额外的奖励)。我可以在某处添加注释或文档字符串,但在Python中指示重写的惯用方法是什么?


当前回答

即兴对@mkorpela的伟大回答,这里有一个版本

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

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 answer的伟大之处在于检查发生在初始化阶段。检查不需要“运行”。参考前面的例子,类B从未初始化(B()),但NotImplementedError仍然会引发。这意味着可以更快地捕获覆盖错误。

其他回答

这里是一个没有注释的不同解决方案。

它有一个稍微不同的目标。其他建议的解决方案检查给定的方法是否实际覆盖了父方法,而这个解决方案检查是否所有的父方法都被覆盖了。

你不必引发AssertionError,但可以打印警告或在生产环境中通过检查__init__中的env并在检查之前返回它。

class Parent:

    def a():
        pass

    def b():
        pass

class Child(Overrides, Parent):

    def a()

    # raises an error, as b() is not overridden


class Overrides:

    def __init__(self):
        # collect all defined methods of all base-classes
        bases = [b for b in self.__class__.__bases__ if b != Overrides]
        required_methods = set()
        for base in bases:
            required_methods = required_methods.union(set([f for f in dir(base) if not f.startswith('_')]))
        
        # check for each method in each base class (in required_methods)
        # if the class, that inherits `Overrides` implements them all
        missing = []
        # me is the fully qualified name of the CLASS, which inherits 
        # `Overrides`
        me = self.__class__.__qualname__
        for required_method in required_methods:

            # The method can be either defined in the parent or the child 
            # class. To check it, we get a reference to the method via 
            # getattr
            try:
                found = getattr(self, required_method)
            except AttributeError:
                # this should not happen, as getattr returns the method in 
                # the parent class if it is not defined in the cild class.
                # It has to be in a parent class, as the required_methods 
                # is a union of all base-class methods.
                missing.append(required_method)
                continue
            
            # here is, where the magic happens.
            # found is a reference to a method, and found.__qualname__ is
            # the full-name of the METHOD. Remember, that me is the full
            # name of the class. 
            # We want to check, where the method is defined. If it is 
            # defined in an parent class, we did no override it, thus it 
            # is missing. 
            # If we did not override, the __qualname__ is Parent.method
            # If we did override it, the __qualname__ is Child.method
            # With this fact, we can determine if the class, which uses
            # `Override` did implement it.
            if not found.__qualname__.startswith(me + '.'):
                missing.append(required_method)

        # Maybe a warning would be enough here
        if missing != []:
            raise AssertionError(f'{me} did not override these methods: {missing}')

如果你只是为了文档的目的,你可以定义你自己的覆盖装饰器:

def override(f):
    return f


class MyClass (BaseClass):

    @override
    def method(self):
        pass

这实际上只是花瓶,除非你创建override(f)的方式实际上是检查重写。

但是,这是Python,为什么要写成Java呢?

下面是一个不需要指定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

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

为此,首先执行pip安装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)

注意,这里必须使用带有括号的decorator

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

在python 3.6及以上版本中,@override提供的功能可以使用python的描述符协议轻松实现,即set_name dunder方法:

class override:
    def __init__(self, func):
       self._func = func
       update_wrapper(self, func)

    def __get__(self, obj, obj_type):
        if obj is None:
            return self
        return self._func

    def __set_name__(self, obj_type, name):
        self.validate_override(obj_type, name)

    def validate_override(self, obj_type, name):
        for parent in obj_type.__bases__:
            func = parent.__dict__.get(name, None)
            if callable(func):
                return
        else:
            raise NotImplementedError(f"{obj_type.__name__} does not override {name}")

注意,这里的set_name是在定义包装类之后调用的,我们可以通过调用包装类的dunder方法基来获得它的父类。

对于它的父类,我们希望检查包装的函数是否在类中通过实现

检查函数名是否在类字典中 它是可调用的


使用i就像这样简单:

class AbstractShoppingCartService:
    def add_item(self, request: AddItemRequest) -> Cart:
        ...


class ShoppingCartService(AbstractShoppingCartService):
    @override
    def add_item(self, request: AddItemRequest) -> Cart:
        ...