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
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With