Comment annoter un @classmethod
qui renvoie une instance de cls
? Voici un mauvais exemple:
class Foo(object):
def __init__(self, bar: str):
self.bar = bar
@classmethod
def with_stuff_appended(cls, bar: str) -> ???:
return cls(bar + "stuff")
Cela renvoie un Foo
mais retourne plus précisément la sous-classe de Foo
qui est appelée, donc annoter avec -> "Foo"
ne serait pas assez bon.
L'astuce consiste à ajouter explicitement une annotation au paramètre cls
, en combinaison avec TypeVar
, pour génériques , et Type
, à représente une classe plutôt que l'instance elle-même , comme ceci:
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())
Normalement, vous pouvez laisser cls
(et self
) sans annotation, mais si vous devez vous référer à la sous-classe spécifique, vous pouvez ajouter un annotation explicite . Notez que cette fonctionnalité est encore expérimentale et peut être boguée dans certains cas. Vous devrez peut-être également utiliser la dernière version de mypy clonée à partir de Github, plutôt que ce qui est disponible sur pypi - je ne me souviens pas si cette version prend en charge cette fonctionnalité pour les méthodes de classe.
Juste pour être complet, dans Python 3.7 vous pouvez utiliser le postponed evaluation of annotations
comme défini dans PEP56 en important from __future__ import annotations
au début du fichier.
Ensuite, pour votre code, il ressemblerait
from __future__ import annotations
class Foo(object):
def __init__(self, bar: str):
self.bar = bar
@classmethod
def with_stuff_appended(cls, bar: str) -> Foo:
return cls(bar + "stuff")