Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

typing: Add typing module #584

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Conversation

stinos
Copy link

@stinos stinos commented Dec 12, 2022

Brought up a couple of times here and on the forums.

To implement this I just took most things from dir(typing) in CPython 3.8, removed what looked too esoteric and kept the rest. Alphabetically so it's easier to find things. Probably still too much, but I really cannot guess what is used and what is not.

Closes #190.

@andrewleech
Copy link
Contributor

There's also a pretty good / simple typing module here: https://github.com/pfalcon/pycopy-lib/tree/master/typing
I've got a rebased copy of this locally that I pulled out as part of my (unfinished) pdb work if it looks useful?

@andrewleech
Copy link
Contributor

I also want to add, thanks for starting this!

While there are a few stub / hack methods of working around typing enough to use autocomplete in IDE's, there's value in a more featured typing module for use with mypy and related tooling

@stinos
Copy link
Author

stinos commented Dec 12, 2022

looks useful

yes but seemed fairly old and had less types than I found in CPython so I just started from scratch

@JayPalm
Copy link

JayPalm commented Jan 14, 2023

Would love this. I didn't even realize until just now that there isn't a typing module till I tried to add type hints for an optional parameter just now. Thanks you!

@jimmo
Copy link
Member

jimmo commented May 30, 2023

Thanks for the reminder @andrewleech. Some thoughts....

  • Having a typing module on the device is obviously important if you want to be able to directly run code that has type annotations. However, I understand that most of this is just to make the from typing import List line work, because the actual annotations are ignored by the compiler (i.e. I can write def foo(x: abc) -> xyz:) and the compiler doesn't care if abc or xyz exist.
  • However, it's a pretty unfortunate waste of flash+RAM to just make a no-op import work.

So... could we get most of what people need by having typing.py just implement a module-level __getattr__ that returns None to every attribute? Then you could write from typing import AnythingYouLike and the compiler would be happy.

Furthermore, could we take this one step further and implement a special case in the compiler to just ignore any import statements from typing ?

Additionally, as far as I understand Python 3.9 now mostly makes importing stuff from the typing module obsolete. You can write x: list[int] now (not List[int]). I guess code will continue to exist for a long time with typing imports.

@andrewleech
Copy link
Contributor

I like most of what's proposed here, in particular having a minimal on-board "stub" to reduce runtime costs.

Even with the newer syntax not needing eg. List there's still a bunch of typing only "constructs" like Union or Optional which I use a lot, though maybe there's new syntax to replace that too... I'll have to take a look.

After that there's an even shorter list of functions in typing such as NewType and cast which are used outside type annotations, so would crash out as a None.

Maybe instead of returning None the getattr could return something callable like

def null(*args, **kwargs):
    return None

or a similar lambda or similar?

@stinos
Copy link
Author

stinos commented May 30, 2023

Furthermore, could we take this one step further and implement a special case in the compiler to just ignore any import statements from typing ?

I think this should suffice, and then this would be my preferred way: no typing module needed at all, all type annotations simply ignored.

jkatins added a commit to baltech-ag/bec2format that referenced this pull request May 31, 2023
…fügt

Micropython bietet kein typing Modul (ist in Arbeit, siehe
micropython/micropython-lib#584). Stattdessen wird ein
Mock Type benutzt.

CUST-234
jkatins added a commit to baltech-ag/bec2format that referenced this pull request May 31, 2023
…fügt

Micropython bietet kein typing Modul (ist in Arbeit, siehe
micropython/micropython-lib#584). Stattdessen wird ein
Mock Type benutzt.

CUST-234
jkatins added a commit to baltech-ag/bec2format that referenced this pull request May 31, 2023
…fügt

Micropython bietet kein typing Modul (ist in Arbeit, siehe
micropython/micropython-lib#584). Stattdessen wird ein
Mock Type benutzt.

CUST-234
jkatins added a commit to baltech-ag/bec2format that referenced this pull request May 31, 2023
…fügt

Micropython bietet kein typing Modul (ist in Arbeit, siehe
micropython/micropython-lib#584). Stattdessen wird ein
Mock Type benutzt.

CUST-234
jkatins added a commit to baltech-ag/bec2format that referenced this pull request May 31, 2023
…ugefügt

Micropython bietet kein typing Modul (ist in Arbeit, siehe
micropython/micropython-lib#584). Stattdessen wird ein
Mock Type benutzt.

CUST-234
jkatins added a commit to baltech-ag/bec2format that referenced this pull request May 31, 2023
…ugefügt

Micropython bietet kein typing Modul (ist in Arbeit, siehe
micropython/micropython-lib#584). Stattdessen wird ein
Mock Type benutzt.

CUST-234
jkatins added a commit to baltech-ag/bec2format that referenced this pull request May 31, 2023
…ugefügt

Micropython bietet kein typing Modul (ist in Arbeit, siehe
micropython/micropython-lib#584). Stattdessen wird ein
Mock Type benutzt.

CUST-234
jkatins added a commit to baltech-ag/bec2format that referenced this pull request May 31, 2023
…ugefügt

Micropython bietet kein typing Modul (ist in Arbeit, siehe
micropython/micropython-lib#584). Stattdessen wird ein
Mock Type benutzt.

CUST-234
jkatins added a commit to baltech-ag/bec2format that referenced this pull request Jun 1, 2023
…ugefügt

Micropython bietet kein typing Modul (ist in Arbeit, siehe
micropython/micropython-lib#584). Stattdessen wird ein
Mock Type benutzt.

CUST-234
jkatins added a commit to baltech-ag/bec2format that referenced this pull request Jun 7, 2023
…ugefügt

Micropython bietet kein typing Modul (ist in Arbeit, siehe
micropython/micropython-lib#584). Stattdessen wird ein
Mock Type benutzt.

CUST-234
jkatins added a commit to baltech-ag/bec2format that referenced this pull request Jun 7, 2023
…ugefügt

Micropython bietet kein typing Modul (ist in Arbeit, siehe
micropython/micropython-lib#584). Stattdessen wird ein
Mock Type benutzt.

CUST-234
jkatins added a commit to baltech-ag/bec2format that referenced this pull request Jun 7, 2023
…ugefügt

Micropython bietet kein typing Modul (ist in Arbeit, siehe
micropython/micropython-lib#584). Stattdessen wird ein
Mock Type benutzt.

CUST-234
jkatins added a commit to baltech-ag/bec2format that referenced this pull request Jun 7, 2023
…ugefügt

Micropython bietet kein typing Modul (ist in Arbeit, siehe
micropython/micropython-lib#584). Stattdessen wird ein
Mock Type benutzt.

CUST-234
jkatins added a commit to baltech-ag/bec2format that referenced this pull request Jun 7, 2023
…ugefügt

Micropython bietet kein typing Modul (ist in Arbeit, siehe
micropython/micropython-lib#584). Stattdessen wird ein
Mock Type benutzt.

CUST-234
jkatins added a commit to baltech-ag/bec2format that referenced this pull request Jun 9, 2023
…ugefügt

Micropython bietet kein typing Modul (ist in Arbeit, siehe
micropython/micropython-lib#584). Stattdessen wird ein
Mock benutzt.

CUST-234
jkatins added a commit to baltech-ag/bec2format that referenced this pull request Jun 15, 2023
…ugefügt

Micropython bietet kein typing Modul (ist in Arbeit, siehe
micropython/micropython-lib#584). Stattdessen wird ein
Mock benutzt.

CUST-234
jkatins added a commit to baltech-ag/bec2format that referenced this pull request Jun 15, 2023
…ugefügt

Micropython bietet kein typing Modul (ist in Arbeit, siehe
micropython/micropython-lib#584). Stattdessen wird ein
Mock benutzt.

CUST-234
jkatins added a commit to baltech-ag/bec2format that referenced this pull request Jun 16, 2023
…ugefügt

Micropython bietet kein typing Modul (ist in Arbeit, siehe
micropython/micropython-lib#584). Stattdessen wird ein
Mock benutzt.

CUST-234
@andrewleech
Copy link
Contributor

I had a go at implementing a "simplified" on-device stub version based on a __getattr__ approach, immediately ran into a bunch of issues with my existing project codebase :-(

It's common practice when using type hinting to create type aliases of complex types for reuse, eg.

from typing import Union, Tuple
LedLevels: TypeAlias = Tuple[Union[int, float, None], Union[int, float, None], Union[int, float, None]]

While it might just be a case of "you can't do this with microypthon" it's a bit of a shame... it works with the existing stub library here.

This slightly bigger on-device stub does seem to work with my above use case and any similar "real" use of typing features:

class _TypeIgnore:
    def __init__(*args, **kwargs):
        pass

    def __call__(self, *args, **kwargs):
        return self

    def __getitem__(self, attr):
        return self

_type_ignore = _TypeIgnore()

def __getattr__(attr):
    return _type_ignore

Comment on lines +1 to +154

class ClassVar:
pass


class Final:
pass


class Hashable:
pass


class IO:
pass


class NoReturn:
pass


class Sized:
pass


class SupportsInt:
pass


class SupportsFloat:
pass


class SupportsComplex:
pass


class SupportsBytes:
pass


class SupportsIndex:
pass


class SupportsAbs:
pass


class SupportsRound:
pass


class TextIO:
pass


AnyStr = str
Text = str
Pattern = str
Match = str
TypedDict = dict

AbstractSet = _Subscriptable
AsyncContextManager = _Subscriptable
AsyncGenerator = _Subscriptable
AsyncIterable = _Subscriptable
AsyncIterator = _Subscriptable
Awaitable = _Subscriptable
Callable = _Subscriptable
ChainMap = _Subscriptable
Collection = _Subscriptable
Container = _Subscriptable
ContextManager = _Subscriptable
Coroutine = _Subscriptable
Counter = _Subscriptable
DefaultDict = _Subscriptable
Deque = _Subscriptable
Dict = _Subscriptable
FrozenSet = _Subscriptable
Generator = _Subscriptable
Generic = _Subscriptable
Iterable = _Subscriptable
Iterator = _Subscriptable
List = _Subscriptable
Literal = _Subscriptable
Mapping = _Subscriptable
MutableMapping = _Subscriptable
MutableSequence = _Subscriptable
MutableSet = _Subscriptable
NamedTuple = _Subscriptable
Optional = _Subscriptable
OrderedDict = _Subscriptable
Sequence = _Subscriptable
Set = _Subscriptable
Tuple = _Subscriptable
Type = _Subscriptable
Union = _Subscriptable

TYPE_CHECKING = False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def cast(type, val):
return val
def get_origin(type):
return None
def get_args(type):
return ()
def no_type_check(arg):
return arg
def overload(func):
return None
class _AnyCall:
def __init__(*args, **kwargs):
pass
def __call__(*args, **kwargs):
pass
_anyCall = _AnyCall()
class _SubscriptableType:
def __getitem__(self, arg):
return _anyCall
_Subscriptable = _SubscriptableType()
def TypeVar(type, *types):
return None
def NewType(name, type):
return type
class Any:
pass
class BinaryIO:
pass
class ClassVar:
pass
class Final:
pass
class Hashable:
pass
class IO:
pass
class NoReturn:
pass
class Sized:
pass
class SupportsInt:
pass
class SupportsFloat:
pass
class SupportsComplex:
pass
class SupportsBytes:
pass
class SupportsIndex:
pass
class SupportsAbs:
pass
class SupportsRound:
pass
class TextIO:
pass
AnyStr = str
Text = str
Pattern = str
Match = str
TypedDict = dict
AbstractSet = _Subscriptable
AsyncContextManager = _Subscriptable
AsyncGenerator = _Subscriptable
AsyncIterable = _Subscriptable
AsyncIterator = _Subscriptable
Awaitable = _Subscriptable
Callable = _Subscriptable
ChainMap = _Subscriptable
Collection = _Subscriptable
Container = _Subscriptable
ContextManager = _Subscriptable
Coroutine = _Subscriptable
Counter = _Subscriptable
DefaultDict = _Subscriptable
Deque = _Subscriptable
Dict = _Subscriptable
FrozenSet = _Subscriptable
Generator = _Subscriptable
Generic = _Subscriptable
Iterable = _Subscriptable
Iterator = _Subscriptable
List = _Subscriptable
Literal = _Subscriptable
Mapping = _Subscriptable
MutableMapping = _Subscriptable
MutableSequence = _Subscriptable
MutableSet = _Subscriptable
NamedTuple = _Subscriptable
Optional = _Subscriptable
OrderedDict = _Subscriptable
Sequence = _Subscriptable
Set = _Subscriptable
Tuple = _Subscriptable
Type = _Subscriptable
Union = _Subscriptable
TYPE_CHECKING = False
from micropython import const
TYPE_CHECKING = const(False)
if TYPE_CHECKING:
# Type Checkers on PC will "override" this dynamically to turn on TYPE_CHECKING and include below
def TypeVar(type, *types):
return None
def NewType(name, type):
return type
def cast(type, val):
return val
def get_origin(type):
return None
def get_args(type):
return ()
def no_type_check(arg):
return arg
def overload(func):
return None
class _AnyCall:
def __init__(*args, **kwargs):
pass
def __call__(*args, **kwargs):
pass
_anyCall = _AnyCall()
class _SubscriptableType:
def __getitem__(self, arg):
return _anyCall
_Subscriptable = _SubscriptableType()
class Any:
pass
class BinaryIO:
pass
class ClassVar:
pass
class Final:
pass
class Hashable:
pass
class IO:
pass
class NoReturn:
pass
class Sized:
pass
class SupportsInt:
pass
class SupportsFloat:
pass
class SupportsComplex:
pass
class SupportsBytes:
pass
class SupportsIndex:
pass
class SupportsAbs:
pass
class SupportsRound:
pass
class TextIO:
pass
AnyStr = str
Text = str
Pattern = str
Match = str
TypedDict = dict
AbstractSet = _Subscriptable
AsyncContextManager = _Subscriptable
AsyncGenerator = _Subscriptable
AsyncIterable = _Subscriptable
AsyncIterator = _Subscriptable
Awaitable = _Subscriptable
Callable = _Subscriptable
ChainMap = _Subscriptable
Collection = _Subscriptable
Container = _Subscriptable
ContextManager = _Subscriptable
Coroutine = _Subscriptable
Counter = _Subscriptable
DefaultDict = _Subscriptable
Deque = _Subscriptable
Dict = _Subscriptable
FrozenSet = _Subscriptable
Generator = _Subscriptable
Generic = _Subscriptable
Iterable = _Subscriptable
Iterator = _Subscriptable
List = _Subscriptable
Literal = _Subscriptable
Mapping = _Subscriptable
MutableMapping = _Subscriptable
MutableSequence = _Subscriptable
MutableSet = _Subscriptable
NamedTuple = _Subscriptable
Optional = _Subscriptable
OrderedDict = _Subscriptable
Sequence = _Subscriptable
Set = _Subscriptable
Tuple = _Subscriptable
Type = _Subscriptable
Union = _Subscriptable
else:
# On-device when compiled to mpy the above should be stripped out leaving just this section.
class _TypeIgnore:
def __init__(*args, **kwargs):
pass
def __call__(self, *args, **kwargs):
return self
def __getitem__(self, attr):
return self
_type_ignore = _TypeIgnore()
def __getattr__(attr):
return _type_ignore

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how well it works suggesting this inline, but this is my attempt at providing an on-device simplified runtime stub, but keeping the "full" details for PC based type checking. mpy-cross does strip out the bulk of it, compiled to mpy beforehand was about 1.7KB, with this change it brings it down to 289 bytes for me.

@Josverl
Copy link

Josverl commented May 30, 2024

@andrewleech ,

Ive been bundling the typings.py in micropython-stubs , and one of the users, @ANogin, found a runtime issue when defining Generics.

The error reported is on class _AnyCall ,

TypeError: '_AnyCall' object isn't subscriptable
Josverl/micropython-stubs#755

I cannot find a reference of _AnyCall in pylance/mypy typing (Python 3.11), while I can find usage of _AnyCallable.

_AnyCallable: TypeAlias = Callable[..., object]
in functools.pyi

what was the origin of your _AnyCall and could / should we adopt it or replace it by _AnyCallable ?
Is that the same / similar ?

@andrewleech
Copy link
Contributor

andrewleech commented May 31, 2024

The AnyCall in this PR doesn't come from anywhere necessarily I don't think, it's just a minimal stub that implements __call__().

It's then wrapped in simple Subscriptable type that's used by everything that should be used the way, it would be useful to see a bug report about what usage threw the error?

@ANogin
Copy link

ANogin commented May 31, 2024

The AnyCall in this PR doesn't come from anywhere necessarily I don't think, it's just a minimal stub that implements __call__().

It's then wrapped in simple Subscriptable type that's used by everything that should be used the way, it would be useful to see a big report about what usage threw the error?

See Josverl/micropython-stubs#755 (comment) for a specific example of the error.

@jack60612
Copy link

is this still something being considered, or is typing going to be ignored by the interpreter?

@Josverl
Copy link

Josverl commented Aug 16, 2024

@jack60612 For now you can add add this to your mcu or freeze it to shave off some memory.
mpremote mip install github:josverl/micropython-stubs/mip/typing.mpy

@stinos
Copy link
Author

stinos commented Aug 29, 2024

@Josverl @andrewleech we should move this forward; did you come to a conlusion about the various versions (typing.py doesn't have if TYPE_CHECKING for example)?

To summarize these seem to be the possibilities:

  • jimmo's idea of making micropython ignore any typing import; would be my preferred solution, should I have a go at that or would an actual typing.py be preffered?
  • andrew's typing.py with
class _TypeIgnore:
    def __init__(*args, **kwargs):
        pass

    def __call__(self, *args, **kwargs):
        return self

    def __getitem__(self, attr):
        return self

_type_ignore = _TypeIgnore()

def __getattr__(attr):
    return _type_ignore
  • the above, but conditional on TYPE_CHECKING such that classes can still be defined if needed
  • something else?

@Josverl
Copy link

Josverl commented Aug 30, 2024

C / Python implementation

In my current testing I can compile the .py files down to approx 440 bytes, so if the C implementation takes less than that im all for it. Freezing may reduce that further.
I don't know the other effects on memory - but creating objects either way is bound to use and cause some memory fragmentation.

minimal compiled versions using mpy-cross mp_abc_min.py -O3

stubtestprojects\rt_typing
30-08-2024  18:24               116 mp_abc_min.mpy
30-08-2024  18:24               324 mp_typing_min.mpy
               2 File(s)            440 bytes

The .py version with some extra's

If I add just the __getattr__(attr): I find I can omit most of the other classes , reducing the overall size significantly.
the class _TypeIgnore class is the same as the _AnyCall class , so 1 is sufficent , I do like the name

conditional on TYPE_CHECKING

These are runtime modules , so TYPE_CHECKING will always be False by definition.

from abc import ABC

I think we can expect that ABC will also be used, so I did a quick test on that as well.

"""
abc.py - Micropython runtime Abstract Base Classes module
"""
from typing import _any_call  # type: ignore
def abstractmethod(funcobj):
    return funcobj
def __getattr__(attr):
    return _any_call

That allows me to run though a a few test , but it is not perfect as it fails on : class MyABC(metaclass=ABCMeta): with a
TypeError: function doesn't take keyword arguments
Ive not found a way around this , also not sure how common this sample is.

Testing

I have made my current test in a jupyter notebook running against a board.
that is fine for prototyping , but we need a body of scripts / snippets to test against, most likely on the unix port for ease of testing.
I could extend some of my stub-quality tests, but only part of them were actually made to be actually run.

Josverl added a commit to Josverl/micropython-stubs that referenced this pull request Aug 30, 2024
related: micropython/micropython-lib#584
Signed-off-by: Jos Verlinde <Jos.Verlinde@microsoft.com>
@stinos
Copy link
Author

stinos commented Sep 4, 2024

if the C implementation takes less

I had a proper look at this. My first idea was to ignore any typing imports but that makes things rather complicated because statements like 'from typing import List' are split in 'import typing' and 'lookup the List attribute on the module' but for that last one there should be an actual mp_obj_t available.

I skipped on that and tried an alternative: make typing an actual but empty module with just an attr function which returns None for everything. Small in size, works ok for standard type hints, but this case immediately breaks

from typing import *

Vector = List[float]

because typing has nothing in it, List isn't defined. Not sure how common that is though. This also doesn't work:

from typing import List

Vector = List[float]

because I made 'from typing import ...' return None. Also note that even if ignoring anything typing-related in C would work, the 2 above examples would also fail with that mechanism.

So: unless I'm missing something, ignoring typing in C isn't going to work, and a working implementation would basically mean implementing pretty much everything the .py file does in C. Seeing it's not a lof of code after all, I wonder if that's going to be much smaller than freezing the module.

@stinos
Copy link
Author

stinos commented Sep 5, 2024

Seeing it's not a lof of code after all, I wonder if that's going to be much smaller than freezing the module.

It's likely not.. A C implementation of this module

class _TypeIgnore:
    def __init__(*args, **kwargs):
        pass

    def __call__(self, *args, **kwargs):
        return self

    def __getitem__(self, attr):
        return self

_type_ignore = _TypeIgnore()

def __getattr__(attr):
    return _type_ignore

is 436 bytes here. Freezing the Python module is 480 bytes. So an 'obscure' C implementation doesn't really seem worth it. Let's see if Andrew has a preference.

@andrewleech
Copy link
Contributor

Very interesting investigation about code options and size, thanks guys.

To be honest I'm really conflicted about all this myself!

It seems like it should be unnecessary to ever cost any on-device bytes for this sort of feature that's really just used by on-pc tooling.
Especially when most forms on type information is stripped out by the micropython computer, any extra typing support code just seems like wasted space.

But then we have to consider there's two separate environments that need to share the one application codebase:

  • PC / cpython tooling (ruff, mypy, pyright / vscode etc)
  • On-device micropython

The cpython tooling should never see the fake typing stub being discussed here. Similarly I don't see any value in a more full featured typing library that runs on micropython - the extensive stubs from @Josverl fill this need and are installed into cpython venv, not the micropython application.

But making the code that uses these cpython compatible typing features compatible enough to run in the micropython environment is obviously crucial.

I do keep falling back on my first workaround; just wrapping any import and usage of typing with a try/except. This does not cost a module&class worth of code but it's ugly, has a very slight runtime overhead during an often-time-sensitive startup import phase, and doesn't really help if you want to reuse a cpython module without modifying stuff.

I do like @jimmo's import skip suggestion but as you found @stinos it's more complicated that it seems, what with the from/import structure and potential use of imported objects outside of pure type hinting syntax.

I did also think perhaps the typing import could be filtered out in the compiler, similar to how the type hints themselves already are.
Perhaps any usage of any objects imported from typing could be similarly filtered out - but I haven't delved into the compiler code before to get a feel for how much work this is.

I've also thought of ways to make a C stub typing module that might be much smaller, but haven't written this yes to see if it comes out small enough to justify.

Lastly there's the current python minimal version presented here. It has the advantage of flexibility - users can install it only if/ when it's needed by the users application.
But as I started with, this is really just wasted space because the application isn't really using the typing code.
So yeah, I'm conflicted on the most appropriate way forward...

@Josverl
Copy link

Josverl commented Sep 7, 2024

The advantage of a typing.py/.mpy module is that it

  • can be simply added to the source modules of anyone that wants to use these, and thus has no impact at all to the firmware size.
  • is mip installable
  • can be changed simpler compared to a C-implementation
    -can be frozen into custom firmware

I'm not sure if we should consider any difference in runtime impact to memory allocation/fragmentation or processing overhead.
On one hand I think both impacts are small, but on the other hand static typing is becoming more and more part of Python.
I Think it's worth clarifying such impact in advance, to help make a good implementation choice.

@stinos
Copy link
Author

stinos commented Sep 11, 2024

I do keep falling back on my first workaround; just wrapping any import and usage of typing with a try/except.

Indeed as you mention that has drawbacks. Personally not really a favorite,

I did also think perhaps the typing import could be filtered out in the compiler, similar to how the type hints themselves already are. Perhaps any usage of any objects imported from typing could be similarly filtered out - but I haven't delved into the compiler code before to get a feel for how much work this is.

I tried a partial implementation for filtering out the import at the compiler stage to check the code size and it is rather small (about 60 bytes), but that is only the import itself.
The problem is indeed going to be with filtering out type aliases (Vector = List[float]) and similar. Unless I'm missing something that would almost require keeping a list of everything imported from typying and then matching that anywhere such name like List could be used. That's going to be larger in size and might come with a considerable runtime overhead. We could go the route of saying micropython only filters out the import but doesn't support using the imported names except in type hints (because they are ignored there anyway) but I think type aliases are used too much for that being really useful. tldr; unless I'm missing something it's als not a real solution.

So functionality-wise and for easy of use, freezing a minimal typing.py to me still seems the best solution.

Unless... Starting in Python 3.10 there's a TypeAlias notation and in Python 3.12 the type statement. That would again be fairly easy to ignore in the compiler. So we could go that way, saying micropython supports (but ignores) typing and type aliases but only 3.10+ syntax. Still a restriction, but not quite as bad, and I think ok-ish code-size wise. What do you guys think?

@Josverl
Copy link

Josverl commented Sep 11, 2024

I quite like the 3.12 syntax, but can't really assess the effort needed for the the uplift from 3.~5 syntax. I do note that authors of other tools such as the python minifier are spending a considerable amount of effort to implement the 3.12 changes.
Also 3.9 actually deprecated most of the collection-like Things in typings in favor of collections.abc so we probably want one more module that deal with any requires runtime behavior.

And actually that is something that I am not perfectly clear on, namely:

  • What is the minimal required runtime support needed ?
    From the typing doc page :

    This module provides runtime support for type hints.
    but it does not state what that support is

would it be useful to add a minimal shim/thunk such as class _TypeIgnore as a global singleton in C
and then return that for any module import in ["typing","typing_extention","abc","collection.abc", "__futures__"] ( module in C or Python)

we probably could sanity check with the python/typing community to make sure that we are aligned to mypy and pyright

@stinos
Copy link
Author

stinos commented Sep 11, 2024

in favor of collections.abc so we probably want one more module that deal with any requires runtime behavior.

Right, to doublecheck: supporting that (and typing_extensions etc) can be done using the same minimal implementation as proposed here for typing.py i.e. a _TypeIgnore instance, right?

would it be useful to add a minimal shim/thunk such as class _TypeIgnore as a global singleton in C
and then return that for any module import in ["typing","typing_extenstion","abc","collection.abc"] ( module in C or Python)

I'm not completely sure what you mean, can you elaborate? I'm not sure what is the difference between for example what I tried (implementing _TypeIgnore and typing module in C) and what you propose here (well, apart from that instead of only typing there's also 3 other alternative names for the module)?

@projectgus projectgus self-requested a review September 12, 2024 00:54
@Josverl
Copy link

Josverl commented Sep 12, 2024

Right, to doublecheck: supporting that (and typing extensions etc) can be done using the same minimal implementation as proposed here for typing.py i.e. a _TypeIgnore instance, right?

Indeed , and if possible in a single piece of code to minimize storage, CPU , and Memory, and memory fragmentation impacts

I'm not completely sure what you mean, can you elaborate? I'm not sure what is the difference between for example what I tried (implementing _TypeIgnore and typing module in C) and what you propose here (well, apart from that instead of only typing there's also 3 other alternative names for the module)?

Indeed, 4 or 5x different modules with different names, but the same ( or very similar) implementation)
So I was wondering if , and how, its possible to avoid just copying & renaming this.
I'm not quite sure on the memory and fragmentation impact

  • typing.py as discussed above
  • the other modules could essentially be reduced to from typing import *

compared to a C implementation where perhaps this might be more efficient by just using the different module names to import the same code, at the cost of flexibility

if we opt for .py version , then I think we should consider creating a bundle-typing to make it simpler to include the set.

@stinos
Copy link
Author

stinos commented Sep 16, 2024

So I was wondering if , and how, its possible to avoid just copying & renaming this.

If implemented in C as a module then it's just a matter of adding extra module registrations like MP_REGISTER_MODULE(MP_QSTR_typing_extenstion, mp_module_typing);. Which is actually smaller in code size than freezing extra modules even if they only contain from typing import *

@andrewleech
Copy link
Contributor

Yeah I've been thinking about this a fair bit too.
The from typing import * won't work for most things anyway, as that only pulls in named items. Each wrapper module would also need the getattr as the catch all, which all adds up.

So yes could have it in C with the single module object registered with multiple names, or possibly still written in python but have that module register itself in sys.modules under the other names. I'm not completely sure how a module can reference itself, other than maybe a scheduled callback that itself imports the module - that kind of indirection adds size too though

@Josverl
Copy link

Josverl commented Sep 19, 2024

I find that from typing import __getattr__ # type: ignore works well to pull in the same logic as used in typing, while keeping the other files small.

ls :/lib
         202 __future__.mpy
          70 abc.mpy
           0 collections/
         381 typing.mpy
          66 typing_extensions.mpy
ls :/lib/collections
          82 abc.mpy

I have added more runtime test , and more or less sorted them into categories in : verify_type_checking.ipynb

A lot works fine, but there are some typing syntaxes that

  • work only partially:

    • get_args
    • get_orgin,
  • dont work at all :

    • metaclass
    • runtime_checkable
    • python 3.12 syntax
  • that need some sample code to actually run a runtime test

    • IO*
    • asyncio
    • and likely some other common uses

im still trying out how to make Generics work

  • basic generic work OK
  • user defined Generics do not work (yet ?)
    class Registry(Generic[T]):
    def __init__(self) -> None:
        self._store: Dict[str, T] = {}
        
     TypeError: 'type' object isn't subscriptable

@stinos
Copy link
Author

stinos commented Sep 20, 2024

I find that from typing import getattr # type: ignore works well

That's neat. I looked into the pure C implementation again and could reduce it from 4 to 2 reusable functions, I added the other modules (except collections.abc, still need to figure out how to do that) and now the code size difference with a frozen Python implementation is getting larger i.e. C implementation is smaller. Still not sure if it's the way to go but was planning on first getting more tests then a PR so others can check, so:

I have added more runtime test

Nice, that's exactly what's needed :)

@Josverl
Copy link

Josverl commented Sep 21, 2024

From https://peps.python.org/pep-0560/#using-class-getitem-in-c-extensions

I gather that runtime support for custom Generic classes may be achievable, but as that is a 3.8 feature I'm not sure how that aligns with the 3.4-ish python compat.

@andrewleech
Copy link
Contributor

MicroPython does support a number of features from newer python versions, but in my understanding there's no particular newer one that's considered "broadly supported" enough to consider it the "standard version" as such.
Newer python features are certainly allowed in though!

@stinos
Copy link
Author

stinos commented Sep 25, 2024

I have added more runtime test , and more or less sorted them into categories in : verify_type_checking.ipynb

That's a 404 for me.

work only partially:
dont work at all

I don't think that's possible without compiler support.

@jack60612
Copy link

@jack60612 For now you can add add this to your mcu or freeze it to shave off some memory. mpremote mip install github:josverl/micropython-stubs/mip/typing.mpy

meant to reply to this earlier, but thanks!

@projectgus projectgus removed their request for review October 8, 2024 06:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Typing module
7 participants