在Python中,如何指示我要覆盖方法?


173

例如,在Java中,@Override注释不仅提供覆盖的编译时检查,而且还提供了出色的自记录代码。

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


13
换句话说,您从未表明要覆盖某个方法吗?留给读者自己弄清楚吗?
09年

2
是的,我知道来自编译语言的情况似乎容易出错,但是您只需要接受即可。实际上,我并没有发现这是个大问题(就我而言,Ruby,不是Python,而是相同的想法)
Ed S.

当然可以 Triptych的答案和mkorpela的答案都很简单,我很喜欢,但是后者的显式过分内含的精神,并且可以避免错误地赢得了理解。
Bluu

1
它不是直接相同的,但是抽象基类将检查所有抽象方法是否已被子类覆盖。当然,如果您要覆盖具体的方法,这无济于事。
letmaik

Answers:


206

基于此和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!!!!!!!

17
太棒了 我第一次尝试时发现了一个拼写错误的错误。荣誉
Christopher Bruns,2012年

7
mfbutner:每次执行该方法时都不会调用它-仅在创建方法时才调用。
mkorpela 2014年

3
这对于文档字符串也很好!overrides如果覆盖方法没有自己的方法之一,则可以复制覆盖方法的文档字符串。
letmaik

5
@mkorpela,嘿,您的这段代码应该在python默认的lib系统中。为什么不将其放入点子系统?:P

5
@mkorpela:哦,我建议通知python核心开发人员不要使用此软件包,他们可能要考虑不要在内核python系统上添加覆盖装饰器。:)

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

2
有点神奇,但使典型用法变得容易得多。您可以包括用法示例吗?
2013年

使用此装饰器的平均和最坏情况的费用是多少,可能表示为与@classmethod或@property等内置装饰器的比较?
larham1 2013年

4
@ larham1此修饰器在分析类定义时执行一次,而不是在每次调用时执行一次。因此,与程序运行时相比,它的执行成本无关紧要。
Abgan 2014年

多亏了PEP 487,这在Python 3.6中会更好。
Neil G

为了获得更好的错误消息:断言任何(hasattr(CLS,方法.__ name__)在base_classes CLS),“覆盖方法‘{}’并没有在基类中找到。”格式(方法.__ name__)
伊万·科夫通

14

如果仅出于文档目的而希望这样做,则可以定义自己的替代装饰器:

def override(f):
    return f


class MyClass (BaseClass):

    @override
    def method(self):
        pass

除非您以一种实际上检查替代的方式创建override(f),否则这实际上只是让人眼花can乱。

但是,这就是Python,为什么要像Java一样编写它?


2
可以通过检查对override装饰器添加实际的验证。
埃里克·卡普伦

70
但是,这就是Python,为什么要像Java一样编写它?因为Java中的某些想法很好,值得扩展到其他语言吗?
Piotr Dobrogost

9
因为当您在超类中重命名方法时,很高兴知道某些向下2级的子类将其覆盖。当然,这很容易检查,但是来自语言解析器的一点帮助也没有什么坏处。
Abgan

4
因为这是个好主意。多种其他语言都具有该功能的事实不成立-支持或反对。
sfkleach

6

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

我认为文档字符串中的注释很多。这允许您的方法的任何用户键入help(obj.method)并看到该方法是替代。

您还可以使用显式扩展一个接口class Foo(Interface),该接口允许用户键入help(Interface.method)以了解有关您的方法旨在提供的功能的想法。


57
@OverrideJava 的真正目的不是记录文档,而是在您打算覆盖某个方法时发现一个错误,但最终却定义了一个新方法(例如,因为拼写错误的名称;在Java中,也可能是因为您使用了错误的签名,但这在Python中不是问题-但仍然存在拼写错误)。
帕维尔·米纳夫

2
@ Pavel Minaev:是的,但是拥有文档仍然很方便,特别是如果您使用的IDE /文本编辑器没有自动替代的指示符(例如,Eclipse的JDT在行号旁边巧妙地显示它们)。
Tuukka Mustonen

2
@PavelMinaev错误。@Override除了编译时间检查外,文档的主要内容之一是文档。
siamii

6
@siamii我认为对文档的帮助很大,但是在我看到的所有官方Java文档中,它们仅表明编译时检查的重要性。请证实您的说法,帕维尔(Pavel)是“错误的”。
Andrew Mellinger 2014年

5

即兴在@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答案,很棒的事情是检查发生在某些初始化阶段。该检查不需要“运行”。参考前面的示例,class B它从未被初始化(B()),但NotImplementedError仍然会上升。这意味着overrides可以更快地发现错误。


嘿! 这看起来很有趣。您可以考虑对我的ipromise项目进行拉取请求吗?我已经添加了答案。
尼尔·G

@NeilG我分叉了ipromise项目并进行了编码。似乎您已经在内实现了此功能overrides.py。我不知道还有什么我可以显著除了从改变异常类型提高TypeErrorNotImplementedError
JamesThomasMoon1979年

嘿! 谢谢,我没有检查覆盖的对象是否实际具有type types.MethodType。那是你回答的好主意。
尼尔·G

2

就像其他人所说的,与Java不同,这里没有@Overide标记,但是您可以使用装饰器创建自己的标记,但是我建议使用getattrib()全局方法,而不要使用内部dict,这样您将获得以下内容:

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

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

同样,这会捕获绑定到类的所有项目,包括类方法和可变项


2

基于@mkorpela的出色回答,我编写了一个类似的软件包(ipromise pypi github),该软件包可以进行更多检查:

假设A从继承BCB从继承C

ipromise模块检查:

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

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

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

  • 您没有模式A.f声明它被覆盖C.f,但是B.f声明它被某些模式覆盖D.f

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


0

听觉是最简单的,并且可以在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()

0

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

有关源代码,请参见:https : //github.com/fireuser909/override

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


0

在Python 2.6+和Python 3.2+中,您可以做到(实际上是模拟它,Python不支持函数重载,并且子类会自动覆盖parent的方法)。我们可以为此使用装饰器。但是首先,请注意,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没有直接的函数重载,因此即使Class B不继承自Class A但需要所有这些foos,也需要使用@Override(尽管使用别名'Overload'看起来在那种情况下更好)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.