Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python - typing - union of subscriptable type

I'd like to create an Array type which should be subscriptable and be a Union of typing.List and numpy.ndarray types. I know numpy doesn't come with stubs, but those numpy stubs (by Machinalis) should work fine as they are subsriptable.

This is the expected behavior:

def foo(bar: Array[int])->None:
    pass

foo([1,2,3])          # No typing error
foo(numpy.arange(4))  # No typing error
foo((1,2,3))          # Error: Expected Array[int], got Tuple[int]
foo([1.,2.,3.])       # Error: Expected Array[int], got Array[float]

I've tried a few things but none of them work as expected.

How would you do that in Python 3.7?

I'll also accept some kind of duck-typing solution, even if it doesn't satisfy the Tuple error. The main point of focus is to create subscriptable union of subscriptable types.

Thanks.

My best attempt : (mypy errors in comments)

class _meta_getitem(type):
    def __getitem__(cls, x):
        return cls.__getitem__(cls, x)

class Array(metaclass=_meta_getitem):

    def __getitem__(self, element_type: type) -> type:
        array_type = typing.Union[List[element_type],  # error: Invalid type "element_type"
                                  numpy.ndarray[element_type]]
        return typing.NewType("Array[{}]".format(element_type.__name__), 
                              array_type)  # The type alias to Union is invalid in runtime context

if __name__ == "__name__":
    x: Array[int] = numpy.arange(4) # "Array" expects no type arguments, but 1 given
like image 278
bonoboris Avatar asked Dec 14 '18 13:12

bonoboris


People also ask

What is Union in typing in Python?

Union type; Union[X, Y] is equivalent to X | Y and means either X or Y. To define a union, use e.g. Union[int, str] or the shorthand int | str . Using that shorthand is recommended.

Does Python enforce type hints?

Python will always remain a dynamically typed language. However, PEP 484 introduced type hints, which make it possible to also do static type checking of Python code. Unlike how types work in most other statically typed languages, type hints by themselves don't cause Python to enforce types.

How do you type hints in Python?

Here's how you can add type hints to our function: Add a colon and a data type after each function parameter. Add an arrow ( -> ) and a data type after the function to specify the return data type.

What are type hints?

Introduction to Python type hints It means that you need to declare types of variables, parameters, and return values of a function upfront. The predefined types allow the compilers to check the code before compiling and running the program.


1 Answers

Creating a type alias of Union[List[T], Array[T]] ought to work:

from typing import TypeVar, Union, List

T = TypeVar('T')
Array = Union[List[T], numpy.ndarray[T]]

def foo(bar: Array[int]) -> None: pass

See the mypy docs on generic type aliases for more info about this technique.

This code may potentially fail at runtime since numpy.ndarray isn't actually subscriptable at runtime, only in the type-hinting world. You can work around this by hiding your custom type hint behind a typing.TYPE_CHECKING guard, which is always false at runtime and true at type-check time.

You can do this relatively cleanly in Python 3.7+:

from __future__ import annotations
from typing import TypeVar, Union, List, TYPE_CHECKING

if TYPE_CHECKING:
    T = TypeVar('T')
    Array = Union[List[T], numpy.ndarray[T]]

def foo(bar: Array[int]) -> None: pass

You have to wrap your Array[int] inside of a string for older versions of Python 3, however:

from typing import TypeVar, Union, List, TYPE_CHECKING

if TYPE_CHECKING:
    T = TypeVar('T')
    Array = Union[List[T], numpy.ndarray[T]]

def foo(bar: "Array[int]") -> None: pass

Note that attempting to construct your own Array type hint by composing together several other type hints at runtime is unlikely to work: static analysis tools like mypy work by actually analyzing your code without running it: it's not actually going to attempt to evaluate anything inside of your custom Array class.

A little more generally speaking, attempting to "use" type hints at runtime tends to be fraught with peril: they're really only meant to be used as type hints.

Finally, you appear to be misunderstanding what NewType is for. I recommend reading the relevant docs for more info.

like image 160
Michael0x2a Avatar answered Sep 30 '22 18:09

Michael0x2a