Discussion of Python 3.8 support
This issue is intended to track the status of Python 3.8 core features as implemented by MicroPython.
Python 3.8.0 (final) was released on the 14 October 2019. The Features for 3.8 are defined in PEP 569 and a detailed description of the changes can be found in What's New in Python 3.8.
- PEP 570, Positional-only arguments
- PEP 572, Assignment Expressions; Done, see #4908
- PEP 574, Pickle protocol 5 with out-of-band data
- PEP 578, Runtime audit hooks
- PEP 587, Python Initialization Configuration
- PEP 590, Vectorcall: a fast calling protocol for CPython
Misc
- f-strings support = for self-documenting expressions and debugging; Done, see #7649
- A
continuestatement was illegal in thefinallyclause due to a problem with the implementation. In Python 3.8 this restriction was lifted; Done, see 82c494a97e874912e7eb23d2f03f39212e343fb3 - The
bool,int, andfractions.Fractiontypes now have anas_integer_ratio()method like that found infloatanddecimal.Decimal - Constructors of
int,floatandcomplexwill now use the__index__()special method, if available and the corresponding method__int__(),__float__()or__complex__()is not available - Added support of
\N{name}escapes in regular expressions - Dict and dictviews are now iterable in reversed insertion order using
reversed() - The syntax allowed for keyword names in function calls was further restricted. In particular, f((keyword)=arg) is no longer allowed
- Generalized iterable unpacking in yield and return statements no longer requires enclosing parentheses
- When a comma is missed in code such as [(10, 20) (30, 40)], the compiler displays a SyntaxWarning with a helpful suggestion
- Arithmetic operations between subclasses of
datetime.dateordatetime.datetimeanddatetime.timedeltaobjects now return an instance of the subclass, rather than the base class - When the Python interpreter is interrupted by
Ctrl-C (SIGINT)and the resultingKeyboardInterruptexception is not caught, the Python process now exits via aSIGINTsignal or with the correct exit code such that the calling process can detect that it died due to aCtrl-C - Some advanced styles of programming require updating the
types.CodeTypeobject for an existing function - For integers, the three-argument form of the pow() function now permits the exponent to be negative in the case where the base is relatively prime to the modulus
- Dict comprehensions have been synced-up with dict literals so that the key is computed first and the value second
- The
object.__reduce__()method can now return a tuple from two to six elements long
Changes to MicroPython built-in modules
- asyncio
-
asyncio.run()has graduated from the provisional to stable API - Running
python -m asynciolaunches a natively async REPL - The exception
asyncio.CancelledErrornow inherits fromBaseExceptionrather thanExceptionand no longer inherits fromconcurrent.futures.CancelledError - Added
asyncio.Task.get_coro()for getting the wrapped coroutine within anasyncio.Task - Asyncio tasks can now be named, either by passing the name keyword argument to
asyncio.create_task()or thecreate_task()event loop method, or by calling theset_name()method on the task object - Added support for Happy Eyeballs to
asyncio.loop.create_connection(). To specify the behavior, two new parameters have been added:happy_eyeballs_delayandinterleave.
-
- gc -
get_objects()can now receive an optional generation parameter indicating a generation to get objects from- (Note, though, that while
gcis a built-in,get_objects()is not implemented for MicroPython)
- (Note, though, that while
- math
- Added new function
math.dist()for computing Euclidean distance between two points - Expanded the
math.hypot()function to handle multiple dimensions - Added new function,
math.prod(), as analogous function tosum()that returns the product of a ‘start’ value (default: 1) times an iterable of numbers - Added two new combinatoric functions
math.perm()andmath.comb() - Added a new function
math.isqrt()for computing accurate integer square roots without conversion to floating point - The function
math.factorial()no longer accepts arguments that are not int-like
- Added new function
- sys - Add new
sys.unraisablehook()function which can be overridden to control how “unraisable exceptions” are handled
(Changes to non-built-in modules will need to be documented elsewhere.)
Support for PEP 572 Assignment Expression
I realise that the current MicroPython doesn't yet fully support all of Python 3.5, let alone 3.6 or 3.7, but since 3.8 is going into beta release I'd like to make a plea for the addition of a very useful and powerful Python 3.8 feature: Assignment Expressions.
Regular variable assignment in Python is a statement, not an expression. While this is fine in most cases there are many common cases where it would be extremely valuable to be able to both store the value of an expression in a variable and make use of that value in another expression. PEP 572 lays out the case for doing this in general. I would like to make the case for why this would be particularly useful in MicroPython.
MicroPython (in my mind at least) is all about being able to run Python in environments that are limited in both memory and CPU power. As a result MicroPython needs to (a) be as efficient as possible and (b) allow the user to be as efficient as possible. More compact code that repeats itself less in both text and action can help a lot, and that is exactly the goal of assignment expressions.
Consider the common scenario in MicroPython where one has a gadget with some registers that include flags and data; you need to check some flags and if they are in some condition you need to perform some processing on the values. Right now you might write:
reg1 = thing.get_reg(1)
if reg1 & R1_READY:
reg2 = thing.get_reg(2)
if reg2 & R2_OTHER:
something_useful(reg1, reg2)
By using assignment expressions this becomes:
if (reg1 := thing.get_reg(1)) & R1_READY and (reg2 := thing.get_reg(2)) & R2_OTHER:
something_useful(reg1, reg2)
Another scenario that's not uncommon in MicrpPython systems is looping to receive some data and quitting when some sentinel value arrives. Right now you need to do this:
while True:
line = port.get_line()
if line == "QUIT":
break
process_input(line)
With assignment expressions this becomes much more compact and readable:
while (line := port.get_line()) != "QUIT":
process_input(line)
Another case where assignment expressions are valuable is as a "witness" inside a loop or comprehension. Have you ever got a False result from calling all() and wondered which one wasn't true? Now you can know:
if all((nonblank := line).strip() == '' for line in lines):
print("All lines are blank")
else:
print("First non-blank line:", nonblank)
Capturing processed values in a list comprehension also benefits from avoiding doing processing twice. Right now you have a couple of options:
# Inefficient...
[i.cleaned() for i in stuff if i.cleaned() != '']
# or complex and opaque
[clean for clean in (i.cleaned() for i in stuff) if clean != '']
Now we can write:
[clean for i in stuff if (clean := i.cleaned()) != '']
As a result of all these I think that the benefits of assignment expressions would be particularly useful to Micropython.
Of course all of the usual arguments for not implementing a feature hold just as well for assignment expressions. There will be extra code in the ROM for the parser and byte code generator and there may be some complexity in handling the exceptional cases. That said, I think that there is plenty of scope for this reducing the size of the Python code written by the user and likely for smaller byte code for those cases too.
Probably the most compelling argument against this right now is that this is a Python 3.8 feature code, so updated code won't work on most desktop or server Python deployments just yet. This is probably an argument for this not being a high priority for the time being, but not against feature itself.