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.
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.
- 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 typelist
. 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 syntaxeif __name__ == '__main__'
. Le point dâentrĂ©e dâun script se fait maintenant simplement Ă lâaide dâune fonctionmain
.
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.