Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to alias or subclass the Annotated type in Python and pass mypy validation?

These are my attempts so far, using Python 3.10:

from typing import Annotated, NewType, TypeAlias

AsTypeAlias: TypeAlias = Annotated
# Annotated[...] must have exactly one type argument and at least one annotation   

AsNewType = NewType('AsNewType', Annotated)  
# Argument 2 to NewType(...) must be a valid type; Annotated[...] must have exactly one type argument and at least one annotation

AsVariableAssignment = Annotated
# no error 
Test: AsVariableAssignment[str, 'annotation']  
# Variable "annotation_test.AsVariableAssignment" is not valid as a type;  Name "annotation" is not defined

class AsSubClass(Annotated):  
    pass
#  INTERNAL ERROR - mypy crashes

Am I trying to do something that I shouldn't be doing?

like image 223
Supertod Avatar asked Oct 24 '25 18:10

Supertod


1 Answers

Just like some other types in the module typing, Annotate is not a regular type, but rather a special form that can be used as a type in annotations with []. Without [], Annotation is not a valid type. You cannot alias it, and you should not subclass it.

TypeAlias

TypeAlias allows you explicitly alias a type, while Annotated is not.

AsTypeAlias: TypeAlias = Annotated
# Annotated[...] must have exactly one type argument and at least one annotation   

NewType

This is invalid for the same reason. Annotated is not a valid type without [].

AsNewType = NewType('AsNewType', Annotated)  
# Argument 2 to NewType(...) must be a valid type; Annotated[...] must have exactly one type argument and at least one annotation

Implicit Definition of a Type Alias

In Python, you can either explicitly create a type alias (with TypeAlias in PEP 613) or implicitly via Alias = SomeType.

One crucial task for the type checker for the latter case is figuring out the semantic =. Is it a regular variable assignment or an implicit type alias definition?

Consider the different between

MyInt = int

# No error
def foo(a: MyInt): ...

and

MyInt = 5

# Variable "alt.MyInt" is not valid as a type
def foo(a: MyInt): ...

The first example implicitly create a type alias, while the second one creates a variable. This seems trivial, but it in fact reflects that the type checker decides whether MyInt should be a type alias or not by checking the type of the expression on the right hand side of the assignment. This is also the case when we are trying to do

AsVariableAssignment = Annotated

mypy needs to know if the RHS is a valid type or not. It handles special forms like Annotated as a special case.

Only Tuple, Callable, Any are special forms that are valid types without [], and Annotated is not one of them. Therefore, mypy infers that AsVariableAssignment = Annotated is a variable assignment as Annotated is not a valid type, leading to the error Variable "annotation_test.AsVariableAssignment" is not valid as a type since it considers AsVariableAssignment as a variable.

'annotation' Is Not Literal['annotation]

Mypy thinks 'annotation' is a string type, so it looks for the definition of a type that is named annotation, in this case, you should use Literal['annotation'] instead. This error has nothing to do with Annotated.

AsVariableAssignment = Annotated
# no error 
Test: AsVariableAssignment[str, 'annotation']  
# Variable "annotation_test.AsVariableAssignment" is not valid as a type;  Name "annotation" is not defined

Subclassing

Many special forms cannot be subclassed, and mypy should normally give an error like this:

# error: Invalid base class "Union"
class AsSubClass(Union):
    pass

While the following crashes mypy. This is likely a bug that should be reported.

class AsSubClass(Annotated):  
    pass

Here is the traceback.

error: INTERNAL ERROR -- Please try using mypy master on Github:
https://mypy.readthedocs.io/en/stable/common_issues.html#using-a-development-mypy-build
Please report a bug at https://github.com/python/mypy/issues
version: 0.930
Traceback (most recent call last):
  File "mypy/semanal.py", line 5132, in accept
  File "mypy/nodes.py", line 1005, in accept
  File "mypy/semanal.py", line 1105, in visit_class_def
  File "mypy/semanal.py", line 1127, in analyze_class
  File "mypy/semanal.py", line 1337, in clean_up_bases_and_infer_type_variables
  File "mypy/semanal.py", line 1428, in get_all_bases_tvars
  File "mypy/types.py", line 615, in accept
  File "mypy/typeanal.py", line 1296, in visit_unbound_type
IndexError: tuple index out of range
like image 103
PIG208 Avatar answered Oct 27 '25 07:10

PIG208