Subclase en sugerencias de tipo
Quiero permitir sugerencias de tipos usando Python 3 para aceptar subclases de una determinada clase. P.ej:
class A:
pass
class B(A):
pass
class C(A):
pass
def process_any_subclass_type_of_A(cls: A):
if cls == B:
# do something
elif cls == C:
# do something else
Ahora al escribir el siguiente código:
process_any_subclass_type_of_A(B)
Recibo una sugerencia de PyCharm IDE: "Se esperaba el tipo A, en su lugar obtuve el tipo [B]".
¿Cómo puedo cambiar las sugerencias de tipo aquí para aceptar cualquier subtipo de A?
Según PEP 484 ("Las expresiones cuyo tipo es un subtipo de un tipo de argumento específico también se aceptan para ese argumento"), ¿entiendo que mi solución (cls: A)
debería funcionar?
Cuando especifica cls: A
, está diciendo que cls
espera una instancia de tipo A
. La sugerencia de tipo para especificar cls
como objeto de clase para el tipo A
(o sus subtipos) utiliza typing.Type
.
from typing import Type
def process_any_subclass_type_of_A(cls: Type[A]):
pass
Del tipo de objetos de clase :
A veces quieres hablar de objetos de clase que heredan de una clase determinada. Esto se puede escribir como
Type[C]
¿dóndeC
hay una clase? En otras palabras, cuandoC
es el nombre de una clase, usarloC
para anotar un argumento declara que el argumento es una instancia deC
(o de una subclase deC
), pero usarType[C]
como argumento la anotación declara que el argumento es un objeto de clase que se deriva deC
( o aC
sí mismo).
Si miramos la Type
descripción del typing
módulo, vemos estos documentos:
Una construcción especial que se puede utilizar para anotar objetos de clase.
Por ejemplo, supongamos que tenemos las siguientes clases::
class User: ... # Abstract base for User classes class BasicUser(User): ... class ProUser(User): ... class TeamUser(User): ...
Y una función que toma un argumento de clase que es una subclase de Usuario y devuelve una instancia de la clase correspondiente::
U = TypeVar('U', bound=User) def new_user(user_class: Type[U]) -> U: user = user_class() # (Here we could write the user object to a database) return user joe = new_user(BasicUser)
En este punto, el verificador de tipos sabe que Joe tiene el tipo BasicUser.
En base a esto, puedo imaginar un ejemplo sintético que reproduce el problema con los errores de sugerencia de tipos en PyCharm.
from typing import Type, Tuple
class BaseClass: ...
class SubClass(BaseClass): ...
class SubSubClass(SubClass): ...
def process(model_instance: BaseClass, model_class: Type[BaseClass]) -> Tuple[BaseClass, BaseClass]:
""" Accepts all of the above classes """
return model_instance, model_class()
class ProcessorA:
@staticmethod
def proc() -> Tuple[SubClass, SubClass]:
""" PyCharm will show an error
`Expected type 'tuple[SubClass, SubClass]', got 'tuple[BaseClass, BaseClass]' instead` """
return process(SubClass(), SubClass)
class ProcessorB:
@staticmethod
def proc() -> Tuple[SubSubClass, SubSubClass]:
""" PyCharm will show an error
`Expected type 'tuple[SubSubClass, SubSubClass]', got 'tuple[BaseClass, BaseClass]' instead` """
return process(SubSubClass(), SubSubClass)
Pero vemos en los documentos Type
que la situación se puede corregir usando TypeVar
el bound
argumento. Luego úselo en lugares donde BaseClass
esté declarado como tipo.
from typing import TypeVar, Type, Tuple
class BaseClass: ...
B = TypeVar('B', bound=BaseClass)
class SubClass(BaseClass): ...
class SubSubClass(SubClass): ...
def process(model_instance: B, model_class: Type[B]) -> Tuple[B, B]:
""" Accepts all of the above classes """
return model_instance, model_class()
class ProcessorA:
@staticmethod
def proc() -> Tuple[SubClass, SubClass]:
return process(SubClass(), SubClass)
class ProcessorB:
@staticmethod
def proc() -> Tuple[SubSubClass, SubSubClass]:
return process(SubSubClass(), SubSubClass)
Esperamos que esto sea útil.
El tipo [A] acepta también la clase misma, que no siempre es necesaria.
Si desea que su función acepte solo subclases, debe utilizar NewType , como
class A:
pass
B = NewType('B', A)
def foo(cls: Type[B]):
...