例如,在Java中,@Override注释不仅提供了重写的编译时检查,而且可以生成优秀的自文档代码。
我只是在寻找文档(尽管如果它是一些检查器(如pylint)的指示器,那是额外的奖励)。我可以在某处添加注释或文档字符串,但在Python中指示重写的惯用方法是什么?
例如,在Java中,@Override注释不仅提供了重写的编译时检查,而且可以生成优秀的自文档代码。
我只是在寻找文档(尽管如果它是一些检查器(如pylint)的指示器,那是额外的奖励)。我可以在某处添加注释或文档字符串,但在Python中指示重写的惯用方法是什么?
当前回答
这里是一个没有注释的不同解决方案。
它有一个稍微不同的目标。其他建议的解决方案检查给定的方法是否实际覆盖了父方法,而这个解决方案检查是否所有的父方法都被覆盖了。
你不必引发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}')
其他回答
在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:
...
下面是一个不需要指定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'会更好看)
基于@mkorpela的精彩回答,我写了一个类似的包(ipromise pypi github),它做了更多的检查:
假设A继承了B和C, B继承了C。
模块ipromise检查:
If A.f overrides B.f, B.f must exist, and A must inherit from B. (This is the check from the overrides package). You don't have the pattern A.f declares that it overrides B.f, which then declares that it overrides C.f. A should say that it overrides from C.f since B might decide to stop overriding this method, and that should not result in downstream updates. You don't have the pattern A.f declares that it overrides C.f, but B.f does not declare its override. You don't have the pattern A.f declares that it overrides C.f, but B.f declares that it overrides from some D.f.
它还具有用于标记和检查实现抽象方法的各种特性。
如果你只是为了文档的目的,你可以定义你自己的覆盖装饰器:
def override(f):
return f
class MyClass (BaseClass):
@override
def method(self):
pass
这实际上只是花瓶,除非你创建override(f)的方式实际上是检查重写。
但是,这是Python,为什么要写成Java呢?