-
-
Notifications
You must be signed in to change notification settings - Fork 8.6k
extmod: Implement a minimal typing module. #15911
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
base: master
Are you sure you want to change the base?
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #15911 +/- ##
=======================================
Coverage 98.57% 98.57%
=======================================
Files 164 165 +1
Lines 21336 21344 +8
=======================================
+ Hits 21031 21039 +8
Misses 305 305 ☔ View full report in Codecov by Sentry. |
|
Code size report: |
e63ca72 to
80a1dfa
Compare
|
Test for failing Codecov added. |
80a1dfa to
320c464
Compare
|
We added |
|
Agree that currently the most important use case for I see a lot of downloads of the type stubs, and it's growing steadily. Thr use will only increase when the stubs are integrated with the documentation. So I would prefer this to be enabled on the major ports, and disabled on the memory constrained ports. For |
23816a9 to
090a41a
Compare
On second thought I don't think
Essentially, yes. It requires that collections/abc.py is added in micropython-lib and contains just Likewise |
|
Hi @stinos, I build and tested a RPI_PICO image with // enable typing module in C
#define MICROPY_PY_TYPING (1)
#define MICROPY_PY_TYPING_EXTRA_MODULES (1)I can the same notebook with tests that I used to validate the a short breakdown:
of the failing runtime checks I think that
for complete test results see : verify_type_checking.ipynb for the tests that have different results between CPython , the .mpy amd the .c implementation I have added the results in # %%micropython --reset
from typing import TYPE_CHECKING
from abc import ABC, abstractmethod
from math import pi
class Shape(ABC):
@abstractmethod
def get_area(self) -> float:
pass
@abstractmethod
def get_perimeter(self) -> float:
pass
class Circle(Shape):
def __init__(self, radius) -> None:
self.radius = radius
def get_area(self) -> float:
return pi * self.radius**2
def get_perimeter(self) -> float:
return 2 * pi * self.radius
class Square(Shape):
def __init__(self, side) -> None:
self.side = side
def get_area(self) -> float:
return self.side**2
def get_perimeter(self) -> float:
return 4 * self.side
c1 = Circle(5)
s1 = Square(5)
for shape in [c1, s1]:
a = shape.get_area()
p = shape.get_perimeter()
print(a, p)
print(f"{type(a)=}")
print(f"{type(p)=}")
assert isinstance(a, (float, int)), "Area should be a float"
assert isinstance(p, (float, int)), "Perimeter should be a float"
# CPython
# ----------------------
# 78.53981633974483 31.41592653589793
# type(a)=<class 'float'>
# type(p)=<class 'float'>
# 25 20
# type(a)=<class 'int'>
# type(p)=<class 'int'>
# MicroPython typing.py
# ----------------------
# 78.53982 31.41593
# type(a)=<class 'float'>
# type(p)=<class 'float'>
# 25 20
# type(a)=<class 'int'>
# type(p)=<class 'int'>
# Micropython - modtyping.c
# ----------------------
# <any_call> <any_call>
# type(a)=<class 'any_call'>
# type(p)=<class 'any_call'>
# WARNING |Traceback (most recent call last):
# WARNING | File "<stdin>", line 52, in <module>
# ERROR |AssertionError: Area should be a float
# |
|
Great, thanks for testing! Do you mind checking the code size the .c implementation takes (pico isn't included in the ci build code size report)? Thing is still: the closer it gets to just freezing the .py implementations (and for whatever reason on the unix build it seems already way over it), the less sense it makes to follow this path.
I think that repository is private since I get a 404 trying to access it.
Well, it's a bit more than just 'isinstance does not work', this is not good.. I didn't even test that because I had no idea the ability to derive from a native type could even be broken and assumed it would first look in the derived class for attributes and only then in the base class. edit implementation details: core reason is that
The implementation is likely not active; either you have to make sure there's no |
Firmware size. |
090a41a to
a32e6d6
Compare
Yes that works, but you'd have to repeat that for every single type, whereas
Essentially what you're posted, except that 'No Typing' and 'C typing' have the same size so they're both 'C typing' probably? Anyway while fixing the implementation (code sample you posted works now) I found a blocker, see your So that means our typing implementation, whatever it is, must have all possible typing-related decorators implemented explicitly (and they definitely do not currently) otherwise the runtime behavior of code using an unlisted decorator is incorrect because the current implementations which make the module's No-one probably really thought this through so far, but this is a rather serious issue. |
No
yeah, noticed that, may have fumbled the definitions, here are numbers for Variants I created of the PIMORONI_PICOLIPO-FLASH
The .c implementation , even if it is for part of the functionality, and currently does not pass all tests, is significantly lower. .mpy Package size breakdown :
I think the risk is acceptable, as with the implementation of the typing @decorators, we are actually 'unbreaking' code, rather than breaking it. The way I have been using |
|
Thanks for the elaborate info.
Right, I changed that already in the most recent version though.
Yeah, I kind of expected this.
I have to say I'm not convinced, at least not for our production code :)
Ok but if that is a hard requirement, the C implementation is a no-go?
I made a start with implementing it in C the other way around, so no |
The general rule in MicroPython is that if something can be reasonable implemented in Python, then it should. that is why there are several As static and runtime typing in Python is still rapidly developing, my worry is that a size optimized C-implementation will be harder to maintain and extend, than a Python based solution. Also the mip installable approach adds a degree of freedom, as it can be installed on (almost) any port/board/family of micropython. perhaps we need to rank/ weight what is, or we find, important, in order to make a good-enough choice wrt to the implementation:
and then score the different solutions. |
|
@stinos, |
extmod/modtyping.c
Outdated
| mp_obj_base_t base; | ||
| } mp_obj_any_call_t; | ||
|
|
||
| extern const mp_obj_type_t mp_type_any_call_t; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this not be static const mp_obj_type_t mp_type_any_call_t; ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is possible I made it extern with the idea that would allow using this in other C code for extending typing stuff. Don't remember, and currently not needed, so indeed can be static.
Yes we definitely need tests. In fact if I remember correctly I simply stopped going further with this PR because I figured that without a rather complete test suite it's all a bit useless. For example there might be a wanted feature which is actually impossible to implement in and then we should know that and because it will also make it easier to decide between the various possible solutions (this vs your implementation vs Andrew's) - or perhaps some combination thereof depending on what is more important for the user. Now, because the typing module is quite large it can make it pretty hard to figure out if everything (i.e., from CPython) is covered. So my idea was that the test code should closely follow the CPython docs and would basically duplicate most code listed there (no idea if allowed license-wise but perhaps manually typing a code sample which essentially replicates the original might just be fine?) and leave comments for pieces which aren't supported (but, like your code, can document that in cpydiff as well). I realize that might be some work, but I have a feeling Python's typing is still in active development and I'm afraid otherwise things might get really hard to follow. And since from the earlier comments it sounds as if there might be some caveats with potentially dangerous behavior, I don't feel very comfortable with this PR unless it's very clear what is ok and what is not. Or does this sound like overkill?
It definitely is. I checked my last incantation (was never pushed) and it's like magic. At least needs more code comments.
I briefly went over all comments and all things considered find it still hard to make a decision or even rank. Yet I think it might simply come down to the code size again since the difference seems substantial. |
I think that could be done by adding this to # avoid overwriting built-in collections.abc typing dummy module
try:
import typing as abc
import sys
sys.modules['collections.abc'] = abc
except ImportError:
passnot sure how to register it in modcollections.c though I currently get an import error Testing
I have indeed been trawling the CPython docs and adding that as test material for the runtime tests , as well as the cpy-diffs.
It is, but as long as there will be new python versions planned, typing will never be "Completed".
|
|
I have been working on adding tests based on the list of typing PEPs Tests:
Current StateTest state:pass extmod/typing_pep_0484.py cpy-diffs6 cpydiffs test defined for missing / different functionality
big item issues:
Miscellaneoustyping.Callable, ParamSpec
typing.get_origin()
Python 3.8PEP 544 - Protocols: Structural subtyping (static duck typing)Merging and extending protocols:
PEP 589 - TypedDict: Type hints for dictionaries with a fixed set of keysTotality and optional keys
Inheritance and required/optional mix
Runtime checks: TypedDict cannot be used with isinstance/class checks
Alternative functional syntax and constructors
Totality and mixing with Required/NotRequired
extra_items and closed examples
Interaction with Mapping and dict conversions
PEP 0591 - Final qualifier for typesThe Final annotation
|
|
I reworked the python version of the typing module to reduce the impact to the firmware size while freezing, ( See linked PR above) and ran the same tests against both. To my surprise there is not much difference in code size between for the unix port ( though I could be measuring the wrong thing) |
This is consistent with the other ports (see py/mkrules.mk) and makes more sense overall because it makes everything which is compiled use the same flags; until now all compilation steps which ran before or in absence of FreezeModules (e.g. qstr generation, compiling a single file on the command line or in the IDE) would use different PP defs. This didn't happen to cause any issues apparently but it's just more consistent/safer to not do that. Signed-off-by: stijn <stijn@ignitron.net>
Signed-off-by: stijn <stijn@ignitron.net>
Signed-off-by: Jos Verlinde <jos_verlinde@hotmail.com>
Signed-off-by: Jos Verlinde <jos_verlinde@hotmail.com>
These are constructs used in static typing that are different between MicroPython and CPython, and should be documented as such. Signed-off-by: Jos Verlinde <jos_verlinde@hotmail.com>
PEP 484 Type Hints Python 3.5 PEP 526 Syntax for Variable Annotations Python 3.6 PEP 544 Protocols: Structural subtyping (static duck typing) Python 3.8 PEP 560 Core support for typing module and generic types Python 3.7 PEP 586 Literal Types Python 3.8 PEP 589 TypedDict: Type Hints for Dictionaries with a Fixed Set of Keys Python 3.8 PEP 591 Adding a final qualifier to typing Python 3.8 Signed-off-by: Jos Verlinde <Jos_Verlinde@hotmail.com>
Signed-off-by: Jos Verlinde <Jos_Verlinde@hotmail.com>
…in typing alias specifications. Signed-off-by: Jos Verlinde <Jos_Verlinde@hotmail.com>
c99a336 to
01085c8
Compare
|
I rebased this for completeness. But as noted here and in related issues/pull requests, for me personally the blocker is that it's easy to break runtime behavior when doing anything more than annotating ( |
Indeed, dealing with this sort of thing...: T = TypeVar('T')
L = list[T] # lowercase list
G = Generic[T]
class XX(L,G):
...I've already got reasons to want to refactor the whole call tree under Adding actual support for PEP560 I don't think there's a small and efficient way to make lowercase And then for the typing objects themselves, assuming there's a cheap test this can add for whether or not any given object is a type-hint object, we could have |
Honestly, type statements absolutely feel like the sort of thing that should've already become a |
|
As there are a lot of flavors of Typing, and the Discussions are quite active. Another thing I notice is that the typing community has a strong preference to implement typing code in Python where possible C is just too much work for too few people, as C-Devs < Py-Devs. I think for MicroPython that is no different. Im all for implementing a minimal |
|
Btw I started adding/enabling m,ore tests to get a clearer picture of what is needed: https://github.com/stinos/micropython/tree/typingtests |
Summary
The compiler already ignores type hints but to improve runtime support a minimal
typing(andabcandtyping_extensions) module is implemented in C because code-size wise that is the cheapest to do. Also see micropython/micropython-lib#584 for background.Note @Josverl asked to support
__future__andcollections.abcas well but I did not (yet) do that because:__future__is easy to add as an alias but the result would be that anything you ask is possible,i.e.if __future__.just_inventing_something:would pass. Unclear if we want that.collections.abccorrectly in the presence of micropython-lib'scollectionsmoduleTesting
A basic test is added.
Trade-offs and Alternatives
Nested = Iterable[Tuple[MyAlias, ...]]) work because the compiler cannot distinguish them from normal Python code.Note I really have no idea how many people would actually use this so maybe makes more sense to disable this by default in all but the PC ports.
Also note that
dir(typing)or REPL completion ontyping.will list all qstrs :)