Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MyPy annotation for classmethod returning instance

Tags:

python

mypy

How should I annotate a @classmethod that returns an instance of cls? Here's a bad example:

class Foo(object):     def __init__(self, bar: str):         self.bar = bar      @classmethod     def with_stuff_appended(cls, bar: str) -> ???:         return cls(bar + "stuff") 

This returns a Foo but more accurately returns whichever subclass of Foo this is called on, so annotating with -> "Foo" wouldn't be good enough.

like image 320
taway Avatar asked Jun 19 '17 21:06

taway


People also ask

What is TypeVar?

In short, a TypeVar is a variable you can use in type signatures so you can refer to the same unspecified type more than once, while a NewType is used to tell the type checker that some values should be treated as their own type.

Can I use self in Classmethod?

The @classmethod Decorator This decorator exists so you can create class methods that are passed the actual class object within the function call, much like self is passed to any other ordinary instance method in a class.


1 Answers

The trick is to explicitly add an annotation to the cls parameter, in combination with TypeVar, for generics, and Type, to represent a class rather than the instance itself, like so:

from typing import TypeVar, Type  # Create a generic variable that can be 'Parent', or any subclass. T = TypeVar('T', bound='Parent')  class Parent:     def __init__(self, bar: str) -> None:         self.bar = bar      @classmethod     def with_stuff_appended(cls: Type[T], bar: str) -> T:         # We annotate 'cls' with a typevar so that we can         # type our return type more precisely         return cls(bar + "stuff")  class Child(Parent):     # If you're going to redefine __init__, make sure it     # has a signature that's compatible with the Parent's __init__,     # since mypy currently doesn't check for that.      def child_only(self) -> int:         return 3  # Mypy correctly infers that p is of type 'Parent', # and c is of type 'Child'. p = Parent.with_stuff_appended("10") c = Child.with_stuff_appended("20")  # We can verify this ourself by using the special 'reveal_type' # function. Be sure to delete these lines before running your # code -- this function is something only mypy understands # (it's meant to help with debugging your types). reveal_type(p)  # Revealed type is 'test.Parent*' reveal_type(c)  # Revealed type is 'test.Child*'  # So, these all typecheck print(p.bar) print(c.bar) print(c.child_only()) 

Normally, you can leave cls (and self) unannotated, but if you need to refer to the specific subclass, you can add an explicit annotation. Note that this feature is still experimental and may be buggy in some cases. You may also need to use the latest version of mypy cloned from Github, rather then what's available on pypi -- I don't remember if that version supports this feature for classmethods.

like image 127
Michael0x2a Avatar answered Sep 17 '22 11:09

Michael0x2a