在Python中,我如何指示I'm覆盖一个方法?

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

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

127002 次浏览

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

我认为文档字符串中的注释就足够了。这允许你的方法的任何用户输入help(obj.method),并看到方法是一个覆盖。

你也可以显式地用class Foo(Interface)扩展接口,这将允许用户输入help(Interface.method)来了解你的方法想要提供的功能。

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

def override(f):
return f




class MyClass (BaseClass):


@override
def method(self):
pass

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

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

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

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

好吧,Python不是Java,但Python有强大的功能——显式比隐式好——在现实世界中,有一些真实的具体案例,这个东西会帮助我。

这是overrides decorator的草图。这将检查作为参数给出的类是否与被修饰的方法具有相同的方法(或其他东西)名称。

如果你能想到一个更好的解决方案,请张贴在这里!

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!!!!!!!

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

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

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

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

此外,它还捕获绑定到类的所有项,包括类方法和变量

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

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

基于@mkorpela的回答很棒,我写了一个类似的包(ipromise pypi github),它执行更多的检查:

假设A继承自BC,则B继承自C

模块ipromise检查:

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

  • 你没有这样的模式:A.f声明它覆盖B.f,而B.f又声明它覆盖C.fA应该说它覆盖了C.f,因为B可能决定停止覆盖这个方法,这应该不会导致下游更新。

  • 你没有这样的模式:A.f声明它覆盖C.f,但是B.f没有声明它的覆盖。

  • 你没有这样的模式:A.f声明它覆盖C.f,但是B.f声明它覆盖一些D.f

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

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

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

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

在Python 2.6+和Python 3.2+中,你可以这样做(实际模拟一下, Python不支持函数重载,子类自动覆盖父类的方法)。我们可以使用decorator。但首先,请注意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)

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

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

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

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

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

在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方法基来获得它的父类。

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

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

使用i就像这样简单:

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




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

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

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

你不必引发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}')