URL: https://linuxfr.org/news/parution-de-python-3-5 Title: Parution de Python 3.5 Authors: Tiwaz Benoît Sibaud, palm123, Snark, M5oul, bubar🦥, jcr83, Davy Defaud, Nÿco, Lucas et Pierre Jarillon Date: 2015年09月15日T00:04:29+02:00 License: CC By-SA Tags: python et lwn Score: 55 La version officielle de Python 3.5 est parue le 13 septembre 2015, presque un an après la publication de la [Python Enhancement Proposals (PEP) 0478](https://www.python.org/dev/peps/pep-0478/#id5) qui annoncait le planning de cette dernière, et un peu plus de 8 mois après la première alpha (du 8 février 2015).  Voyons dans la suite de cette dépêche les nouveautés introduites par cette version. ---- [Site officiel de Python](https://www.python.org/) [Un article sur Python 3.5 en Français](http://zestedesavoir.com/articles/264/sortie-de-python-35/) [Les nouveautés de Python 3.5](https://docs.python.org/3/whatsnew/3.5.html) [Keynote de G. van Rossum](https://lwn.net/Articles/651967/) ---- # PEP 441 : amélioration du support des applications ZIP L'interpréteur Python peut, depuis la version 2.6, exécuter des répertoires ou des fichiers archives au format zip. Lorsque l'interpréteur est appelé avec l'argument idoine, ce dernier ajoute le répertoire, ou le contenu du fichier zip, à _sys.path_, et exécute le module __ main __ contenu (dans le répertoire et le fichier zip). Cela permet de publier de manière simple un programme python complexe via un unique fichier. Cette fonctionnalité n'est pas très utilisée. La raison la plus couramment invoquée est que, malgré sa présence dès Python 2.6, elle n'a pas été très médiatisée. L'autre problème vient également du fait que l'installateur Windows n'enregistre pas de type spécifique pour cette fonctionnalité. Cela posait problème, puisqu'un fichier **(.zip)** est souvent perçu comme un fichier à décompresser, alors qu'un fichier Python **(.py)** est plutôt vu comme un fichier texte lisible. Pour remédier à ce problème, deux nouvelles extensions voient le jour : - **.pyz**, pour les applications en mode console ; - **pyzw**, pour les applications en mode fenêtré (pas d’ouverture d’une console). ## Exemple pratique ## Pour créer de tels fichiers, il faut utiliser le module **zipapp**, qui définit, entre autres, la fonction suivante : ```python create_archive(source, target=None, interpreter=None, main=None) ``` Cette dernière est assez explicite. Prenez un répertoire où se situe un code source Python, avec un fichier **__ main __.py** qui sera le point de lancement de l'application. Maintenant, très simplement, utilisez la commande : ```bash python -m zipapp directory -o myUberConsoleApp.pyz ``` Et vous voilà avec un fichier **myUberConsoleApp.pyz** unique à distribuer. Un clic (double ou simple, selon votre configuration), ou un simple appel à **python myUberConsoleApp.pyz** lancera votre **über** programme. # PEP 448 : dépaquetage généralisé (Additional Unpacking Generalizations) # Dur à traduire, mais plus aisé à comprendre. C'est en particulier deux limites qui ont été supprimées. 1. Désormais, la limitation d'un seul argument à « dépaqueter » est levée. Vous pouvez en utiliser autant que vous le désirez, tant pour les fonctions que pour les listes, les tuples ou les dictionnaires. (Note que le dernier argument « dépaqueté » écrase le précédent en cas de conflit). 2. Il est désormais possible de « dépaqueter » dans les déclarations d'éléments itérables (_tuple_/n-uplet, _list_/liste, _set_/collection, _dict_/dictionnaire). ## Exemple pratique ## Vous avez deux listes d'arguments : ```python my_args_1={'site':"My_Crosoft",'operation':"dos"} my_args_2={'botnet_to_user':4,'method':"brute_force"} # Avant, mais ça, c'était avant, vous deviez faire: my_args_1.update(my_args_2) do_the_job(**my_args_1) # ou bien from collections import ChainMap do_the_job(**ChainMap(my_args_1, my_args_2)) #Désormais, un simple: do_the_job(**my_args_1,**my_args_2) suffira. ``` C'est ça, la pureté syntaxique de Python. Moins de sucre, pour autant de cholestérol. ```python # Avant : dict={"b":2,"c":3} result={"a":1} result.update(dict) tab=[2,3,4] result=[1,] + tab # En 3.5 : dict={"b":2,"c":3} result={"a":1,**dict} tab=[2,3,4] result=[1,*tab] ``` et ça marche pour les *args aussi. # PEP 461 : ajout du formateur % pour les octets et tableaux d'octets # Python 3, c'est bien, mais pas pour tout le monde. En particulier, avec son unicodification généralisée, **str** et **bytes** (octets) ne voulaient plus se causer. Un bien (unicode pour les str) pour un mal (les bytes ne permettent pas le formatage direct). Mais pourquoi donc, de nos jours, utiliser les bytes et le formatage direct ? Pour un grand nombre de protocoles, pour l'écriture de fichier binaire, et pour plein d'autres choses souvent utiles. Et le **str** de Python 2.x était pour cela, ma foi, fort pratique. Malgré quelques réticences à rendre les bytes « formattables » grâce à l'opérateur **%**, cela revenant à réintroduire les problèmes de Python 2.x dans Python 3, la simplification induite pour toute une gamme de programme, fut un argument fort et suffisamment pertinent pour son implémentation dans Python 3.5. ## Exemple pratique ## ```python b"%x" % val # remplacant avantageusement le ("%x" % val).encode("ascii") # ou encore, plus parlant:>>> b'%#4x' % 10 ' 0xa'>>> b'%04X' % 10 '000A' ``` # PEP 471 : la fonction os.scandir(), un itérateur meilleur et plus rapide sur un répertoire # Il y a toute une histoire et polémique derrière cette nouveauté, qui concerne Python 2.x, mais nous y reviendrons après vous avoir présenté la chose. **os.walk()** comme chacun le sait, permet d'obtenir un itérateur sur la liste des fichiers et dossiers d'un répertoire. Cette fonction est relativement lente, pour une raison toute simple : son implémentation exécute un appel _stat()_ ou _GetFileAttributes()_ sur chaque entrée pour déterminer si cette dernière est un fichier ou un répertoire. Or, la fonction (_FindNextFile_ / _readdir_) retourne directement cette information, ce qui rend inutile l'appel à _stat()_. C'est ainsi qu'est né **os.scandir()**, un itérateur théoriquement plus rapide. Cette suppression a permis d'obtenir des gains de vitesse assez importants, puisque l'on parle d'une fonction désormais **8 à 9 fois plus rapide sous Windows et 2 à 3 fois plus rapide sur un système POSIX.** (Avec une pointe à 36 fois plus rapide en NFS sous Windows, comme l'indiquent [ces benchmarks](https://github.com/benhoyt/scandir#benchmarks), avec du NFS sans cache sur les propriétés des fichiers, qui reste la configuration par défaut) ## La polémique ## La polémique est née du portage de cette "correction" dans Python 2.x, normalement en mode "maintenance". C'est une optimisation et non une correction de bug, donc il est difficile de comprendre qu'on porte cette amélioration. La ligne directrice était : vous voulez l'amélioration, venez sur Python 3.x. La branche 2.x ne doit recevoir que des corrections de bogues. Cette modification relativement triviale permettrait d'améliorer Python2.7, encore largement utilisé. De fait, le [module scandir](https://pypi.python.org/pypi/scandir) existe pour Python 2.6+, mais n'est pas inclus à la bibliothèque standard, comme dans Python 3.5, # PEP 475 : réessayer les appels système ayant échoué avec un EINTR # Encore une fonction intéressante. Ici, on parle de gérer de manière la plus fine possible les signaux envoyés par l'OS. Par exemple, ce fameux _Break/SIGINT_ (via Ctrl-C) qui interrompt (ou le devrait) tout traitement. En Python 3.4, pour gérer une exception _InterruptError_, cela doit être fait et dupliqué partout. Or, bien peu de modules le font vraiment. Par exemple, voici comment écrire une simple lecture de fichier, si l'on refuse d'être interrompu : ```python while True: try: data = file.read(size) break except InterruptedError: continue ``` Couvrir l'ensemble des modules pour gérer correctement cette exception est complexe et long, ce qui a conduit à un état de fait : peu de modules gèrent ce _InterruptedError_. (asyncio,asyncore,io , _pyio, multiprocessing, selectors, socket, socketserver, subprocess sont les seuls modules à le gérer). D'autres langages de programmation traitent le problème différemment, à un niveau plus bas, ce qui permet aux bibliothèques et aux applications de s'en abstraire. Python décide d'aller lui aussi dans cette voie. S'il est toujours possible d'attacher une fonction à un signal, cette dernière sera appelée et du résultat dépendra la levée ou non d'une exception. Si aucune exception n'est levée, alors Python recommencera l'opération système sur lequel il s'était à l'origine arrêté, et si cette opération système disposait d'un _timeout_, ce dernier sera recalculé. # PEP 479 : modification de la gestion de StopIteration dans les générateurs # L'exception StopIteration combinée avec des générateurs est souvent source d'ennuis, surtout lorsqu'il est nécessaire de déboguer, avec l'introduction de bugs silencieux. Cette PEP clarifie donc un point essentiel, qui est que pour quitter un générateur, il faut utiliser un **return** et non lever une exception de type StopIteration. Désormais, si une exception de type _StopIteration_ survient dans un générateur, elle est remplacée par une _RuntimeError_, qui fait échouer le prochain appel à `next()`, et permet de dépiler correctement l’exception. Ce changement est important, puisqu'il semble que de nombreux codes utilisaient cette exception pour sortir d'un itérateur. Il sera donc nécessaire de faire attention à vos sources lors d'un passage à la version 3.5 # PEP 484 : module typing, le nouveau standard pour les annotations de typage # Je reste dubitatif devant cette PEP, même si cette dernière vient de [Guido van Rossum lui-même](https://www.python.org/dev/peps/pep-0484/). Les annotations existent [depuis Python 3](https://www.python.org/dev/peps/pep-3107/), et étaient jusqu'à présent relativement souples, permettant d'utiliser n'importe quel objet, qui sera stocké dans l'attribut *__annotations__*. Cela ne sert à rien (l'interpréteur n'utilise pas cette information), c'est du sucre syntaxique. Éventuellement, cela permet de mieux documenter les fonctions. ```python # Cette fonction prend en argument une chaîne de caractères et en retourne une. def upper_vowel(words: str) -> str: return "".join([(lambda a: a.upper() if a in "aeiouy" else a)(letter) for letter in words]) ``` Python 3.5 vient donc restreindre et préciser le rôle des annotations. Par convention, elles deviennent réservées au typage. Cela consiste à indiquer le type des arguments et préciser également le type des retour des fonctions. Pour cela un module **typing** a été créé pour permettre de définir des types génériques. Conscient que ces annotations peuvent nuire à la lisibilité du code, nous obtenons l'ajout de fichiers Stub, qui permettent de créer des annotations pour un fichier source dans un fichier séparé **.pyi**, avec une syntaxe spécifique, qui reprend uniquement le nom des classes et des fonctions annotés. # PEP 485 : math.isclose(), a function for testing approximate equality # Lorsqu'on en vient aux flottants et aux approximations, il est nécessaire de toujours faire attention aux inégalités. Bref, cela n'est pas spécifique à Python, mais à la représentation des flottants. ```python math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0) # Utilisable ainsi: math.isclose(1.1,1.2,0.1) =>True ``` Par défaut, la valeur relative de test est de 1e-9 (c'est explicité dans l'aide de la fonction) ## À connaître :## NaN est considéré comme n'étant proche d'aucune autre valeur, même NaN: math.isclose(NaN,NaN) => False math.isclose(NaN,0) => False Inf et -Inf ne sont proches que d'eux-mêmes. # PEP 488 : suppression des fichiers .pyo # Les fichiers .pyo disparaissent... Mais pas d'inquiétude, il est toujours possible d'utiliser Python avec les options -o et -oo. C'est la nomenclature qui change. En effet, utiliser Python avec -o ou -oo conduisait à la création de ces fichiers **.pyo**, indifférenciables du niveau d'optimisation, ce qui pouvait prêter à confusion. Désormais nous avons: python => .pyc python -o => .opt-1.pyc python -oo => .opt-2.pyc # PEP 0465 : un opérateur infixe dédié pour la multiplication matricielle # Python 3.5 voit l'apparition d'un nouvel opérateur, l'opérateur de la multiplication matricielle. Bien qu'en soit, cela soit une fonctionnalité qui semble importante, elle a été grandement sur-médiatisée. Cet opérateur, **@** remplace la fonction de classe idoine sur les objets de type matrice, déjà développée et utilisée par d'autres bibliothèques (np.dot dans numpy par exemple). ```python S = (H.dot(beta) - r).T.dot(inv(H.dot(V).dot(H.T))).dot(H.dot(beta) - r) # pourra s'écrire désormais S = (H @ beta - r).T @ inv(H @ V @ H.T) @ (H @ beta - r) ``` La seconde forme est beaucoup plus lisible et proche de la notation mathématique. En pratique, c'est inutile sans bibliothèque externe spécifique, puisqu'aucun objet ou type de la bibliothèque standard ne prend en compte cet opérateur. Il a été créé spécifiquement pour les bibliothèques de calcul numérique en Python, tel que [numpy](http://www.numpy.org/), [pandas](http://pandas.pydata.org/), [blaze](http://blaze.pydata.org/en/latest/) ou encore [thenao](http://deeplearning.net/software/theano/). # PEP 492 : coroutines avec les mots-clés async et await # Ici, c'est la création de deux nouveaux mot-clés, **async** et **await** dont il est question. La programmation asynchrone n'ayant pas attendu Python 3.5, le nombre de bibliothèques externes dédiées, ainsi que le relatif regain d'intérêt pour ce type de programmation ont poussé une méthode standard dans le cœur du langage. En particulier, c'est l'intégration de la [bibliothèque **asyncio**](https://docs.python.org/3/library/asyncio.html) dont il est question. De l'extérieur, **async** vient remplacer le décorateur **asyncio.coroutine** et **await** l'expression **yield from**. La présentation du concept de générateur (dont les fonctions await et async sont parentes) et de la programmation asynchrone sont légèrement au-delà du but de cette dépêche. Mais voici un exemple illustrant le concept de programmation avec ces deux nouveaux mots-clés. ## Exemple ## ```python import asyncio async def echo_server(): print('Serving on localhost:8000') await asyncio.start_server(handle_connection, 'localhost', 8000) async def handle_connection(reader, writer): print('New connection...') while True: data = await reader.read(8192) if not data: break print('Sending {:.10}... back'.format(repr(data))) writer.write(data) loop = asyncio.get_event_loop() loop.run_until_complete(echo_server()) try: loop.run_forever() finally: loop.close() ``` # En vrac # - Une nouvelle exception **RecursionError** est désormais levée lorsque la profondeur maximale de récursion est atteinte. Précédemment, nous avions droit à une **RuntimeError**. # Améliorations générales # ## Performance ## - collections.OrderedDict est maintenant implémenté en C, ce qui permet des gains d'un facteur **4 à 100**. - **functools.lru_cache()** a été largement réécrit en C, permettant d'obtenir de meilleures performances (facteur **4 à 6**) - de nombreuses opérations sur les adresses IP (comme **subnets(), supernet(), summarize_address_range(), collapse_addresses(),** ...) ont subi une refonte avec des améliorations de performance allant d'un facteur de **3 à 15**. - de nombreuses opérations sur **io.BytesIO** sont maintenant **1,5 à 2** fois plus rapides, via une réimplémentation des allocations mémoire similaires à StringIO. - la fonction **marshal.dumps()** peut-être jusqu'à **5** fois plus rapide dans le meilleur des cas. Sinon, l'amélioration se situe entre 20% et 80%. - l'encodeur UTF-32 est désormais entre **3 et 7** fois plus rapide. - les expressions régulières sont désormais traitées **10%** plus rapidement. - instancier des **fractions.Fraction** est désormais **30%** plus rapide. - les objets utilisés par le module **random** utilisent désormais deux fois moins de mémoire sur les compilations 64bits - les méthodes de l'objet **String** find(), rfind(), split(), partition() ainsi que les opérateurs sont désormais significativement plus rapides pour les recherches de sous-chaînes d'un caractère seulement. - **json.dumps()** a été optimisé de manière à ce que l'option **ensure_ascii=False** soit désormais aussi rapide que **ensure_ascii=True** ## Sécurité ## - **SSLv3** est maintenant désactivé, même s'il est possible de le réactiver en instanciant manuellement un ssl.SSLContext. - le module SSL a été retravaillé pour permettre de séparer la partie réseau de la gestion du protocole. - l'analyse des cookies HTTP est désormais plus simple et plus stricte, évitant d'éventuelles attaques par injection. ## Petit changement important ## - **subprocess.run()** devient la fonction recommandée pour exécuter ses sous-processus. - le module **traceback** s'enrichit de deux nouvelles fonctions, walk_stack() et walk_tb(), ainsi que de trois nouvelles classes : TracebackException, StackSummary, and FrameSummary. - les fonctions **getfullargspec(), getargvalues(), getcallargs(), getargvalues(), formatargspec()** et **formatargvalues()** sont obsolètes. Il est recommandé d'utiliser l'API **inspect.signature()** en remplacement. - bool(datetime.time(0,0))==True (Précédement, cela retournait False) - ssl.SSLSocket.send() lève une exception plutôt que retourner 0 sur une socket non-bloquante si une opération bloque. - pour les noyaux supérieurs à 3.6, les sockets Python exportent désormais la fonctionnalité **CAN_RAW_FD_FRAMES** # L’avis de Guido # Le [discours d'ouverture](https://lwn.net/Articles/651967/) de Guido van Rossum pour [l'EuroPython 2015](https://ep2015.europython.eu/) était intéressant à bien des égards. En voici les grandes lignes pour les curieux. Il a ainsi tenu un discours sur la nécessité de passer à Python 3, tout simplement parce que c'est un meilleur langage, plus simple à apprendre (plus cohérent). De plus, il s'améliore avec le temps, là où Python 2.7 est un langage figé. Pour ma part, cette évolution peut également être un frein pour de nombreuses équipes, puisqu'il y a régulièrement de la **casse**, ou même des régressions, là où Python 2.7 apporte une certaine stabilité. Par ailleurs, il travaille chez DropBox, où ils utilisent une version de Python 2.7 largement modifiée. Certaines parties pourraient largement tirer bénéfices de l'utilisation de pypy, mais l'entreprise est très _frileuse_ à l'idée des incompatibilités possibles & subtiles que cela pourrait engendrer, via des bugs complexes à reproduire et corriger. C'est le problème principal des grosses bases de code, qu'il devient complexe de faire migrer après une masse critique de déploiements atteinte. Et c'est également le problème de Python 3. Et pour finir, il explique un point noir, pour lui : tout ce qui tourne autour de la distribution de paquets pour Python. Il n'existe, selon lui, pas de solution miracle à ce problème.