Say I already have a method with type annotations:
class Shape:
def area(self) -> float:
raise NotImplementedError
Which I will then subclass multiple times:
class Circle:
def area(self) -> float:
return math.pi * self.radius ** 2
class Rectangle:
def area(self) -> float:
return self.height * self.width
As you can see, I'm duplicating the -> float
quite a lot. Say I have 10 different shapes, with multiple methods like this, some of which contain parameters too. Is there a way to just "copy" the annotation from the parent class, similar to what functools.wraps()
does with docstrings?
It is what overriding for. The overridden method shall not be accessible from outside of the classes at all. But you can call it within the child class itself. to call a super class method from within a sub class you can use the super keyword.
@Override @Override annotation informs the compiler that the element is meant to override an element declared in a superclass. Overriding methods will be discussed in Interfaces and Inheritance. While it is not required to use this annotation when overriding a method, it helps to prevent errors.
The @Override annotation indicates that the child class method is over-writing its base class method. It extracts a warning from the compiler if the annotated method doesn't actually override anything. It can improve the readability of the source code.
On the Code menu, click Override methods Ctrl+O . Alternatively, you can right-click anywhere in the class file, then click Generate Alt+Insert , and select Override methods. Select the methods to override (hold the Shift or Ctrl key to perform a multiple select).
This might work, though I'm sure to miss the edge cases, like additional arguments:
from functools import partial, update_wrapper
def annotate_from(f):
return partial(update_wrapper,
wrapped=f,
assigned=('__annotations__',),
updated=())
which will assign "wrapper" function's __annotations__
attribute from f.__annotations__
(keep in mind that it is not a copy).
According to documents the update_wrapper
function's default for assigned includes __annotations__
already, but I can see why you'd not want to have all the other attributes assigned from wrapped.
With this you can then define your Circle
and Rectangle
as
class Circle:
@annotate_from(Shape.area)
def area(self):
return math.pi * self.radius ** 2
class Rectangle:
@annotate_from(Shape.area)
def area(self):
return self.height * self.width
and the result
In [82]: Circle.area.__annotations__
Out[82]: {'return': builtins.float}
In [86]: Rectangle.area.__annotations__
Out[86]: {'return': builtins.float}
As a side effect your methods will have an attribute __wrapped__
, which will point to Shape.area
in this case.
A less standard (if you can call the above use of update_wrapper standard) way to accomplish handling of overridden methods can be achieved using a class decorator:
from inspect import getmembers, isfunction, signature
def override(f):
"""
Mark method overrides.
"""
f.__override__ = True
return f
def _is_method_override(m):
return isfunction(m) and getattr(m, '__override__', False)
def annotate_overrides(cls):
"""
Copy annotations of overridden methods.
"""
bases = cls.mro()[1:]
for name, method in getmembers(cls, _is_method_override):
for base in bases:
if hasattr(base, name):
break
else:
raise RuntimeError(
'method {!r} not found in bases of {!r}'.format(
name, cls))
base_method = getattr(base, name)
method.__annotations__ = base_method.__annotations__.copy()
return cls
and then:
@annotate_overrides
class Rectangle(Shape):
@override
def area(self):
return self.height * self.width
Again, this will not handle overriding methods with additional arguments.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With