@Override
注释不仅提供了覆盖的编译时检查,还可以生成极好的自描述代码。我只是在寻找文档(虽然如果它能作为像pylint这样的检查器的指示符,那就更好了)。我可以在某个地方添加注释或文档字符串,但在Python中指示覆盖的惯用方式是什么?
@Override
注释不仅提供了覆盖的编译时检查,还可以生成极好的自描述代码。基于此和fwc的答案,我创建了一个可以通过pip安装的软件包https://github.com/mkorpela/overrides
时不时地,我会来到这里看看这个问题。主要是在我们的代码库中再次看到相同的bug后:有人忘记在重命名“interface”中的方法时实现类……
嗯,Python不是Java,但Python很强大——显式比隐式更好——在现实世界中有真正具体的案例,在这些案例中这个东西会帮助我。
因此,这里是overrides装饰器的草图。这将检查作为参数给定的类是否与被装饰的方法具有相同的方法(或其他内容)名称。
如果你能想到更好的解决方案,请在这里发布!
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!!!!!!!
from typing import override
class Parent:
def foo(self) -> int:
return 1
def bar(self, x: str) -> str:
return x
class Child(Parent):
@override
def foo(self) -> int:
return 2
@override
def baz() -> int: # Type check error: no matching signature in ancestor
return 1
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
如果你只是想用于文档目的,你可以定义自己的重写修饰器:
def override(f):
return f
class MyClass (BaseClass):
@override
def method(self):
pass
这真的只是一些华而不实的东西,除非你创建了一个检查覆盖的 override(f)。
但这是Python啊,为什么要像Java一样写呢?
override
装饰器中。 - Erik Kaplun在 @mkorpela 的优秀答案的基础上进行改进,这里提供了更精确的检查、命名和错误提示。
def overrides(interface_class):
"""
Function override annotation.
Corollary to @abc.abstractmethod where the override is not of an
abstractmethod.
Modified from answer https://dev59.com/G3M_5IYBdhLWcg3w6X5e#8313042
"""
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
错误被更早地捕获。
overrides.py
中实现了这个功能。除了将异常类型从TypeError
更改为NotImplementedError
之外,我不确定我还能做出什么其他显著的改进。 - JamesThomasMoontypes.MethodType
。你在答案中提出的建议很好。 - Neil GPython不是Java。当然,没有编译时检查这样的东西。
我认为在文档字符串中加上注释就足够了。这样可以让任何使用你方法的用户键入help(obj.method)
并查看该方法是一个重写。
你也可以使用class Foo(Interface)
显式地扩展一个接口,这将允许用户键入help(Interface.method)
来了解您的方法所提供的功能。
@Override
的真正作用并不是为了文档记录,而是在你意图重写一个方法但实际上定义了一个新方法时(例如拼写错误),帮助你捕捉这个错误。在 Java 中,可能会发生因为使用了错误的签名而导致的问题,但在 Python 中不存在这个问题——不过拼写错误仍然是需要注意的。 - Pavel Minaev@Override
的主要作用之一是除了编译时检查外还有文档说明的作用。 - siamii像其他人所说的,与Java不同,Python中没有@Overide标记,但是您可以使用装饰器创建自己的标记。然而,我建议使用getattrib()全局方法,而不是使用内部字典,这样您将获得以下内容:
def Override(superClass):
def method(func)
getattr(superClass,method.__name__)
return method
A
继承自 B
和 C
,B
继承自 C
。如果 A.f
覆盖了 B.f
,那么 B.f
必须存在,并且 A
必须继承自 B
。(这是 overrides 包中的检查)。
您没有模式 A.f
声明它覆盖了 B.f
,然后声明它覆盖了 C.f
。A
应该说它从 C.f
覆盖,因为 B
可能决定停止覆盖此方法,这不应导致下游更新。
您没有模式 A.f
声明它覆盖了 C.f
,但 B.f
没有声明其覆盖。
您没有模式 A.f
声明它覆盖了 C.f
,但 B.f
声明它从某个 D.f
覆盖。
此外,它还具有各种功能来标记和检查实现抽象方法。
从Python 3.6开始,@override提供的功能可以通过Python的描述符协议轻松实现,即使用set_name方法:
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}")
class AbstractShoppingCartService:
def add_item(self, request: AddItemRequest) -> Cart:
...
class ShoppingCartService(AbstractShoppingCartService):
@override
def add_item(self, request: AddItemRequest) -> Cart:
...
MyFoobar
),你定义一个接口(一个协议),其中包含你的实现所有方法和字段的签名,我们称之为IFoobar
。myFoobar: IFoobar = MyFoobar()
。现在,如果你使用一个在接口中缺失的字段/方法,Mypy会在使用时抱怨(即使它在运行时可行!)。如果你未在实现中实现接口中的方法,Mypy也会抱怨。如果你实现了接口中不存在的内容,Mypy不会抱怨。但这种情况很少见,因为接口定义是紧凑且易于审查的。你将无法实际使用该代码,因为Mypy会抱怨。ABC
的情况。但是,在 Java 中即使没有接口实现,也会使用 override
。这个解决方案覆盖了这种情况。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
结果?还需要指定版本号和工具(我认为 mypy
是这个问题线程中的新工具)吗?我认为这样会使你的回答更加自包含。 - Bluu
@override
装饰器,并且可以从typing
导入,例如from typing import override
https://docs.python.org/3.12/library/typing.html#typing.override。 - Yogev Neumann