Question or problem about Python programming:
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?
How to solve the problem:
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!!!!!!!
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).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=overrides, stack=inside class def'n, stack=outside class def'n derived_class_locals = stack.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] 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
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?
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.
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:
NotImplementedError “not 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
function "a" is an @override but that function is not implemented in base class
Traceback (most recent call last): … File "C:/Users/user1/project.py", line 135, in 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
NotImplementedError “expected 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
function "a" is an @override but that is implemented as type in base class , expected implemented type
Traceback (most recent call last): … File "C:/Users/user1/project.py", line 135, in 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 in base class , expected implemented type
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.