Sortie de Python 3.7

74
10
sept.
2018
Python

Python 3.7 a été publié le 27 juin 2018, soit un an et demi après la précédente version. Celle‐ci vient avec son lot de nouveaux modules et fonctionnalités que nous détaillons dans la suite de la dépêche.

Logo de Python

Sommaire

Python 3.7 en résumé

La fonctionnalité la plus « marquante » est sans doute l’introduction du module dataclasses qui permettra aux développeurs d’écrire des classes avec une syntaxe bien plus concise qu’à l’accoutumée. Une nouvelle fonction native a été introduite, à savoir breakpoint(), facilitant l’utilisation du débogueur et offrant la possibilité, pour des usages plus avancés, de le personnaliser. Le module asyncio a été fortement amélioré, notamment en simplifiant l’API ; en plus de cela, async et await deviennent désormais des mots clefs. Ajoutons à cela, le fait que la conservation de l’ordre d’insertion des dict est désormais officielle d’après les spécifications, et vous aurez une bonne vision des modifications liées à la syntaxe du langage.

Python 3.7 apporte aussi des améliorations sur des fonctionnalités plus avancées, comme la possibilité de personnaliser l’accès aux attributs de module, mais aussi l’introduction du module typing dans le cœur de Python, ainsi que la possibilité d’effectuer une évaluation différée des annotations, ce qui amène à Python des évolutions intéressantes concernant son système de typage.

Enfin, cette version vient avec la possibilité de faire des builds reproductibles, et de très nombreuses améliorations de performance faisant de Python 3.7 la version la plus rapide de Python.

Nouveaux modules

PEP 557 : Dataclasses

La PEP 557 introduit le nouveau module dataclasses qui fournit le décorateur @dataclass permettant d’écrire une « classe de données » de façon plus concise. Les dataclasses peuvent être vues comme un named tuple mutable avec des valeurs par défaut. Le décorateur de classe repose sur la PEP 526 qui a introduit les annotations de variables. On peut donc désormais écrire ceci en Python 3.7 :

from dataclasses import dataclass

@dataclass
class BankAccount:
    """class that corresponds to a bank account."""
    bank_name: str  # no default value
    owner_name: str
    currency: str = "dollar"  # assign a default value for 'currency'
    value: int = 0

    def tax(self, val) -> int:
        self.value -= val

Cela donne ensuite :

>>> print(BankAccount('BNP', 'toto'))
BankAccount(bank_name='BNP', owner_name='toto', currency='dollar', value=0)
>>> account = BankAccount('LCL', 'tata', 'euros', 1000)
>>> account.tax(5)
>>> print(account.value)
995

L’intérêt d’utiliser une dataclass et non pas un namedtuple, c’est qu’il s’agit ni plus ni moins d’une classe normale en Python. On peut donc utiliser tous les concepts de la programmation objet associés aux classes (héritage, métaclasse, doc strings). On voit dans l’exemple ci‐dessus que le décorateur va générer une classe et va fournir automatiquement le __init__, le __repr__, ainsi que la fonction d’égalité __eq__ :

>>> print(account)
<class '__main__.BankAccount'>
>>> dir(account1)
['__annotations__', '__class__', '__dataclass_fields__', '__dataclass_params__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bank_name', 'currency', 'owner_name', 'tax', 'value']

Il est possible de passer des paramètres au décorateur pour générer, par exemple, des fonctions de comparaison avec le paramètre order. Par exemple, la classe suivante permettra de faire des comparaisons :

@dataclass(order=True)
class Item:
    price: float
    product_name: str 

>>> pomme = Item(1.5, 'pomme')
>>> ordinateur = Item(500, 'ordinateur')
>>> pomme < ordinateur
True

Python va comparer les éléments en considérant que les éléments sont des tuples, il va donc comparer les champs un par un.

Il est possible de convertir une instance dataclass en tuple ou dict via les fonctions astuple() ou asdict() disponibles dans le module. Dans l’exemple ci‐dessus, cela donne :

from dataclasses import astuple, asdict
>>> print(astuple(pomme))
(1.5, 'pomme')
>>> print(asdict(pomme))
{'price': 1.5, 'product_name': 'pomme'}

Les dataclasses peuvent faire penser à l’utilisation que l’on peut faire des named tuples, mais ces derniers ont certaines restrictions. De par leur nature, ce sont des objets immuables. Ainsi, si l’on écrit :

from collections import namedtuple
Item = namedtuple('Item', ['price', 'product_name'])
apple = Item(2, 'apple')

Modifier le champ price va provoquer une erreur :

>>> apple.price = 3
AttributeError: can't set attribute

De même, on peut accidentellement comparer deux named tuples qui ont les mêmes valeurs, mais qui ne représentent pas les mêmes objets :

Stock = namedtuple('Stock', ['quantity', 'name'])
apple_stock = Stock(2, 'apple')
>>> apple_stock == apple
True

L’utilisation de dataclass empêche cela car la comparaison de deux objets de classes différentes renverra toujours False.

PEP 567 : Variables contextualisées

Cette nouvelle fonctionnalité demande une petite introduction.

Si l’on prend un programme « classique » mono‐thread avec une exécution linéaire, toutes les variables sont accessibles simplement en lecture ou écriture et ne sont pas susceptibles de changer. Jusque‐là, rien de compliqué.

Maintenant, prenons le cas d’un programme multi‐thread. Le contexte d’exécution — et en particulier toutes les variables — est partagé entre tous les threads. Il est donc difficile pour un thread de manipuler des données privées, réservées au thread. Python propose une solution pour ce problème avec l’API threading.local, qui fournit un objet où la modification et la récupération d’une variable donnée pourra être différente suivant le thread qui l’appelle.

Avec l’arrivée de l’asynchronicité dans l’exécution du code, de nouveaux problèmes se présentent : on peut avoir à l’intérieur du même thread d’exécution plusieurs fonctions qui s’exécutent de façon asynchrone et qui auraient besoin d’un contexte différent par fonction plutôt que par thread.

C’est ce problème que résout le nouveau module contextvars. Son introduction a été discutée et approuvée via la PEP 567 — Variables contextualisées. Cette PEP est elle‐même un sous‐ensemble d’une PEP plus ambitieuse, la PEP 550 — Contexte d’exécution. Cette dernière apportait la notion de contexte à un ensemble plus large de construction Python (en particulier les générateurs) mais n’a pas fait l’unanimité parmi les core‐developers. L’auteur de la PEP, Yury Selivanov, a repris les parties qui faisaient consensus dans la PEP 567, qui a été intégrée dans Python 3.7.

L’exemple suivant utilise asyncio et une liste pour créer trois contextes différents dans init_var(), puis le programme appelle la fonction hello(), à chaque fois avec une valeur différente de name programmée dans le contexte :

import contextvars
import asyncio
import datetime

all_names = ['Donald', 'Mickey', 'Dominique']

# La variable contextuelle
name = contextvars.ContextVar('name')

def hello():
    date = datetime.datetime.now().strftime('%H:%M:%S')
    print('Bonjour {}, il est {}'.format(name.get(), date))

def init_var(event_loop):
    name.set(all_names.pop())
    event_loop.call_later(1, hello)

# on récupère une boucle d’évènements
event_loop = asyncio.get_event_loop()

# on planifie des appels toutes les secondes
event_loop.call_later(0, init_var, event_loop)
event_loop.call_later(1, init_var, event_loop)
event_loop.call_later(2, init_var, event_loop)

# on prévoit de s’arrêter tout de même
event_loop.call_later(5, event_loop.stop)

# on met tout en route et on attend
event_loop.run_forever()

Le résultat :

>py -3.7 asyncio_example2.py
Bonjour Dominique, il est 13:10:03
Bonjour Mickey, il est 13:10:04
Bonjour Donald, il est 13:10:05

Ajout de l’API « importlib.resources » pour lire des ressources embarquées

Le terme « ressource » désigne ici un fichier de données. Avec cette nouvelle API, il est possible d’accéder en lecture à un fichier qui serait embarqué dans un paquet Python, et donc installé en même temps que le paquet. Ou bien de donner un accès en lecture à une donnée qui fait partie de l’application distribuée mais n’est pas représentée strictement comme un fichier.

Accéder à des ressources de données est un besoin courant pour certains types d’applications. Par exemple, les applications graphiques peuvent ainsi distribuer aisément leurs icônes et images.

Avant la disponibilité de cette API, embarquer un fichier de données à l’empaquetage et y accéder à l’exécution se faisait soit à coups de bidouilles sur la variable __file__ pour localiser le module en cours et en déduire l’emplacement du fichier (la méthode old school, qui ne marchait pas toujours dans des environnements complexes), soit en utilisant l’API Basic Resource Access de setuptools, l’outil d’empaquetage de Python.

C’est surtout ce dernier qui est remplacé par la nouvelle API, plus robuste et plus simple à utiliser. Une documentation spécifique est disponible pour migrer de pkg_resources à importlib.resources, en plus de la documentation standard.

À noter la disponibilité d’un portage vers les précédentes versions de Python (2.7, 3.4, 3.5 et 3.6) via le paquet importlib_resources sur PyPi. C’est d’ailleurs dans ce paquet que l’API a maturé dans les six derniers mois avant de rejoindre Python 3.7.

Syntaxe et caractéristiques des dictionnaires

async et await deviennent des mots clefs réservés

Pour éviter les problèmes de rétro‐compatibilité, async et await n’ont pas été ajoutés à la liste des mots clefs réservés lorsqu’ils ont été inclus dans Python 3.5.
Il était donc possible de nommer des variables et des fonctions async ou await. Dorénavant, ce n’est plus possible en Python 3.7 :

>>> async = 42
  File "<stdin>", line 1
    async = 42
          ^
SyntaxError: invalid syntax
>>> def await():
  File "<stdin>", line 1
    def await():
            ^
SyntaxError: invalid syntax

Ordre d’insertion dans les dictionnaires

La conservation de l’ordre d’insertion dans les dict est désormais officielle. Ce comportement provient au départ d’un effet de bord de l’amélioration de l’implémentation des dictionnaires (consommation mémoire réduite de 20 à 25 %) réalisée pour la version 3.6. Cette propriété a été dans un premier temps définie en tant que détail d’implémentation de CPython 3.6, afin de ne pas trop perturber la compatibilité arrière et les autres implémentations de Python.

Concrètement, en Python 3.5 :

>>> d = {}
>>> d['a'] = 1
>>> d['z'] = 2
>>> for k in d: print(k)  # affiche toutes les clefs du dictionnaire
z
a

L’ordre de parcours des clefs du dictionnaire est non défini à l’avance, et peut bouger au fur à mesure que le dictionnaire évolue :

>>> d['j'] = 3
>>> for k in d: print(k)  # affiche toutes les cles du dictionnaire
z
j
a

Le nouvel élément ajouté a changé l’ordre de parcours. Maintenant, en Python 3.6 et 3.7 :

>>> d={}
>>> d['a'] = 1
>>> d['z'] = 2
>>> for k in d: print(k)
a
z
>>> d['j'] = 3
>>> for k in d: print(k)
a
z
j

Les clefs sont maintenant affichées dans l’ordre où elles ont été ajoutées au dictionnaire. Un effet de bord sympa est que pour les fonctions à arguments par mot clef, l’ordre est préservé aussi.

L’ancien comportement en Python 3.5 :

>>> def f(**kw):
...   for k in kw: print(k)
>>> f(a=1, j=2, z=3)
z
j
a
>>> f(z=3, a=2, j=1)
z
j
a

On constate que l’ordre d’affichage des clefs est celui du dictionnaire utilisé, et va donc varier en fonction des clefs du dictionnaire.

Maintenant, en Python 3.6 et 3.7 :

>>> f(a=1, j=2, z=3)
a
j
z
>>> f(z=3, j=2, a=1)
z
j
a

L’ordre d’affichage dépend désormais de l’ordre de création des clefs. Ce n’est pas une killer‐feature de Python, mais c’est plutôt confortable.

Note personnelle de l’un des auteurs : Ça m’est arrivé plusieurs fois lors de phases de débogage d’être perturbé parce que les nouvelles entrées dans un dictionnaire étaient affichées au milieu plutôt qu’à la fin. Ce ne sera plus le cas.

À noter que la classe OrderedDict du module collections devient beaucoup moins utile, mais pas nécessairement obsolète : son auteur Raymond Hettinger explique dans un courriel que l’implémentation de OrderedDict est optimisée afin de permettre des ré‐ordonnancements rapides et fréquents lors de l’évolution du dictionnaire. Ce n’est pas le cas des dict normaux qui, eux, sont optimisés pour être compacts en mémoire et rapides lors des accès. Une autre différence est que pour être égaux, deux dict normaux ont besoin d’avoir les mêmes clefs et valeurs, alors que deux OrderedDict ajoutent la contrainte que les clefs ont été insérées dans le même ordre.

Améliorations

PEP 553 : Introduction de la fonction breakpoint

La PEP 553 introduit la nouvelle fonction native breakpoint() pour faciliter et uniformiser l’accès au débogueur Python.

Auparavant, pour faire appel à pdb, le débogueur standard de Python, vous deviez écrire import pdb; pdb.set_trace(). Désormais, vous pourrez faire breakpoint() uniquement. Cela a pour premier avantage de réduire le nombre de caractères à taper, et que ce soit plus facile à retenir ; mais le véritable avantage est la possibilité de personnaliser le comportement du débogueur.

En effet, cette nouvelle fonction native fait appel à la fonction sys.breakpointhook() qui, elle‐même, interroge une nouvelle variable d’environnement nommée PYTHONBREAKPOINT. Selon la valeur choisie, on peut désactiver le débogage (pour le désactiver dans un environnement de production), utiliser le comportement par défaut avec l’appel à pdb, ou appeler un autre débogueur (celui intégré dans l’EDI utilisé, par exemple).

PEP 560 et PEP 563 : Améliorations autour des annotations

Les annotations des paramètres et des valeurs de retour de fonctions ont été introduites en même temps que le passage à Python 3.0, par la PEP 3107 en 2006. À l’époque, la syntaxe a été choisie de façon « neutre » afin de ne pas forcer un fonctionnement spécifique alors que l’annotation de type était encore quelque chose de très expérimental (voire d’inexistant) et novateur. Guido avait bien en tête que cela évoluerait vers de l’annotation de type poussée (cf. son blog), mais il était trop tôt pour figer cela dans le marbre et, surtout, le passage à Python 3 occupait déjà beaucoup de monde.

Par neutre, on entend que l’on pouvait rajouter des trucs (techniquement des expressions) après les arguments et les valeurs de retour des fonctions, mais que ces trucs étaient totalement ignorés par l’implémentation, en dehors du fait qu’ils soient accessibles.

Les annotations étaient décrites dans la grammaire en tant que :

    def foo(a: expression, b: expression = 5, *args: expression, **args: expression) -> expression:
        ...

Les deux exemples d’utilisation potentielle donnés dans la PEP étaient :

    def compile(source: "something compilable",
                filename: "where the compilable thing comes from",
                mode: "is this a single statement or a suite?"):
        ...

    def haul(item: Haulable, *vargs: PackAnimal) -> Distance:
        ...

Évidemment, c’était surtout le cas haul() qui motivait l’évolution.

L’aspect « neutre » signifiait qu’en dehors de la syntaxe du langage et d’une fonction pour rapatrier la chaîne de l’annotation, on ne touche pas au fonctionnement interne du langage.

Avançons de huit années, jusqu’en 2014. Durant ces huit ans, des outils se sont emparés des possibilités ouvertes par l’annotation de type et ont ainsi permis de vérifier statiquement la cohérence des types d’un ensemble de modules Python. Le plus connu alors est mypy, développé par l’équipe de Dropbox, dans laquelle travaille Guido. Lorsqu’on l’exécute sur un ensemble de code Python, il fournit un rapport sur la cohérence globale des types déclarés dans les fonctions et leur utilisation (de la même façon que les compilateurs C++, Java, etc.).

En 2014, Guido propose une nouvelle PEP, la PEP 484Indication de types, pour standardiser la façon dont on décrit le type d’un paramètre dans les annotations. Le but est de permettre aux différents outils travaillant avec les annotations de type d’utiliser un vocabulaire commun.

Cette PEP ajoute un module typing à Python 3.5, qui permet de définir des types complexes à utiliser lors de l’annotation de type. Le module est provisionnel, ce qui veut dire qu’il est proposé en l’état, mais qu’il pourrait encore évoluer.

Concrètement, on peut maintenant écrire :

    def adder(val: int, l: Sequence[int]) -> List[int]:
        ret = []
        for v in l:
            ret.append( v+val )
        return ret

L’annotation val: int était déjà possible avant. La nouveauté de la PEP 484 concerne tous les types élaborés comme Sequence, List, Dict et Any, ainsi que toutes les constructions pour fabriquer des types plus spécifiques ou plus génériques.

Le code ci‐dessus nécessite d’être préfixé par la ligne suivante :

    from typing import Sequence, List

En effet, les annotations sont des expressions ordinaires qui respectent la syntaxe Python.

On note aussi la syntaxe inhabituelle, <container class> [ <type> ]. Il fallait une syntaxe lisible (pour rester dans l’esprit du langage) mais distincte de ce qui existe déjà, et qui ne crée pas de confusion pour l’interpréteur Python. Il y a eu pas mal de débat pour arriver à ce choix.

Cette syntaxe est possible grâce à l’utilisation d’une méthode __getitem__() dans la méta‐classe . Si vous ne savez pas ce qu’est une méta‐classe, ne vous en faite pas, c’est très très spécifique. En gros, une méta‐classe fonctionne pour une classe de la même façon qu’une classe pour une instance : elle permet de contrôler la création de la classe et de lui adjoindre des méthodes génériques disponibles pour toutes les classes qui y réfèrent. Ça permet notamment d’adjoindre et de partager des comportements génériques par des classes sans passer par le système classique d’héritage. On pourrait écrire trois pages sur le sujet sans le couvrir réellement.

Toujours est‐il qu’on reste dans l’esprit d’une modification « neutre » du langage au sens où l’on utilise deux fonctionnalités déjà présentes, un module et des méta‐classes pour proposer l’annotation de type.

La prise en charge des annotations a continué d’évoluer, avec dans Python 3.6 la PEP 526Une syntaxe pour les annotations de variables, qui permet désormais d’annoter le type d’une variable en plus des arguments d’une fonction.

Concrètement, cela prend la forme de :

    primes: List[int] = []

    captain: str  # Note: no initial value!

    class Starship:
        stats: ClassVar[Dict[str, int]] = {}

C’est particulièrement utile lors de l’initialisation de conteneurs vides, pour lesquels l’analyseur de type ne peut savoir quels seront les types stockés.

C’est une standardisation dans le langage d’une fonctionnalité déjà présente dans mypy qui permettait la même chose, mais en s’appuyant sur des commentaires :

    primes = []  # type: List[int]

    class Starship:
        stats = {}  # type: ClassVar[Dict[str, int]]

Pour en revenir aux annotations, la neutralité et la généricité ont commencé à montrer leur limites lorsque l’on en fait un usage intensif. C’est ce qui est arrivé ces dernières années, et qui a motivé deux PEP implémentées dans Python 3.7.

L’utilisation poussée du module typing a montré qu’il était plutôt lent, du fait de l’utilisation intensive des méta‐classes et notamment de l’héritage multiple de méta‐classes. C’est pourquoi Python 3.7 introduit la PEP 560 — Gestion dans le cœur de Python du module typing et des types génériques. Comme le dit son nom, cette PEP intervient dans le cœur de l’interpréteur CPython, sur des aspects techniques et hardcore pour accélérer et simplifier le module typing.

De son côté, la PEP 563 — Évaluation différée des annotations, vise à corriger une erreur de jeunesse sur les annotations : celles‐ci sont évaluées lors du chargement des modules Python. Pour un programme qui contient beaucoup d’annotations, il y a donc un coût en temps, au démarrage du programme, alors même que les annotations ne sont pas utilisées. L’autre inconvénient est que l’évaluation directe ne permettait pas les « références par avance » (forward references), où l’on définit plus bas dans le code un type que l’on souhaite utiliser dès maintenant pour l’annotation.

La proposition de la PEP est de stocker la chaîne de caractères de l’expression dans le champ __annotation__, plutôt que directement le résultat de l’expression évaluée. Pour les outils de vérification de type, ça ne change rien, car il était déjà possible de stocker une chaîne de caractères pour résoudre, justement, le problème des références par avance.

Pour d’autres usages des annotations, il faudra forcer un appel eval(ann, globals, locals) pour obtenir l’expression de l’annotation. À noter que l’expression n’est plus évaluée dans son périmètre d’origine (à l’analyse du module) mais dans le périmètre qui appelle eval, ce qui peut avoir des conséquences si l’expression utilisait des variables du périmètre local. Pour cette raison, c’est un changement incompatible avec le passé.

Du fait de cette incompatibilité, beaucoup de précautions sont prises pour l’activation de cette fonctionnalité :

  • pour Python 3.7, ce n’est pas actif par défaut et il faudra une importation explicite pour en bénéficier :

    from __future__ import annotations
  • pour Python 3.8, l’utilisation d’annotations incompatibles avec la PEP 563 déclenchera un PendingDeprecationWarning, signifiant en gros « attention, bientôt, l’usage sera obsolète » ; on parle ici uniquement de l’usage d’expressions dans les annotations qui dépendent du périmètre local, le reste fonctionne très bien en l’état ;

  • pour Python 3.9, on passera à un DeprecationWarning, signifiant cette fois : « maintenant, cet usage est obsolète » ;

  • et pour Python 4.0, une erreur sera levée à la compilation.

À noter qu’avec ces deux PEP, les annotations de type rentrent dans le cœur du langage Python, même si elles restent totalement optionnelles.

Amélioration du module asyncio

Le module asyncio a été introduit dans Python 3.4 pour gérer les entrées‐sorties asynchrones, les boucle d’évènements, les co‐routines et les tâches. Dans Python 3.7, asyncio a reçu plusieurs nouvelles fonctionnalités, ainsi que des améliorations d’utilisabilité et de performance.

Parmi les nouveautés, on peut citer la nouvelle fonction asyncio.run() qui simplifie l’appel de co‐routines depuis un code synchrone :

import asyncio

async def bonjour():
    print('Bonjour les Moules< !')

asyncio.run(bonjour())

Autre nouveauté, asyncio accepte maintenant les variables de contexte. Voir la PEP 567 pour plus de détails.

La nouvelle fonction asyncio.create_task() a été ajoutée comme raccourci de asyncio.get_event_loop().create_task().

La nouvelle fonction asyncio.current_task() retourne la tâche courante et la nouvelle fonction asyncio.all_tasks() retourne l’ensemble des tâches existantes dans une boucle donnée. Les méthodes Task.current_task() et Task.all_tasks() sont devenues obsolètes.

La nouvelle fonction asyncio.get_running_loop() retourne la boucle courante et lance une RuntimeError s’il n’y a pas de boucle s’exécutant actuellement. À la différence de asyncio.get_event_loop() qui va créer une nouvelle boucle plutôt que de lancer une exception.

La nouvelle méthode StreamWriter.wait_closed() permet d’attendre jusqu’à la fermeture d’un flux. StreamWriter.is_closing() peut être utilisé pour savoir si le flux est en cours de fermeture.

La nouvelle méthode loop.sock_sendfile() permet l’envoi d’un fichier en utilisant os.sendfile quand c’est possible.

Les nouvelles méthodes Task.get_loop() et Future.get_loop() retournent leur boucle d’origine. Server.get_loop() permet de faire la même chose pour les objets asyncio.Server.

Il est maintenant possible de contrôler le lancement des instances asyncio.Server. Auparavant, le serveur se lançait immédiatement après sa création. Le nouvel argument start_serving de loop.create_server() et loop.create_unix_server(), ainsi que Server.start_serving() et Server.serve_forever() peut être utilisé pour découpler l’instanciation du serveur et le lancement du serveur. La nouvelle méthode Server.is_serving() retourne True si le serveur est lancé.

Les objets retournés par loop.call_later() ont maintenant une méthode when() qui retourne leur heure d’exécution estimée.

Les exceptions lancées lorsqu’une tâche est annulée ne sont plus journalisées.

Plusieurs API asyncio sont devenues obsolètes.

PEP 564 : Gestion des nanosecondes pour le module time

Après avoir permis à Python 3.3 d’avoir une horloge monotone, après avoir amélioré les méthodes de mesures des micro‐benchmarks, Victor Stinner nous propose cette fois d’améliorer la précision des mesures de temps en Python.

La PEP 564 introduit la gestion des nanosecondes dans le module time. La précision des horloges dans les systèmes d’exploitation modernes peut excéder la précision du nombre flottant de secondes retourné par la fonction time.time(), ainsi que ses différentes variantes.

Pour éviter la perte de précision, cette PEP ajoute six nouvelles fonctions liées à la précision nanosecondes :

  • time.clock_gettime_ns() ;
  • time.clock_settime_ns() ;
  • time.perf_counter_ns() ;
  • time.process_time_ns() ;
  • time.time_ns().

Des mesures montrent que la résolution de time.time_ns() est désormais trois fois plus précise que celle de time.time(). Sur la machine utilisée pour les benchmarks, le plus petit intervalle mesuré s’approche des 300 ns sous Windows et fait moins de 100 ns sous GNU/Linux.

Message d’erreur ImportError

L’erreur ImportError va désormais afficher un message d’erreur plus explicite en affichant le nom du module et le chemin du fichier utilisé lorsque l’expression from … import … échoue.

Concrètement, avec Python 3.6, on a :

>py -3.6
>>> from itertools import toto
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: cannot import name 'toto'

Avec Python 3.7, c’est mieux :

>py -3.7
>>> from itertools import toto
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: cannot import name 'toto' from 'itertools' (unknown location)

PEP 538 : Forçage de la locale C en UTF-8

La PEP 538 propose d’ajuster le comportement de CPython en présence de la locale C vide ou introuvable. Dans ce cas, CPython se comportera comme si l’environnement était le suivant :

    LC_CTYPE=C.UTF-8
    PYTHONIOENCODING=utf-8:surrogateescape

Un très gros travail a été fait dans le cycle de Python 3 pour attacher un encodage précis à toute chaîne de caractères : dans les noms de fichiers, dans les variables d’environnement, etc. Il reste encore quelques cas subtils, et c’est l’objet de cette PEP rédigée par Nick Coghlan.

Dans les cas où la locale C est complètement vide, CPython se considérait en ASCII 7 bits et pouvait générer facilement des erreurs de conversion. En forçant l’UTF-8 dans ces situations, CPython devient à même de mieux gérer le problème et de mieux interagir avec les autres modules qui s’attendent tous à travailler en encodage 8 bits. Lorsque CPython réalise cet ajustement, il force la variable d’environnement LC_TYPE ce qui a pour effet important que toutes les bibliothèques chargées dynamiquement depuis l’interpréteur et tous les processus lancés depuis l’interpréteur héritent de ce changement. Cela permet notamment de configurer correctement GNU readline, dont le comportement dépend de la locale en cours.

À l’intérieur de la PEP, Nick liste des cas concrets où cela peut se présenter aujourd’hui et où cette PEP fait la différence :

  • lors de l’utilisation de technologies de conteneurs : Docker, Kubernetes, OpenShift, mais aussi GNOME Flatpak ou Ubuntu Snappy ;
  • lors d’un ssh vers un serveur, lorsque la locale du PC initiant la connexion est exportée en tant que variable d’environnement mais n’existe pas sur le serveur.

Un pas de plus vers l’universalité d’Unicode !

PEP 540 : Forçage de l’UTF-8 à l’exécution

La PEP 540 adresse un problème similaire à la PEP précédente, mais avec des cas d’usage légèrement différents. Deux changements sont proposés :

  1. permettre le forçage de l’utilisation d’UTF-8 par CPython, en ignorant la locale ; ceci peut être activé soit en passant un paramètre au lancement de l’interpréteur (-X UTF8), soit en fixant la variable d’environnement PYTHONUTF8 à 1 ;
  2. activer ce forçage lorsque la locale est fixée à C (POSIX) ; ceci rejoint le comportement de la PEP précédente.

Les effets du forçage d’UTF-8 :

  • sys.getfilesystemencoding() retourne systématiquement UTF-8, en ignorant la locale ;
  • locale.getpreferredencoding() retourne systématiquement UTF-8, en ignorant la locale et le paramètre do_setlocale ;
  • sys.stdin, sys.stdout et sys.stderr utilisent l’encodage UTF-8 avec la gestion d’erreur surrogateescape pour les entrées et sorties standards, et backslashreplace pour la sortie erreur standard.

Ces deux PEP permettent d’ajuster le comportement de CPython lorsque la locale est configurée différemment pour un système. La différence principale entre les deux approches est que le forçage d’UTF-8 n’affecte que l’interpréteur en cours, il n’est pas hérité par les autres modules dynamiques du même environnement ou par les sous‐processus lancés. Les deux PEP sont donc complémentaires.

PEP 552 : Des .pyc basés sur les hashes

La compilation des modules Python en bytecode est stockée dans un fichier <nom du module>.pyc dans le répertoire __pycache__. En informatique, lorsque l’on parle de cache, il se pose toujours la question délicate de l’invalidation du cache, et Python ne fait pas exception. Python a choisi jusqu’à présent de stocker des horodatages (timestamps) dans le .pyc. Lors de l’exécution, l’interpréteur regarde la date de modification du module Python, la date stockée dans le .pyc et si cette dernière est plus ancienne, il recompile le module. L’approche a plutôt bien marché pour les vingt‐quatre dernières années, mais il se trouve des cas où ce n’est pas approprié :

  1. lorsque l’horodatage des fichiers n’est pas complètement fiable ou précis (salut NFS, salut les partages réseaux Windows !) ;
  2. lorsque l’on veut créer des builds reproductibles, c’est‐à‐dire construire un logiciel de façon à ce que, pour des données d’entrée identiques, les données de sortie soient identiques ; faire dépendre le choix de recompiler un module d’une date de modification d’un fichier altère cette reproductibilité.

La solution proposée et implémentée par la PEP 552 est de stocker un condensat (hash) du fichier source dans le .pyc, en lieu et place de l’horodatage. L’algorithme de hachage choisi est SipHash, parce qu’il est déjà disponible dans l’interpréteur.

L’interpréteur CPython ne générera jamais spontanément de .pyc basé sur les hashes. Pour obtenir de tels .pyc, il faut déclencher soi‐même la compilation avec les modules compileall ou py_compile.

Deux types de .pyc basés sur les hashes ont été définis :

  • les vérifiés, pour lesquels CPython va recalculer le hash du module source et vérifier si un changement a eu lieu ;
  • les non‐vérifiés, pour lesquels CPython se contente de charger le module ; cela correspond à des environnements ou un autre programme est chargé de vérifier et générer les .pyc en fonction de l’évolution des sources.

PEP 562 : Extension de l’accès aux attributs de module

Avec la PEP 562, Python 3.7 permet de définir une fonction __getattr__() pour les modules. Cette fonction sera appelée lorsque l’attribut d’un module n’est pas trouvé, selon le même principe que __getattr__() pour les classes. Il est également possible de définir la fonction __dir__() qui sera appelée lorsque l’utilisateur liste le contenu du module (avec dir()).

Un exemple typique d’utilisation de ces deux fonctionnalités est la possibilité de gérer l’obsolescence de certaines fonctions du module, ou encore de permettre un chargement « à la demande » (lazy loading).

Exemple d’émission d’alerte (warning) pour des fonctions obsolètes :

# monmodule.py
from warnings import warn
deprecated_names = ["old_function", ...]

def _deprecated_old_function(arg, other):
    

def new_function():
    

def __getattr__(name):
    if name in deprecated_names:
        warn(f"{name} is deprecated", DeprecationWarning)
        return globals()[f"_deprecated_{name}"]
    raise AttributeError(f"module {__name__} has no attribute {name}")

def __dir__():
    return ["new_function"]+ deprecated_names

# main.py
from monmodule import old_function  # fonctionne, mais émet un warning
print(dir(monmodule))  # affiche ["new_function", "old_function" ]
# avant python 3.7, cela aurait affiché ["_deprecated_old_function", "new_function", ...]

PEP 565 : Meilleure gestion des alertes d’API obsolètes

Avec la PEP 565, Python 3.7 fait des ajustements à la marge sur la gestion des alertes (warnings) lors de l’usage d’API obsolètes. Une gestion appropriée de ce type d’alerte n’est pas facile à trouver et les développeurs de Python cherchent le bon niveau de compromis.

Python dispose de trois types principaux d’alertes à lever pour les API qui sont sur le chemin de l’obsolescence :

  • PendingDeprecationWarning, utilisé pour les API qui deviendront prochainement obsolètes ;
  • DeprecationWarning, utilisé pour les API qui sont obsolètes et disparaîtront dans le futur ;
  • FutureWarning, utilisé pour les constructions syntaxiques dont la construction va changer dans le futur.

Ces alertes sont levées sous forme de messages sur la sortie d’erreur mais n’affectent pas le programme outre mesure. L’étape après DeprecationWarning est de lever une exception, qui cette fois arrête le programme en cours d’exécution.

Jusqu’à Python 2.6 et 3.1 , le comportement par défaut était de cacher les PendingDeprecationWarning et d’afficher les autres. Un mode développeur pouvait être activé avec -Wdefault, dans lequel Python affichait aussi les PendingDeprecationWarning, ce mode étant notamment activé par le module unittest. L’idée était que les développeurs utilisent le mode -Wdefault, et que les utilisateurs d’un programme ou d’une bibliothèque en Python voient uniquement les FutureWarning et DeprecationWarning.

À l’usage, les développeurs de Python ont constaté qu’un nombre croissant d’utilisateurs de bibliothèques ou programmes écrits en Python étaient confrontés à des alertes DeprecationWarning, pour lesquelles ils ne pouvaient rien : elles étaient levées par des dépendances de leur code, sur lesquelles ils n’avaient pas de contrôle. Cela correspondait en général à une situation où une bibliothèque avait été validée et livrée avec une version de Python plus ancienne, dans laquelle cette alerte n’existait pas encore. La décision a donc été prise pour Python 2.7 et 3.2 d’ignorer les alertes DeprecatonWarning par défaut, tout comme les PendingDeprecationWarning. Il faut activer le mode -Wdefault pour revoir ces deux types d’alertes.

Cependant, ce changement a rendu ces alertes trop discrètes et des développeurs ont été surpris de la disparition de certaines API dans de nouvelles versions de Python, alors que leur disparition avait été préparée. La PEP 565 propose un compromis plus subtil pour gérer la situation : afficher les DeprecationWarning lors de l’exécution du module principal. Le module principal s’entend comme le module __main__ pour un script d’un seul fichier ou le code entré dans l’interpréteur interactif.

D’autres changements plus légers sont inclus dans cette PEP :

  • la signification de FutureWarning est étendue aux alertes destinées aux utilisateurs d’une application, pour les prévenir d’une utilisation en passe de devenir obsolète (on peut imaginer par exemple un réglage particulier d’une application qui n’a plus de sens) ;
  • la documentation sur les alertes devient beaucoup plus détaillée, avec la liste complète des options en ligne de commande pour les contrôler, le détail du filtre par défaut et pas mal d’exemples pour montrer comment tester la génération des alertes ;
  • il y a maintenant une recommandation officielle à destination des développeurs d’outils de tests, d’activer les alertes développeur lors de l’exécution d’une suite de tests ;
  • de même, les développeurs de « ligne de commande interactive » (shell), tel que par exemple IPython, sont priés de configurer les alertes afin d’afficher les DeprecationWarning pour les commandes entrées interactivement.

Le nouveau mode d’exécution « dev »

La ligne de commande a maintenant une option en plus, -X dev qui active le mode développement de CPython (également activable via la variable d’environnement PYTHONDEVMODE). Dans ce mode, CPython réalise des vérifications supplémentaires à l’exécution, qui sont trop consommatrices pour être activées par défaut :

  • affichage de tous les types d’alertes (y compris le DeprecationWarning cité plus haut) ;
  • activation des traceurs mémoire pour les allocations, cf. PyMem_SetupDebugHooks() ;
  • activation du module faulthandler qui permet d’afficher la pile d’appel lors d’un crash ;
  • activation du mode débogage d’asyncio.

Amélioration de l’API C

PEP 539 : Introduction d’une nouvelle API C pour le stockage interne à un thread

La PEP 539 propose une nouvelle API pour le stockage de données privées pour un fil d’exécution (Thread Local Storage ou TLS). Il existait déjà une telle API dans CPython, mais celle‐ci souffrait du problème suivant : la clef utilisée pour chercher des valeurs spécifiques à un fil d’exécution était un int. C’est contraire à la norme POSIX pthreads, qui exige un type opaque pthread_key_t. Cela n’est pas un problème pour les plates‐formes où le type pthread_key_t peut se convertir facilement en int, comme GNU/Linux (où c’est un unsigned int) ou Windows (où c’est un DWORD). Cependant, d’autres plates‐formes existent, qui sont compatibles POSIX, et pour lesquelles l’API existante ne peut pas fonctionner : Cygwin et CloudAPI, par exemple.

L’API actuelle de stockage interne à un fil d’exécution est donc marquée comme étant obsolète, et une nouvelle API compatible POSIX la remplace.

Communauté

PEP 545 : Officialisation des traductions de la documentation

La PEP 545 décrit le processus de traduction de la documentation Python. Trois traductions sont désormais hébergées sur python.org :

La PEP permet de mettre en valeur les efforts fait par différentes communautés pour traduire la documentation, et permettra d’uniformiser le travail fait pour d’autres langues : espagnol, chinois, etc.

On en profite pour faire appel à des contributeurs : la traduction française est aujourd’hui à 29 % et pourrait recevoir plus de contributeurs. Ça se passe sur GitHub.

Liens complémentaires

Toutes les nouvelles PEP introduites :

  • PEP 538 : Force l’utilisation de la locale C historique à une locale UTF-8 ;
  • PEP 539 : Nouvelle API C pour ajouter une mémoire locale de fil d’exécution, ou Thread Local Storage (TLS) ;
  • PEP 540 : Ajout d’un nouveau mode UTF-8 ;
  • PEP 545 : Officialisation du processus de traduction de la documentation ;
  • PEP 552 : Reproductibilité des fichiers .pyc ;
  • PEP 557 : Introduction des dataclasses ;
  • PEP 560 : Gestion dans le cœur de Python du module typing et des types génériques ;
  • PEP 562 : Extension de l’accès aux attributs de module ;
  • PEP 563 : Évaluation différée des annotations ;
  • PEP 564 : Gestion des nanosecondes pour le module time ;
  • PEP 565 : Meilleure gestion des alertes d’API obsolètes ;
  • PEP 567 : Variables contextualisées.

Aller plus loin

  • # Version intégrée à LibreOffice

    Posté par  . Évalué à 3.

    Il y a une version intégrée à LibreOffice mais je ne trouve pas quelle version c'est, quelqu'un le saurait ?
    Je me demande si cette version y sera implémentée, ça augmenterait les possibilités de développement et améliorait les performances.

  • # Python : la stabilité avant tout

    Posté par  (site web personnel) . Évalué à 9.

    J'ai eu beaucoup de plaisir à creuser les sujets techniques et leur histoire pour co-rédiger cette dépêche.

    Ce qui m'a frappé, c'est le soin qui est pris par l'équipe de dev de Python pour garantir au maximum la compatibilité ascendante. Sur les annotations, l'évolution d'API est sommes toutes mineure (récupérer un string à évaluer plutôt qu'un objet) et déjà prise en compte par les plus gros utilisateurs des annotations. Mais il est quand même prévu un cycle de 4 release avant de casser cela, soit environ 6 ans.

    • [^] # Re: Python : la stabilité avant tout

      Posté par  . Évalué à 7.

      c'est le soin qui est pris par l'équipe de dev de Python pour garantir au maximum la compatibilité ascendante.

      Et ils ont rajouté deux mots-clés. Non mais vraiment, j'aime bien python, j'aime bien son développement, mais on ne rajoute pas des mots-clés sur des versions intermédiaires. Ça casse tout.

      • [^] # Re: Python : la stabilité avant tout

        Posté par  (site web personnel) . Évalué à 10.

        L'option choisie par l'équipe est clairement d'introduire les incompatibilités peu à peu, sûrement par peur de recommencer l'épopée Python 3, qui a mis plus de 10 ans à être réellement adopté à cause d'un trop grand nombre d'incompatibilités.

      • [^] # Re: Python : la stabilité avant tout

        Posté par  (site web personnel) . Évalué à 10.

        Et ils ont rajouté deux mots-clés. Non mais vraiment, j'aime bien python, j'aime bien son développement, mais on ne rajoute pas des mots-clés sur des versions intermédiaires. Ça casse tout.

        async/await ont levé un DeprecationWarning depuis Python 3.5, sauf que ce warning était caché par défaut. D'où le travail sur l'affichage de ce warning dans __main__ et ma nouvelle option -X dev pour afficher tous les warnings partout. On ajoute des warnings, après si les devs ignorent les warnings…

        Usez et abusez de PYTHONDEVMODE=1 à partir de Python 3.7 ! Pour les anciennes versions, perso j'utilise l'option -Wd (abbréviation de "-W default" pour afficher DeprecationWarning et ResourceWarning, par exemple).

  • # PEP 553

    Posté par  (site web personnel, Mastodon) . Évalué à 3.

    Dans la liste des PEP introduites, il manque la PEP 553 (fonction breakpoint).

    WeeChat, the extensible chat client

    • [^] # Re: PEP 553

      Posté par  . Évalué à 3.

      On a beau ce relire mille et unes fois, on rate toujours les erreurs que l'on ne voit pas la première fois…

      Voici le lien pour la PEP 553.

      • [^] # Re: PEP 553

        Posté par  (site web personnel) . Évalué à -5. Dernière modification le 12 septembre 2018 à 21:18.

        On a beau ce relire mille et unes fois,

        On a beau se relire mille et une fois…

        Il parait qu'avec la prochaine réforme de l'EN, les élèves qui auront le bac sauront enfin lire et écrire…
        Autrefois, c'était le cas à partir du CM1.
        J'ai la dent dure avec le mammouth, je le sais, les idéologues égalitaristes partisans du nivellement par le bas ont fait de gros dégâts. La méthode globale en a été le point de départ, suivi des mathématiques modernes et de la suppression du redoublement. On est enfin en train de cesser toutes ces lubies dogmatiques de politiciens.

  • # Merci !

    Posté par  . Évalué à 3.

    Merci pour la dépêche. J'utilise encore 3.5 (Debian stable inside) donc j'ai suivi de loin l'arrivée de 3.7 et je n'ai pas participé à la dépêche, mais j'ai pris plaisir à la lire.

    En plus les retours sur les versions antérieures me permettent de découvrir des choses qui me sont accessibles mais que je ne connaissais pas.

    Dans l'exemple des dataclasses, je pense que la valeur de retour indiquée par l'annotation est trompeuse (honte à moi, j'aurai du participer à la rédaction plutôt que le signaler après publication):

            def tax(self, val) -> int:
                self.value -= val
    • [^] # Re: Merci !

      Posté par  (site web personnel) . Évalué à 2.

      Oui, il y a clairement un problème sur cette valeur de retour. None semble plus approprié.

      Sinon, je me suis tellement amusé que je serai prêt à faire celle de Python 3.6 juste pour le plaisir, si ça présentait un intérêt pour qq'un…

      • [^] # Re: Merci !

        Posté par  . Évalué à 4.

        Je me souviens avoir participé à la rédaction de la release de Python 3.6 à l'époque de sa sortie, mais la dépêche a du être abandonné manque de contributions, je suppose…

    • [^] # Re: Merci !

      Posté par  . Évalué à 2.

      Tu veux dire que ça aurait du retourner un None ? Mea culpa… Effectivement, tu as raison. J'ai du faire des modifications sur cette partie, sans me relire entièrement…

      • [^] # Re: Merci !

        Posté par  . Évalué à 3.

        Je ne suis pas familier des annotations, donc je sais pas si la pratique consiste à écrire

            -> None

        ou juste ne rien écrire du tout. Mais en tout cas, ça ne renvoie pas un int.

        C'est un détail, hein…

        Mais ça m'a incité à aller regarder quelle était la syntaxe des annotations, donc c'est bien.

  • # dict et OrderedDict

    Posté par  (site web personnel) . Évalué à 10.

    Je trouve qu'officialiser l'ordre conservé dans les dict est une mauvaise idée, alors qu'on a les OrderedDict pour ça.

    Je sais que c'était à la base un peu par hasard (l'implémentation des OrderedDict s'est trouvée être plus efficace que l'implémentation des dict et donc a été reprise telle quelle pour les dict), mais que se passera-t-il quand on trouvera une implémentation encore plus efficace mais qui ne respecte pas l'ordre ? Fera-t-on des dict, des OrderedDict et des UnorderedDict ?

    Sinon, il y a eu quelques autres petits changements dans l'écosystème : les vieilles versions de pip sont cassées (algos TLS qui ne sont plus acceptés, si je ne me trompe pas), et python setup.py sdist upload va disparaître.

    Du coup, parfois un pip install pip --upgrade ne passe plus alors qu'un python3 -m pip install passe…

    Je trouve franchement dommage d'introduire de nouvelles incompatibilités dans un écosystème déjà compliqué. Il aurait été bien plus utile de dégager setuptools/easy_install qui n'utilise pas la même configuration que pip. Par exemple, quand j'installe A qui dépend de B avec pip install A, B sera probablement installé via setuptools et non pip.
    On ne voit pas la différence, sauf quand on utilise uniquement un miroir privé. Parfois l'installation fonctionnera, parfois non…

    • [^] # Re: dict et OrderedDict

      Posté par  (site web personnel) . Évalué à 7.

      Je sais que c'était à la base un peu par hasard (l'implémentation des OrderedDict s'est trouvée être plus efficace que l'implémentation des dict et donc a été reprise telle quelle pour les dict)

      Non, les deux implémentations n'ont rien à voir. Même s'il s'agit du même auteur, les changements sur le type dict sont à la base (et comme je l'ai décrit dans la dépêche) une optimisation de la représentation mémoire pour gagner en performance et taille des objets. Le respect de l'ordre d'insertion est un effet secondaire.

      Les OrderedDict et dict n'ont pas les mêmes caractéristiques de performances, et ne se comparent pas de la même façon.

      Les développeurs ont laissé reposer ce changement pendant 1 an et demi avant de le déclarer officiel.

      De fait, ils se privent éventuellement d'une optimisation qui pourrait subvenir dans le futur ne respectant pas cette règle. Après, concevoir un langage, c'est faire des choix. Ici, un choix a été fait.

      Perso, ça me va, le non-respect de l'ordre d'insertion était plutôt contre-intuitif et je trouve ça une bonne chose que ça se comporte comme on s'y attend implicitement.

      Pour les autres changements de l'écosystème, je n'était pas au courant et j'ai jamais rencontré les cas que tu décris. Quand je fais du pip, tout passe par pip chez moi. Je dois installer des trucs très récents peut-être, qui du coup ont fait la transition.

      En tout cas, gérer l'hétérogénéité des outils de référence est une tâche très complexe. Si on reste sur le conservatisme que tu recommandes pour Python (mais que tu rejettes à moitié pour les outils d'installation), en ne cassant jamais rien, on serait encore uniquement avec easy_install/setuptools, puisque ce sont les pionniers du packaging sous Python. PIP a au départ foutu la m**** et si aujourd'hui, c'est l'outil de référence, c'est parce que tout le monde a accepté un peu de changement (et qu'il faisait mieux le job). Ou tracer la ligne de ce qui est acceptable ou pas, c'est difficile à dire…

      Je trouve que Python s'en sort pas trop mal compte-tenu de la popularité du langage. Les transition Gtk, Qt ou KDE ou encore Gnome par exemple, se sont beaucoup moins bien passée et ont laissé plein de logiciels devenus de fait inutilisables. Ou encore les changements de versions de la libc, ou les changements de versions de gcc. C'est un peu vieux mais ça a été très loin d'un trucs fluide, les distrib ont du s'arracher pour ne pas péter systématiquement ton système quand tu faisais un upgrade…

      • [^] # Re: dict et OrderedDict

        Posté par  (site web personnel) . Évalué à 4. Dernière modification le 10 septembre 2018 à 18:51.

        Je ne recommandes pas du conservatisme pour Python, je trouve simplement qu'on se retrouve avec deux classes qui font doublon, ou alors j'ai raté quelque chose sur la différence entre dict et OrderedDict.

        Et je ne recommande pas non plus du conservatisme pour les outils de packaging, je pense au contraire qu'il aurait fallu finir la transition (actuellement, on a un mélange de pip et de setuptools largement incompréhensible quand on n'est pas connaisseur des subtilités de Python) pour n'avoir qu'un seul outil de gestion de package.
        Accessoirement, rajouter twine rajoute un nouvel outil à installer et à découvrir. Il aurait peut-être été plus intéressant de l'intégrer dans pip, ou simplement moderniser setuptools.

        Personnellement, j'ai eu des soucis avec pip et des Ubuntu 16.04, avec une méthode super propre qui consiste à faire du curl … | bash (pénible à faire quand tu es dans un réseau sans accès direct à internet et que rajouter un package en dehors des quelques technos de miroir maîtrisées est coûteux).

        • [^] # Re: dict et OrderedDict

          Posté par  (site web personnel) . Évalué à 3.

          À l'instant, sur une Ubuntu 18.04 fraîchement installée, voilà ce que ça donne :

          $ sudo pip3 install XXXXXX --upgrade
          Traceback (most recent call last):
          File "/usr/bin/pip3", line 9, in <module>
          from pip import main
          ImportError: cannot import name 'main'
          $ sudo python3 -m pip install XXXXXX --upgrade
          ......
          Successfully installed XXXXXX-1.1.2

          Honnêtement, je trouve que ce genre de soucis ne donnent pas une bonne image du langage.

          • [^] # Re: dict et OrderedDict

            Posté par  (Mastodon) . Évalué à 3.

            Une mauvaise image de Python ou d'ubuntu ?

            Yth.

            • [^] # Re: dict et OrderedDict

              Posté par  (site web personnel) . Évalué à 5.

              C'est vrai qu'on pourrait se poser la question :D
              Mais pour moi, c'est clairement de Python.
              Quand tu fais découvrir l'écosystème Python à quelqu'un et que tu l'aides à mettre en place son environnement de dév' (notamment sur des systèmes non directement connectés à internet), l'effet n'est vraiment pas terrible quand :
              - pip install va déclencher des setuptools install qui vont foirer
              - pip install ne fonctionne pas parce que problème TLS et il faut faire un curl […] | bash
              - pip install ne fonctionne pas mais python -m pip install fonctionne
              - pip install ne fonctionne pas mais si tu relances juste après à l'identique, ça fonctionne
              - pip install toto --upgrade va mettre à jour toutes les dépendances, même celles que tu ne veux pas
              - …

              alors que théoriquement, tout aurait pu fonctionner avec le premier « pip install » s'il n'y avait eu aucun conflit de version et que pip et setuptools utilisaient la même config de miroir.

              • [^] # Re: dict et OrderedDict

                Posté par  (Mastodon) . Évalué à 4. Dernière modification le 15 septembre 2018 à 14:49.

                J'avoue, j'aime beaucoup le Python, mais je n'utilise jamais pip.
                La plupart des choses dont j'ai besoin sont dispo sous Slackware en SlackBuilds.
                Après, apparemment, la suppression des setuptools va poser des soucis avec ce genre d'installations système, mais la discussion n'a pas encore vraiment démarrée sur SBo.
                Je suppose que ça va venir vite par contre…

                Donc je ne te contredirai pas sur la mauvaise image que donne pip du python…
                J'en garde cependant une encore meilleure image de la Slackware, mais ça n'a rien à voir :p

                Yth.

                • [^] # Re: dict et OrderedDict

                  Posté par  (site web personnel) . Évalué à 7. Dernière modification le 15 septembre 2018 à 19:00.

                  Ah mais j'aime beaucoup Python également !

                  C'est d'ailleurs pour ça que je suis autant agacé par ces soucis. Certes, ils sont mineurs (on les contourne quand même assez facilement), mais je trouve vraiment dommage qu'ils ne soient pas corrigés alors qu'ils peuvent rebuter de nouveaux venus (en pratique, je les rencontre à la mise en place de nouveaux environnements de développement, mais une fois que tout est en place, ça tourne comme une horloge) et que les solutions techniques sont assez simples.
                  Accessoirement, quand tu expliques la mise en place des outils de développement à un débutant Python, tu essaies d'expliquer les grandes lignes… et tu te retrouves à expliquer ces problèmes qui mettent en jeu des points assez obscurs.
                  Un peu comme si tu devais faire des apartés sur les nombres imaginaires dans une explication sur l'addition des nombres entiers : ça casse pas mal la fluidité de l'explication. Oui, ça sent le vécu ^

                  En revanche, je suis bien conscient que les corrections peuvent être lourdes dans la mesure où ça casse la comptabilité (mais bon, le passage à Python3 aurait pu être le prétexte à un nettoyage… peut-être pour Py4 ?)

              • [^] # Re: dict et OrderedDict

                Posté par  (site web personnel) . Évalué à 3. Dernière modification le 17 septembre 2018 à 11:25.

                Waouh… Je suis d'accord avec toi, c'est gravement foireux.

                J'ai jamais rencontré un seul de ces problèmes, pour moi, tout a toujours marché comme décrit dans la documentation donc je vois ça plutôt le côté rose de PIP.

          • [^] # Re: dict et OrderedDict

            Posté par  . Évalué à 4.

            Ou de l'utilisateur.

            on ne doit pas faire "sudo pip3 install". Jamais. Never. On ne touche pas au dépendances pip installé par apt. C'est son pré carré. On ne jardinne pas dans /usr/lib/ avec des lib installé par le systeme, donc pareil par Python.

            On fait "pip3 install --user". Toujours.

            • [^] # Re: dict et OrderedDict

              Posté par  (site web personnel) . Évalué à 4.

              Sauf que justement je veux installer un outil pour plusieurs utilisateurs et qu’il ne touche pas aux dépendances.

              • [^] # Re: dict et OrderedDict

                Posté par  . Évalué à 3. Dernière modification le 20 septembre 2018 à 14:46.

                Un virtualenv ça ne marcherait pas ?

              • [^] # Re: dict et OrderedDict

                Posté par  . Évalué à 2. Dernière modification le 30 septembre 2018 à 22:56.

                Tu as essaye un:

                sudo -s
                pip install --prefix /usr/local PACKAGE

                et modifier les PATH, LD_LIBRARY_PATH et PYTHONPATH comme il faut

                Une suggestion comme une autre.

                • [^] # Re: dict et OrderedDict

                  Posté par  . Évalué à -1.

                  Puisque visiblement quelqu'un a trouve le message au dessus n'ayant soit:

                  1. aucun rapport avec le journal
                  2. et/ou le probleme de la personne.

                  J'aimerai bien avoir:

                  1. En quoi cela ne parle pas de Python ni d'installation utilisant pip
                  2. Si c'est absolument faux en quoi et quel est donc la solution propre?

                  Mais bon je me doute bien que je n'aurai pas de reponse ni a l'un ni a l'autre.

          • [^] # Re: dict et OrderedDict

            Posté par  . Évalué à 2.

            Pour plus d'informations sur ton probleme:

            https://github.com/pypa/pip/issues/5599

      • [^] # Re: dict et OrderedDict

        Posté par  . Évalué à 2.

        Les transition Gtk, Qt ou KDE ou encore Gnome par exemple, se sont beaucoup moins bien passée et ont laissé plein de logiciels devenus de fait inutilisables.

        En quoi la transition Qt s'est mal passée ? Les modifications aux sources sont toujours assez minimes avec Qt, cf un exemple porté de Qt 1.0 à Qt 5. Évidemment ce n'est pas un vrai projet mais tout de même.

        • [^] # Re: dict et OrderedDict

          Posté par  (site web personnel) . Évalué à 7.

          Par exemple, si tu utilisais intensément le QListView en Qt3 (l'affichage en arbre), il n'a pas d'équivalent direct en Qt4/5 suite au passage à l'architecture modèle/vue de Qt. Et l'API modèle/vue bien que assez bien conçue, ne permet pas de contrôler aussi bien le widget d'affichage que ne le permettait QListView.

          Dans ce cas précis, il faudrait carrément refondre l'application pour fonctionner dans le nouveau modèle. De plus, celui-ci est plus complexe à appréhender (je me souviens d'une remarque de David Faure à ce sujet d'ailleurs).

  • # Commentaire supprimé

    Posté par  . Évalué à 10.

    Ce commentaire a été supprimé par l’équipe de modération.

  • # Coquille

    Posté par  . Évalué à 0.

    "qu'elle sera" -> "quels seront"

  • # Dataclasses

    Posté par  (site web personnel) . Évalué à 0.

    Les Dataclasses c'est vraiment la classe :)
    Je suis passé à Python 3.7 pour ça.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.