标签归档:self-documenting-code

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

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

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

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

In Java, for example, the @Override annotation not only provides compile-time checking of an override but makes for excellent self-documenting code.

I’m just looking for documentation (although if it’s an indicator to some checker like pylint, that’s a bonus). I can add a comment or docstring somewhere, but what is the idiomatic way to indicate an override in Python?


回答 0

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

Based on this and fwc:s answer I created a pip installable package https://github.com/mkorpela/overrides

From time to time I end up here looking at this question. Mainly this happens after (again) seeing the same bug in our code base: Someone has forgotten some “interface” implementing class while renaming a method in the “interface”..

Well Python ain’t Java but Python has power — and explicit is better than implicit — and there are real concrete cases in the real world where this thing would have helped me.

So here is a sketch of overrides decorator. This will check that the class given as a parameter has the same method (or something) name as the method being decorated.

If you can think of a better solution please post it here!

def overrides(interface_class):
    def overrider(method):
        assert(method.__name__ in dir(interface_class))
        return method
    return overrider

It works as follows:

class MySuperInterface(object):
    def my_method(self):
        print 'hello world!'


class ConcreteImplementer(MySuperInterface):
    @overrides(MySuperInterface)
    def my_method(self):
        print 'hello kitty!'

and if you do a faulty version it will raise an assertion error during class loading:

class ConcreteFaultyImplementer(MySuperInterface):
    @overrides(MySuperInterface)
    def your_method(self):
        print 'bye bye!'

>> AssertionError!!!!!!!

回答 1

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

Here’s an implementation that doesn’t require specification of the interface_class name.

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

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

def override(f):
    return f


class MyClass (BaseClass):

    @override
    def method(self):
        pass

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

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

If you want this for documentation purposes only, you can define your own override decorator:

def override(f):
    return f


class MyClass (BaseClass):

    @override
    def method(self):
        pass

This is really nothing but eye-candy, unless you create override(f) in such a way that is actually checks for an override.

But then, this is Python, why write it like it was Java?


回答 3

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

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

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

Python ain’t Java. There’s of course no such thing really as compile-time checking.

I think a comment in the docstring is plenty. This allows any user of your method to type help(obj.method) and see that the method is an override.

You can also explicitly extend an interface with class Foo(Interface), which will allow users to type help(Interface.method) to get an idea about the functionality your method is intended to provide.


回答 4

即兴在@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可以更快地发现错误。

Improvising on @mkorpela great answer, here is a version with

more precise checks, naming, and raised Error objects

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


Here is what it looks like in practice:

NotImplementedErrornot implemented in base class

class A(object):
    # ERROR: `a` is not a implemented!
    pass

class B(A):
    @overrides(A)
    def a(self):
        pass

results in more descriptive NotImplementedError error

function "a" is an @override but that function is not implemented in base class <class '__main__.A'>

full stack

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


NotImplementedErrorexpected implemented type

class A(object):
    # ERROR: `a` is not a function!
    a = ''

class B(A):
    @overrides(A)
    def a(self):
        pass

results in more descriptive NotImplementedError error

function "a" is an @override but that is implemented as type <class 'str'> in base class <class '__main__.A'>, expected implemented type <class 'function'>

full stack

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




The great thing about @mkorpela answer is the check happens during some initialization phase. The check does not need to be “run”. Referring to the prior examples, class B is never initialized (B()) yet the NotImplementedError will still raise. This means overrides errors are caught sooner.


回答 5

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

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

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

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

Like others have said unlike Java there is not @Overide tag however above you can create your own using decorators however I would suggest using the getattrib() global method instead of using the internal dict so you get something like the following:

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

If you wanted to you could catch getattr() in your own try catch raise your own error but I think getattr method is better in this case.

Also this catches all items bound to a class including class methods and vairables


回答 6

基于@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

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

Based on @mkorpela’s great answer, I’ve written a similar package (ipromise pypi github) that does many more checks:

Suppose A inherits from B and C, B inherits from C.

Module ipromise checks that:

  • 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.

It also has various features for marking and checking implementing an abstract method.


回答 7

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

Hear is simplest and working under Jython with Java classes:

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

回答 8

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

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

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

Not only did the decorator I made check if the name of the overriding attribute in is any superclass of the class the attribute is in without having to specify a superclass, this decorator also check to ensure the overriding attribute must be the same type as the overridden attribute. Class Methods are treated like methods and Static Methods are treated like functions. This decorator works for callables, class methods, static methods, and properties.

For source code see: https://github.com/fireuser909/override

This decorator only works for classes that are instances of override.OverridesMeta but if your class is an instance of a custom metaclass use the create_custom_overrides_meta function to create a metaclass that is compatible with the override decorator. For tests, run the override.__init__ module.


回答 9

在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’看起来在那种情况下更好)

In Python 2.6+ and Python 3.2+ you can do it (Actually simulate it, Python doesn’t support function overloading and child class automatically overrides parent’s method). We can use Decorators for this. But first, note that Python’s @decorators and Java’s @Annotations are totally different things. The prior one is a wrapper with concrete code while later one is a flag to compiler.

For this, first do 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)

output:

foo in A
foo in B
foo in B; arg = 4
foo in B; arg = ('Wheee', 3.14)

Note that you must have to use decorator here with parenthesis

One thing to remember is that since Python doesn’t have function overloading directly, so even if Class B don’t inherit from Class A but needs all those foos than also you need to use @Override (though using alias ‘Overload’ will look better in that case)