How to type annotate overrided methods in a subclass?

2024/10/18 13:01:10

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 ** 2class 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?

Answer

This might work, though I'm sure to miss the edge cases, like additional arguments:

from functools import partial, update_wrapperdef 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 ** 2class 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, signaturedef override(f):"""Mark method overrides."""f.__override__ = Truereturn fdef _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):breakelse: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):@overridedef area(self):return self.height * self.width

Again, this will not handle overriding methods with additional arguments.

https://en.xdnf.cn/q/72937.html

Related Q&A

Import Error: No module named pytz after using easy_install

Today is my first day at Python and have been going through problems. One that I was working on was, "Write a short program which extracts the current date and time from the operating system and p…

Python catch exception pandas.errors.ParserError: Error tokenizing data. C error

I am facing problem with my malfunction csv input file whole reading and which i can deal with by adding "error_bad_lines=False" in my read_csv func to remove those.But i need to report these…

Nested tags in BeautifulSoup - Python

Ive looked at many examples on websites and on stackoverflow but I couldnt find a universal solution to my question. Im dealing with a really messy website and Id like to scrape some data. The markup l…

How do I check if a string is a negative number before passing it through int()?

Im trying to write something that checks if a string is a number or a negative. If its a number (positive or negative) it will passed through int(). Unfortunately isdigit() wont recognize it as a numbe…

openpyxl chage font size of title y_axis.title

I am currently struggling with changing the font of y axis title & the charts title itself.I have tried to create a font setting & applying it to the titles - with no luck what so ever. new_cha…

Combination of all possible cases of a string

I am trying to create a program to generate all possible capitalization cases of a string in python. For example, given abcedfghij, I want a program to generate: Abcdefghij ABcdef.. . . aBcdef.. . ABCD…

How to change download directory location path in Selenium using Chrome?

Im using Selenium in Python and Im trying to change the download path. But either this: prefs = {"download.default_directory": "C:\\Users\\personal\\Downloads\\exports"} options.add…

Keras, TensorFlow : TypeError: Cannot interpret feed_dict key as Tensor

I am trying to use keras fune-tuning to develop image classify applications. I deployed that application to a web server and the image classification is succeeded.However, when the application is used …

How to get matplotlib to place lines accurately?

By default, matplotlib plot can place lines very inaccurately.For example, see the placement of the left endpoint in the attached plot. Theres at least a whole pixel of air that shouldnt be there. In f…

Using Flask as pass through proxy for file upload?

Its for app engines blobstore since its upload interface generates a temporary endpoint every time. Id like to take the comlexity out of frontend, Flask would take the post request and forward it to th…