RunOrVeith
RunOrVeith

Reputation: 4805

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?

Upvotes: 3

Views: 12342

Answers (2)

Kent Shikama
Kent Shikama

Reputation: 4060

You could use a TypeVar.

# foo.py
from dataclasses import dataclass, fields
from typing import Any, TypeVar, Generic, Optional


MyAny = TypeVar('MyAny')

@dataclass()
class Test(Generic[MyAny]):

    a: Any
    b: Optional[MyAny] = None

for field in fields(Test):
    if field.type == Optional[MyAny]:
        print(f"It's {field.name}")
    

Output

$ python3 foo.py 
It's b
 
$ mypy foo.py 
Success: no issues found in 1 source file

Upvotes: 1

Markus Unterwaditzer
Markus Unterwaditzer

Reputation: 8244

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"

Upvotes: 5

Related Questions