pythontypestype-hintingmypypython-dataclasses

Define a custom Type that behaves like typing.Any


I need to create a Type that behaves like typing.Any when looked at by the type checker (mypy), but is distinguishable from typing.Any.

The use case is some pretty "meta" code that needs to find the variable that is annotated with this type out of a set of other variables that could be annotated with typing.Any. Note that I will never have to actually make an instance of this Type, I just need it for type annotations in the context of dataclasses. Example:

from dataclasses import dataclass, fields
from typing import Any


MyAny = ... # What to put here?

@dataclass()
class Test:

    a: Any
    b: MyAny = None


for field in fields(Test):
    if field.type == MyAny:
        print(f"It's {field.name}")   # This should print "It's b"

Things I have tried:

  1. Doesn't work, because you can't subclass Any: TypeError: Cannot subclass <class 'typing._SpecialForm'>

    class MyAny(Any):
       pass
    
  2. Doesn't work, because it is not distinguishable from the normal Any (result of code snipped above is It's a\nIt's b)

    MyAny = Any
    
  3. Works at runtime, but mypy complains about the default value: Mypy: Incompatible types in assignment (expression has type "None", variable has type "MyAny")

    class MyAny:
       pass
    
  4. Works at runtime, but mypy can't tell that this should behave like Any: It complains about the definition that Mypy: Argument 2 to NewType(...) must be subclassable(got "Any") and it complaints about the default parameter: Mypy: Incompatible types in assignment (expression has type "None", variable has type "MyAny")

    from typing import NewType
    MyAny = NewType("MyAny", Any)
    

So is there a way to make this work?


Solution

  • You can use conditionals to trick mypy into interpreting one piece of code while having your runtime execute another one.

    from dataclasses import dataclass, fields
    from typing import Any
    
    
    if False:
        MyAny = Any
    else:
        class MyAny:  # type: ignore
            pass
    
    
    @dataclass()
    class Test:
    
        a: Any
        b: MyAny = None
    
    
    for field in fields(Test):
        if field.type == MyAny:
            print(f"It's {field.name}")   # This should print "It's b"