ramonhagenaars / nptyping

💡 Type hints for Numpy and Pandas
MIT License
593 stars 30 forks source link

Add a Tuple type #80

Closed 0nyr closed 1 year ago

0nyr commented 2 years ago

Associated StackOverflow question

There is no 'Tuple' inside nptyping/typing.py. Hence it is not possible to write something like that:

import numpy
import numpy.typing as npt
from typing import cast, Type, Sequence, Any
import typing
from nptyping import NDArray, Int, Shape, UInt8

RGB: typing.TypeAlias = UInt8
ThreeD: typing.TypeAlias = tuple[int, int, int]
NDArrayRGB: typing.TypeAlias = NDArray[ThreeD, RGB]

def load_images(paths: list[str]) -> tuple[list[NDArrayRGB], list[str]]: ...

This gives an error:

InvalidArgumentsError                     Traceback (most recent call last)
c:\Users\rascoussier\python\type_checking\ndarray\main.ipynb Cell 1' in <cell line: 10>()
      [8](vscode-notebook-cell:/c%3A/Users/rascoussier/python/type_checking/ndarray/main.ipynb#ch0000000?line=7) RGB: typing.TypeAlias = UInt8
      [9](vscode-notebook-cell:/c%3A/Users/rascoussier/python/type_checking/ndarray/main.ipynb#ch0000000?line=8) ThreeD: typing.TypeAlias = tuple[int, int, int]
---> [10](vscode-notebook-cell:/c%3A/Users/rascoussier/python/type_checking/ndarray/main.ipynb#ch0000000?line=9) NDArrayRGB: typing.TypeAlias = NDArray[ThreeD, RGB]
     [12](vscode-notebook-cell:/c%3A/Users/rascoussier/python/type_checking/ndarray/main.ipynb#ch0000000?line=11) def load_images(paths: list[str]) -> tuple[list[NDArrayRGB], list[str]]: ...

File c:\Users\rascoussier\Anaconda3\envs\research310-1\lib\site-packages\nptyping\base_meta_classes.py:138, in SubscriptableMeta.__getitem__(cls, item)
    135 if getattr(cls, "_parameterized", False):
    136     raise NPTypingError(f"Type nptyping.{cls} is already parameterized.")
--> 138 args = cls._get_item(item)
    139 additional_values = cls._get_additional_values(item)
    140 assert hasattr(cls, "__args__"), "A SubscriptableMeta must have __args__."

File c:\Users\rascoussier\Anaconda3\envs\research310-1\lib\site-packages\nptyping\ndarray.py:69, in NDArrayMeta._get_item(cls, item)
     67 def _get_item(cls, item: Any) -> Tuple[Any, ...]:
     68     cls._check_item(item)
---> 69     shape, dtype = cls._get_from_tuple(item)
     70     return shape, dtype

File c:\Users\rascoussier\Anaconda3\envs\research310-1\lib\site-packages\nptyping\ndarray.py:110, in NDArrayMeta._get_from_tuple(cls, item)
    108 def _get_from_tuple(cls, item: Tuple[Any, ...]) -> Tuple[Shape, DType]:
    109     # Return the Shape Expression and DType from a tuple.
--> 110     shape = cls._get_shape(item[0])
    111     dtype = cls._get_dtype(item[1])
    112     return shape, dtype

File c:\Users\rascoussier\Anaconda3\envs\research310-1\lib\site-packages\nptyping\ndarray.py:121, in NDArrayMeta._get_shape(cls, dtype_candidate)
    119 elif cls._is_literal_like(dtype_candidate):
    120     shape_expression = dtype_candidate.__args__[0]
--> 121     shape = Shape[shape_expression]
    122 else:
    123     raise InvalidArgumentsError(
    124         f"Unexpected argument '{dtype_candidate}', expecting"
    125         " Shape[<ShapeExpression>]"
    126         " or Literal[<ShapeExpression>]"
    127         " or typing.Any."
    128     )

File c:\Users\rascoussier\Anaconda3\envs\research310-1\lib\site-packages\nptyping\base_meta_classes.py:138, in SubscriptableMeta.__getitem__(cls, item)
    135 if getattr(cls, "_parameterized", False):
    136     raise NPTypingError(f"Type nptyping.{cls} is already parameterized.")
--> 138 args = cls._get_item(item)
    139 additional_values = cls._get_additional_values(item)
    140 assert hasattr(cls, "__args__"), "A SubscriptableMeta must have __args__."

File c:\Users\rascoussier\Anaconda3\envs\research310-1\lib\site-packages\nptyping\base_meta_classes.py:206, in ContainerMeta._get_item(cls, item)
    204 def _get_item(cls, item: Any) -> Tuple[Any, ...]:
    205     if not isinstance(item, str):
--> 206         raise InvalidArgumentsError(
    207             f"Unexpected argument of type {type(item)}, expecting a string."
    208         )
    210     if item in cls._known_expressions:
    211         # No need to do costly validations and normalizations if it has been done
    212         # before.
    213         return (item,)

InvalidArgumentsError: Unexpected argument of type <class 'type'>, expecting a string.
ramonhagenaars commented 2 years ago

Hi @0nyr, you could use Shape to express this:

from nptyping import NDArray, Shape, UInt8

# A 1-dimensional array (i.e. 1 RGB color).
RGBArray1D = NDArray[Shape["[r, g, b]"], UInt8]

# A 2-dimensional array (i.e. an array of RGB colors).
RGBArrayND = NDArray[Shape["*, [r, g, b]"], UInt8]
0nyr commented 2 years ago

Thanks for the answer. I edited mine. Have a look here | StackOverflow. However, this solution is not well handled by VSCode Pylance;

CaptureTypingNumpy3

I get:

Expected class type but received "Literal"
  "Literal" is not a class
  "Literal" is not a classPylance[reportGeneralTypeIssues](https://github.com/microsoft/pylance-release/blob/main/DIAGNOSTIC_SEVERITY_RULES.md#diagnostic-severity-rules)
Pylance(reportGeneralTypeIssues)
ramonhagenaars commented 2 years ago

Thank you for reporting and answering on StackOverflow! Pyright/Pylance can be kinda tricky to get done to be honest. Especially while also maintaining MyPy acceptance.

However, for this particular error that you show, I managed to trick my way out and get Pyright/Pylance acceptance. There will be a 2.1.3 release in a minute that should fix this error. Yet, I dare not announce an official Pyright support for nptyping at this moment.

EDIT: v2.1.3 is released

0nyr commented 2 years ago

Ok very kind of you. I will try it ASAP and give a final response before closing the issue. Have a nice day.

ramonhagenaars commented 1 year ago

No recent activity: closing