Python 3.8 (2019-10-14 => 2024-10)

../_images/monty_python.jpeg

PEP-0572 Assignment expressions := (the walrus operator)

There is new syntax := that assigns values to variables as part of a larger expression.

It is affectionately known as the walrus operator due to its resemblance to the eyes and tusks of a walrus.

../_images/walrus_operator.jpg
../_images/harry_potter.jpeg

https://fediverse.org/KinkPizza/status/1178323048346329089?s=20

The Walrus in the Room: Assignment Expressions :=

The biggest change in Python 3.8 is the introduction of assignment expressions. They are written using a new notation := .

This operator is often called the walrus operator as it resembles the eyes and tusks of a walrus on its side.

Assignment expressions allow you to assign and return a value in the same expression.

For example, if you want to assign to a variable and print its value, then you typically do something like this

>>> walrus = False
>>> print(walrus)
False

In Python 3.8, you’re allowed to combine these two statements into one, using the walrus operator:

>>> print(walrus := True)
True

Use the walrus operator in the C python source code

Emily Morehouse-Valcarcel

Announce

../_images/emily_announce_2019_01_25.jpg

https://fediverse.org/emilyemorehouse/status/1088593522142339072?s=20

Proposition

In honor of Python 3.8’s release last week, I’m giving away walrus stickers!

Post a screenshot of your PR using the walrus (redact as needed) then DM me your address and I’ll mail you some stickers.

Happy upgrading! ❤️🐍

../_images/emily_proposition.jpeg

https://fediverse.org/emilyemorehouse/status/1186308489338949632?s=20

PEP 0570 Positional-only parameters with the / notation

../_images/delgado.png

https://fediverse.org/pyblogsal

There is a new function parameter syntax / to indicate that some function parameters must be specified positionally and cannot be used as keyword arguments.

This is the same notation shown by help() for C functions annotated with Larry Hastings’ Argument Clinic tool .

En français Arguments exclusivement positionnels ( / notation)

Python permet déjà de déclarer des arguments de fonction exclusivement nommés .

Python 3.8 ajoute la possibilité de déclarer des arguments exclusivement positionnels .

Voici un exemple

def f(a, /, b):
    print(a, b)
f(0, 1)  # valide
f(a=0, b=1)  # invalide car a est nommé
f(0, b=1)  # valide

Certaines API en tireront parti pour simplifier la validation des arguments. À noter, le nom d’un argument positionnel peut être réutilisé en argument nommé. Par exemple :

def f(a, /, **kw):
  print(a, kw)
f(0, a=1)  # valide ! Affiche: '0 {"a": 1}'

Complement keyword-only arguments (with the * notation) => paramètres exclusivement nommés )

Positional-only arguments nicely complement keyword-only arguments .

In any version of Python 3, you can specify keyword-only arguments using the star * notation.

Any argument after * must be specified using a keyword :

1 >>> def to_fahrenheit(*, celsius):
2 ...     return 32 + celsius * 9 / 5
3 ...
4 >>> to_fahrenheit(40)
5 Traceback (most recent call last):
6   File "<stdin>", line 1, in <module>
7 TypeError: to_fahrenheit() takes 0 positional arguments but 1 was given
8 >>> to_fahrenheit(celsius=40)
9 104.0

f-strings support = for self-documenting expressions and debugging

Added an = specifier to f-strings. An f-string such as f’{expr=}’ will expand to the text of the expression, an equal sign, then the representation of the evaluated expression. For example:

>>> user = 'eric_idle'
>>> member_since = date(1975, 7, 31)
>>> f'{user=} {member_since=}'
"user='eric_idle' member_since=datetime.date(1975, 7, 31)"

typing

The typing module incorporates several new features:

A dictionary type with per-key types. See PEP 589 and typing.TypedDict.

typing.TypedDict

TypedDict uses only string keys. By default, every key is required to be present. Specify “total=False” to allow keys to be optional:

class Location(TypedDict, total=False):
    lat_long: tuple
    grid_square: str
    xy_coordinate: tuple

Literal types

Literal types. See PEP 586 and typing.Literal. Literal types indicate that a parameter or return value is constrained to one or more specific literal values:

def get_status(port: int) -> Literal['connected', 'disconnected']:
    ...

“Final” variables, functions, methods and classes. See PEP 591, typing.Final and typing.final(). The final qualifier instructs a static type checker to restrict subclassing, overriding, or reassignment:

pi: Final[float] = 3.1415926536

Protocol definitions

See PEP 544, typing.Protocol and typing.runtime_checkable().

Simple ABCs like typing.SupportsInt are now Protocol subclasses.

New protocol class typing.SupportsIndex.

New functions typing.get_origin() and typing.get_args().

PEP-0544

asyncio

Running python -m asyncio launches a natively async REPL.

This allows rapid experimentation with code that has a top-level await. There is no longer a need to directly call asyncio.run() which would spawn a new event loop on every invocation:

$ python -m asyncio
asyncio REPL 3.8.0
Use "await" directly instead of "asyncio.run()".
Type "help", "copyright", "credits" or "license" for more information.
>>> import asyncio
>>> await asyncio.sleep(10, result='hello')
hello

Interpréteur async

Cette nouveauté n’est pas mise en avant, pourtant elle est bien pratique. En exécutant le module asyncio, CPython propose désormais un interpréteur Python acceptant le mot clef await pour exécuter une co‑routine :

Avant:

$ python3
Python 3.7.3 (default, Apr  3 2019, 05:39:12)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import asyncio
>>> await asyncio.sleep(1)
  File "<stdin>", line 1
SyntaxError: 'await' outside function
>>>

Désormais:

$ python -m asyncio
asyncio REPL 3.8.0
Use "await" directly instead of "asyncio.run()".
Type "help", "copyright", "credits" or "license" for more information.
>>> import asyncio
>>> await asyncio.sleep(1, result='hello')
hello

On pouvait avoir à peu près lʼéquivalent en lançant une boucle par co‑routine

$ python3
Python 3.7.3 (default, Apr  3 2019, 05:39:12)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import asyncio
>>> asyncio.run(asyncio.sleep(1, result='hello'))
'hello'

json

python -m json.tool accepte désormais un objet JSON par ligne avec l’option –json-line.

Ce cas est courant avec les journaux au format JSON.

unicodedata

The unicodedata module has been upgraded to use the Unicode 12.1.0 release.

New function is_normalized() can be used to verify a string is in a specific normal form, often much faster than by actually normalizing the string. (Contributed by Max Belanger, David Euresti, and Greg Price in bpo-32285 and bpo-37966).

statistics

Added statistics.fmean() as a faster, floating point variant of statistics.mean(). (Contributed by Raymond Hettinger and Steven D’Aprano in bpo-35904.)

Added statistics.geometric_mean() (Contributed by Raymond Hettinger in bpo-27181.)

Added statistics.multimode() that returns a list of the most common values. (Contributed by Raymond Hettinger in bpo-35892.)

Added statistics.quantiles() that divides data or a distribution in to equiprobable intervals (e.g. quartiles, deciles, or percentiles). (Contributed by Raymond Hettinger in bpo-36546.)

Added statistics.NormalDist, a tool for creating and manipulating normal distributions of a random variable. (Contributed by Raymond Hettinger in bpo-36018.)

>>>

>>> temperature_feb = NormalDist.from_samples([4, 12, -3, 2, 7, 14])
>>> temperature_feb.mean
6.0
>>> temperature_feb.stdev
6.356099432828281

>>> temperature_feb.cdf(3)            # Chance of being under 3 degrees
0.3184678262814532
>>> # Relative chance of being 7 degrees versus 10 degrees
>>> temperature_feb.pdf(7) / temperature_feb.pdf(10)
1.2039930378537762

>>> el_niño = NormalDist(4, 2.5)
>>> temperature_feb += el_niño        # Add in a climate effect
>>> temperature_feb
NormalDist(mu=10.0, sigma=6.830080526611674)

>>> temperature_feb * (9/5) + 32      # Convert to Fahrenheit
NormalDist(mu=50.0, sigma=12.294144947901014)
>>> temperature_feb.samples(3)        # Generate random samples
[7.672102882379219, 12.000027119750287, 4.647488369766392]

reversed() now works with dict

Since Python 3.7, dictionaries preserve the order of insertion of keys. The reversed() built-in can now be used to access the dictionary in the reverse order of insertion — just like OrderedDict.

>>> my_dict = dict(a=1, b=2)
>>> list(reversed(my_dict))
['b', 'a']
>>> list(reversed(my_dict.items()))
[('b', 2), ('a', 1)]

Simplified iterable unpacking for return and yield

This unintentional behavior has existed since Python 3.2 which disallowed unpacking iterables without parentheses in return and yield statements.

So, the following was allowed:

def foo():
    rest = (4, 5, 6)
    t = 1, 2, 3, *rest
    return t

But these resulted in a SyntaxError:

def baz():
    rest = (4, 5, 6)
    return 1, 2, 3, *rest

def baz():
    rest = (4, 5, 6)
    yield 1, 2, 3, *rest

The latest release fixes this behavior, so doing the above two approaches are now allowed.

new importlib.metadata

importlib.metadata module provides (provisional) support for reading metadata from third-party packages. For example, it can extract an installed package’s version number, list of entry points, and more:

>>>
>>> # Note following example requires that the popular "requests"
>>> # package has been installed.
>>>
>>> from importlib.metadata import version, requires, files
>>> version('requests')
'2.22.0'
>>> list(requires('requests'))
['chardet (<3.1.0,>=3.0.2)']
>>> list(files('requests'))[:5]
[PackagePath('requests-2.22.0.dist-info/INSTALLER'),
 PackagePath('requests-2.22.0.dist-info/LICENSE'),
 PackagePath('requests-2.22.0.dist-info/METADATA'),
 PackagePath('requests-2.22.0.dist-info/RECORD'),
 PackagePath('requests-2.22.0.dist-info/WHEEL')]