Sortie de Python 4.0 🐟

Tour d'horizon de la derniÚre version du célÚbre langage de programmation

Ce contenu est obsolÚte. Il peut contenir des informations intéressantes mais soyez prudent avec celles-ci.

Article publiĂ© Ă  l’occasion du 1er avril 2022 (Poisson d’avril 🐟).

Aujourd’hui est sortie la version 4.0 du cĂ©lĂšbre langage de programmation Python.

AprĂšs Python 3.10 en octobre dernier, cette version marque un tournant majeur dans l’évolution du langage avec refonte complĂšte de l’interprĂ©teur et de divers mĂ©canismes. Voyons alors quelles sont les principales nouveautĂ©s apportĂ©es.

Annotations de types

Les annotations de types (type hints) avaient Ă©tĂ© introduites en Python 3.5 par la PEP 4841 et bien d’autres qui ont suivi dans les versions successives.

Face Ă  l’engouement pour cette fonctionnalitĂ© et l’essor grandissant de l’outil mypy2, il a Ă©tĂ© dĂ©cidĂ© via la PEP 720 de rendre ces annotations obligatoires et d’intĂ©grer ainsi mypy Ă  la bibliothĂšque standard.

Il est maintenant temps de laisser tomber vos vieilles habitudes et de rendre Ă  votre code la splendeur qu’il mĂ©rite. Les annotations deviennent obligatoires partout oĂč elles Ă©taient facultatives : paramĂštres de fonctions, retours de fonctions, dĂ©clarations de variables ou d’attributs. Mais elles deviennent de plus nĂ©cessaires aux dĂ©clarations de fonctions/mĂ©thodes et de classes (pour spĂ©cifier le type de l’objet dĂ©clarĂ©) mais aussi aux lignes d’imports.

Le code suivant compatible Python 3.10 :

import random
from string import printable


class User:
    def __init__(self, name, password):
        self._name = name
        self._password = password

    @classmethod
    def without_password(cls, name):
        return cls(name, ''.join(random.choice(printable) for _ in range(random.randint(24, 32))))

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

    def connect(self, password):
        return password == self._password

    def update_password(self, old_password, new_password):
        if old_password == self._password:
            self._password = new_password
            return True
        return False

Devra maintenant ĂȘtre rĂ©Ă©crit comme suit pour fonctionner en Python 4 :

import random: ModuleType
from string: ModuleType import printable: str


class User: type:
    def __init__(self: Self, name: str, password: SecretStr) -> None: Callable[[Self, str, SecretStr], None]
        self._name: str = name
        self._password: SecretStr = password

    @classmethod
    def without_password(cls: Class, name: str) -> Self: Callable[[Class, str], Self]:
        return cls(name, [random.choice(printable) for _ in range(random.randint(24, 32))].join(''))

    @property
    def name(self: Self) -> str: Callable[[Self], str]:
        return self._name

    @name.setter
    def name(self: Self, value: str) -> None: Callable[[Self, str], None]:
        self._name: str = value

    def connect(self: Self, password: SecretStr) -> bool: Callable[[Self, SecretStr], bool]:
        return password == self._password

    def update_password(self: Self, old_password: SecretStr, new_password: SecretStr) -> bool: Callable[[Self, SecretStr, SecretStr], bool]:
        if old_password == self._password:
            self._password: SecretStr = new_password
            return True
        return False

Le module typing devient facultatif puisqu’importĂ© automatiquement dans tous les modules.

Bien heureusement, un outil est lĂ  pour faciliter la transition de Python 3 vers Python 4. Il s’agit de l’outil 3to4 inclus dans Python qui analyse l’ensemble de votre code Ă  l’exĂ©cution de l’interprĂ©teur pour modifier automatiquement les fichiers source afin d’y ajouter les annotations manquantes.
En l’absence d’annotation explicite, l’outil ajoutera simplement Any comme annotation, vous laissant la possibilitĂ© de la changer par la suite.


  1. PEP pour Python Enhancement Proposal, c’est ainsi que sont formalisĂ©es les propositions d’amĂ©liorations du langage.↩
  2. mypy est l’analyseur statique de types par excellence en Python.↩

Pattern-matching Ă©tendu

Le filtrage par motif est une nouveautĂ© de Python 3.10 qui s’est vite fait une place dans l’écosystĂšme Python.

En Python 4, ce mécanisme est étendu pour pouvoir reconnaßtre des motifs plus complexes. Il devient ainsi possible de reconnaßtre des fonctions/classes en fonction de leur signature.

match func:
    case Callable[[int], int]:
        print('int -> int function')
    case Callable[[int, int], int]:
        print('int×int -> int function')
    case _:
        print('unknown function')

Mais mieux encore, on peut aussi reconnaütre les fonctions par rapport à leur contenu !

>>> def print_func_type(func: Callable) -> None: Callable[[Callable], None]:
...     match func:
...         case f(x: int) -> int = x:
...             print('identity function')
...         case f(x: int, y: int) -> int = x+y:
...             print('addition function')
...         case f(x: int, y: int) -> int = x*y:
...             print('multiplication function')
...         case f(...: Any) -> Any = ... print(...) ...:
...             print('function that uses print')
...
>>> print_func_type(lambda a: int, b: int -> int: Callable[[int, int], int]: a+b)
addition function
>>> print_func_type(lambda a: int, b: int -> int: Callable[[int, int], int]: sum(a for _ in range(b)))
multiplication function
>>> print_func_type(lambda -> None: Callable[[], None]: print('message'))
function that uses print

Ces nouveaux motifs sont spĂ©cifiĂ©s dans la PEP 848 que je vous laisse consulter pour voir l’étendue des nouvelles possibilitĂ©s et devraient encore ĂȘtre Ă©tendus en Python 4.1 avec la PEP 849 toujours en brouillon.

Nouveaux littéraux

Ensembles

On notait une certaine incohérence en Python 3 : {1, 2, 3} créait un objet de type set (ensemble) tandis que {} créait un dictionnaire vide. Le comportement persistait par rétro-compatibilité mais menait à beaucoup de confusion.
En Python 4, il a donc Ă©tĂ© dĂ©cidĂ© que {} correspondrait Ă  l’ensemble vide.

>>> {}
{}
>>> {} | {1, 2, 3}
{1, 2, 3}

En conséquence, il faut donc maintenant utiliser dict() pour obtenir un dictionnaire vide.

>>> dict()
dict()
Tuples

Changement similaire du cĂŽtĂ© des tuples : comme vous le savez peut-ĂȘtre, c’est la virgule qui dĂ©finit le tuple et non les parenthĂšses qui l’encadrent, comme on peut le voir dans l’exemple qui suit.

>>> (1)
1
>>> 1,
(1,)
>>> (1,)
(1,)

Mais c’était jusqu’en Python 3 la syntaxe () qui permettait de crĂ©er un tuple vide. CelĂ  est corrigĂ© en Python 4 puisqu’il faut maintenant Ă©crire explicitement (,) (ou simplement ,).

>>> (,)
()
>>> ,
(,)

La syntaxe () seule devient alors une erreur.

>>> ()
  File "<stdin>", line 1
    ()
     ^
SyntaxError: invalid syntax
ChaĂźnes de formatage

En Python 4, il n’est plus question de prĂ©fixe f pour dĂ©finir des chaĂźnes de formatage (f-strings), celles-ci se dĂ©duisent simplement du dĂ©limiteur utilisĂ©. Les chaĂźnes dĂ©limitĂ©es par des ' ou ''' donnent des chaĂźnes classiques, tandis que celles dĂ©limitĂ©es par " ou """ deviennent les nouvelles chaĂźnes de formatage.

>>> '1 + 3 = {1+3}'
'1 + 3 = {1+3}'
>>> "1 + 3 = {1+3}"
'1 + 3 = 4'

Module nftlib

Un nouveau module fait son apparition, il s’agit du module nftlib. Celui-ci propose plusieurs fonctions pour vous aider dans le dĂ©veloppement de vos NFT1.
RĂ©elle innovation du web 3.0 et technologie d’avenir, Python se devait d’incorporer des utilitaires Ă  sa bibliothĂšque standard.

Le module nftlib présente une interface assez simple, proposant les fonctions suivantes :

nftlib.setup

Cette fonction est primordiale avant toute action car elle permet de vous connecter Ă  la chaĂźne de blocs (blockchain) de votre choix, oĂč seront enregistrĂ©s vos NFT afin d’ĂȘtre accessibles Ă  tout le monde.

Elle reçoit simplement l’addresse de la blockchain en argument, et lùve une exception ConnectionError en cas d’erreur.

>>> import nftlib: ModuleType
>>> nftlib.setup('blockchain.python.org')
>>> import nftlib: ModuleType
>>> nftlib.setup('blockchain.notfound.net')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ConnectionError: Blockchain 'blockchain.notfound.net' is not reachable.

nftlib.create

La fonction create attend l’URL d’une ressource, le nom et l’email d’un propriĂ©taire, ainsi que sa valeur initiale (dans la devise propre Ă  la blockchain utilisĂ©e).
Elle renvoie un objet nftlib.NFT qui pourra ĂȘtre utilisĂ© pour les opĂ©rations qui suivent.

>>> nftlib.create('https://zestedesavoir.com/static/images/logo.4950a265b77d.png', author='entwanne', email='entwanne@mail.org', value=10)
NFT(
  id='23ef847e-132e-4379-8fb4-10b09eebb3f6',
  url='https://zestedesavoir.com/static/images/logo.4950a265b77d.png',
  owners=['entwanne <entwanne@mail.org>'],
  value=10,
  created_at=datetime.datetime(2022, 4, 1, 7, 15, 23, 852425),
  updated_at=datetime.datetime(2022, 4, 1, 7, 15, 23, 852425),
)

nftlib.upload

Cette fonction permet simplement d’enregistrer un NFT ainsi crĂ©Ă© sur la blockchain configurĂ©e.

>>> nftlib.upload(nft)
'23ef847e-132e-4379-8fb4-10b09eebb3f6'

La fonction renvoie l’identifiant du NFT, indiquant qu’il a bien Ă©tĂ© tĂ©lĂ©versĂ©.

nftlib.retrieve

À l’inverse, la fonction retrieve permet de retrouver un NFT depuis la blockchain en fonction de son identifiant. Elle renvoie les informations Ă  jour avec la valeur actuelle (suivant la derniĂšre vente) et la liste des propriĂ©taires successifs.

>>> nftlib.retrieve('23ef847e-132e-4379-8fb4-10b09eebb3f6')
NFT(
  id='23ef847e-132e-4379-8fb4-10b09eebb3f6',
  url='https://zestedesavoir.com/static/images/logo.4950a265b77d.png',
  owners=['Clémentine Sanspépins <clem@zestedesavoir.com>', 'entwanne <entwanne@mail.org>', 'Anonymous <xx@yy.com>', 'Omar Sy <omar@matuer.fr>', 'entwanne <entwanne@mail.org>'],
  value=302492203,
  created_at=datetime.datetime(2022, 4, 1, 7, 15, 23, 852425),
  updated_at=datetime.datetime(2022, 4, 1, 8, 24, 46, 989729),
)

On peut voir que mon Ɠuvre s’est dĂ©jĂ  bien vendue !

nftlib.steal

Cette fonction est sans doute la plus utile de toutes, elle regroupe en son sein retrieve, create et upload.
Elle prend l’identifiant d’un NFT existant et permet de crĂ©er un nouvel NFT pour cette ressource, en prĂ©cisant le nouveau propriĂ©taire et la nouvelle valeur. La fonction renvoie le NFT crĂ©Ă© et enregistrĂ©.

>>> nftlib.steal('23ef847e-132e-4379-8fb4-10b09eebb3f6', author='Great Artist', email='greateartist@artwork.com', value=500000000)
>>> nftlib.retrieve('23ef847e-132e-4379-8fb4-10b09eebb3f6')
NFT(
  id='bbe6c258-e4ac-44f3-8fc1-d93f89797e32',
  url='https://zestedesavoir.com/static/images/logo.4950a265b77d.png',
  owners=['Great Artist <greateartist@artwork.com>'],
  value=500000000,
  created_at=datetime.datetime(2022, 4, 1, 8, 31, 4, 540379),
  updated_at=datetime.datetime(2022, 4, 1, 8, 31, 4, 540379),
)

Vous pouvez visiter la documentation du module pour obtenir plus d’informations à son sujet.


  1. Un NFT (non-fungible token soit _jeton non fongible) est un jeton cryptographique qui permet d’identifier le propriĂ©taire d’un contenu.↩

Autres nouveautés

Quelques autres petites nouveautés viennent aussi avec cette derniÚre version de Python :

  • La mĂ©thode str.join qui Ă©tait utilisĂ©e pour construire une chaĂźne autour d’un sĂ©parateur est maintenant dĂ©placĂ©e vers le type list. Elle prend alors le sĂ©parateur en argument pour construire la chaĂźne Ă  partir des Ă©lĂ©ments de la liste.
    >>> ['a', 'b', 'c'].join(',')
    'a,b,c'
    
  • La syntaxe de slicing accepte maintenant une 4Ăšme valeur qui est le pas multiplicatif (de 1 par dĂ©faut). C’est-Ă -dire que cette valeur sera multipliĂ©e au pas prĂ©cĂ©dent Ă  chaque itĂ©ration pour calculer le nouveau pas.
    >>> values = list(range(20))
    >>> values[::1:1]
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
    >>> values[::1:2]
    [0, 1, 3, 7, 15]
    >>> values[1:19:2:3]
    [1, 3, 9]
    
  • La variable __name__ des modules disparaĂźt, rendant invalide la syntaxe if __name__ == '__main__'. Le point d’entrĂ©e d’un script se fait maintenant simplement Ă  l’aide d’une fonction main.

VoilĂ  pour Python 4.0, vous pouvez retrouver plus d’information sur la page dĂ©diĂ©e de la documentation.

Cette version est d’ores et dĂ©jĂ  disponible au tĂ©lĂ©chargement sur le site officiel de Python ou dans votre gestionnaire de paquets favori.

Pas d’inquiĂ©tude si vous vous sentez un peu perdus avec cette nouvelle version, mon cours Un zeste de Python sera bientĂŽt rĂ©Ă©crit entiĂšrement pour reflĂ©ter ces rĂ©cents changements.

N’hĂ©sitez pas Ă  profiter des commentaires pour toute question que vous auriez sur cette version de Python ou ses Ă©volutions futures.

13 commentaires

Super récapitulatifs des nouveautés.

Je suis vraiment pas convaincu de l’utilitĂ© de l’obligation des Annotations de type, c’est peut-ĂȘtre plus clair pour un dĂ©veloppeur confirmĂ©, mais ça risque de perdre pas mal les dĂ©butants. Python n’est pas Java.

Par contre les outils NFT, c’est une vraie bonne nouvelle, ça crĂ©dibilise le langage, c’est bien. En tant que crypto-bro, je vais de ce pas aller m’amuser avec ces nouveaux outils.

+6 -0

C’est une trĂšs bonne nouvelle ! MĂȘme si je suis sceptique sur la majoritĂ© des changements introduits.

Un NFT (non-fungible token soit _jeton non fongible)

đŸ€” IntĂ©ressant.

PS: La rĂ©activitĂ© (il est d’actualitĂ©) et la qualitĂ© de ce billet font qu’il est en premiĂšre page de Google Ă  la recherche “Python 4.0”. đŸ€Ą

+2 -0
>>> nftlib.create('https://zestedesavoir.com/billets/4170/sortie-de-python-4-0/', author='Migwel', email='migwel@nft4life.org', value=10)

Je suis maintenant l’heureux propriĂ©taire de ce billet. À moi les millions !

Migwel

Plus pour longtemps Mouahahah :pirate:

nftlib.steal('https://zestedesavoir.com/billets/4170/sortie-de-python-4-0/', author='Melcore', email='melcore@nonfungibletolkien', value=12)
+0 -0

Enfin !

J’en avais marre que l’ensemble vide me mette une erreur Ă  chaque fois que je l’utilisais. Globalement d’excellentes amĂ©liorations, on voit que le langage devient de plus en plus mature et prends de plus en plus compte des utilisateurs.

Mais mieux encore, on peut aussi reconnaütre les fonctions par rapport à leur contenu !

À noter que cette fonctionnalitĂ© est une prouesse algorithmique. En effet, identifier Ă  coup sĂ»r le contenu d’une fonction est un problĂšme qui avait Ă©tĂ© soulevĂ© par le mathĂ©maticien Rice en 1953.

L’équipe chargĂ©e du dĂ©veloppement de Python 4.0 a Ă©tĂ© la premiĂšre Ă  produire un algorithme qui rĂ©ponde Ă  cette problĂ©matique, et a rĂ©cemment publiĂ© ses rĂ©sultats dans un long article sur Arxiv. Pour arriver Ă  leurs fins, ils ont notamment dĂ» trouver une solution au problĂšme de l’arrĂȘt, un problĂšme Ă©voquĂ© par Turing dĂšs 1936 et pour lequel personne n’avait encore trouvĂ© d’algorithme satisfaisant.

Un grand bravo Ă  Python et Ă  toute son Ă©quipe !

Juste pour ĂȘtre sĂ©rieux 5min, on peut commenter les fonctionnalitĂ©s dont il Ă©tait question ? Ça me semble rigolo.

L’annotation de type, c’est gĂ©nial, vraiment. Mais l’imposer casse tout l’intĂ©rĂȘt de Python. Non ?

Le module typing devient facultatif puisqu’importĂ© automatiquement dans tous les modules.

Ça par-contre, pourquoi pas, tant que le sĂ»r cout est minime.

Le pattern-matching Ă©tendu est une grosse blague algorithmique.

En Python 4, il a donc Ă©tĂ© dĂ©cidĂ© que {} correspondrait Ă  l’ensemble vide.

TrĂšs bonne idĂ©e non ? Et {:} pour un dictionnaire vide. Idem pour les tupples. Par-contre, ça se gĂąte cotĂ© chaĂźne de formatage.

En Python 4, il n’est plus question de prĂ©fixe f pour dĂ©finir des chaĂźnes de formatage (f-strings), celles-ci se dĂ©duisent simplement du dĂ©limiteur utilisĂ©. Les chaĂźnes dĂ©limitĂ©es par des ' ou '’' donnent des chaĂźnes classiques, tandis que celles dĂ©limitĂ©es par " ou """ deviennent les nouvelles chaĂźnes de formatage.

Il n’y avait pas un PEP Ă  ce sujet ? Moi personnellement, je n’approuve pas. Ça me semble confu et peu claire. Encore si """ et ''' n’étaient pas des f-string et ', " l’étaient ça me semblerait plus simple.

Il me semble que l’idĂ©e de dĂ©placer join est une erreur. C’est ce qui est fait en JS je crois.

La variable name des modules disparaĂźt, rendant invalide la syntaxe if name == 'main'. Le point d’entrĂ©e d’un script se fait maintenant simplement Ă  l’aide d’une fonction main.

Pourquoi pas ?

+0 -0

D’aprùs moi les annotations partout rendent le code illisible.
Je me suis fait la rĂ©flexion que join ne respectait pas la logique habituelle. C’est chose faite.
Il aurait peut-ĂȘtre Ă©tĂ© plus simple d’utiliser un prĂ©fixe pour forcer une chaine string classique

+0 -0

La syntaxe de slicing accepte maintenant une 4Ăšme valeur qui est le pas multiplicatif (de 1 par dĂ©faut). C’est-Ă -dire que cette valeur sera multipliĂ©e au pas prĂ©cĂ©dent Ă  chaque itĂ©ration pour calculer le nouveau pas.

deja les slices en python sont complique alors si on ajoute une 4eme valeur :euh: , nos cerveaux vont exploser :p

La variable name des modules disparaĂźt, rendant invalide la syntaxe if name == 'main'. Le point d’entrĂ©e d’un script se fait maintenant simplement Ă  l’aide d’une fonction main.

heritage du c ? o_O

je trouve que c’est beaucoup mieux que if __name__ == '__main__'

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte