Cette dépêche est la deuxième d’une série de quatre sur le packaging en Python :
- L’histoire du packaging Python
- Tour de l’écosystème actuel
- Le casse-tête du code compilé
- La structure de la communauté en question
Je vais donc proposer un aperçu plus ou moins complet des différents outils, et de ce qu’ils font ou ne font pas, en essayant de les comparer. Mais je parlerai aussi des fichiers de configuration, des dépôts où les paquets sont publiés, des manières d’installer Python lui-même, et de l’interaction de tout ceci avec les distributions Linux. En revanche, je laisse de côté pour l’instant les paquets écrits en C, C++ ou Rust et la complexité qu’ils apportent.
Sommaire
- Pip
- Les environnements virtuels : venv, virtualenv, pipx
- L’invocation d’un build backend : build
- Le transfert sur PyPI : twine
- La configuration d’un projet : le fichier
pyproject.toml
- Les build backends pour code Python pur
- La gestion des versions de Python : pyenv
- Un outil de test et d’automatisation : tox
- Interlude : vous avez dit lock file?
- Un gestionnaire de lock file : pip-tools
- Les outils « tout-en-un »
- La création d’exécutables indépendants et installeurs : PyInstaller, cx_freeze, briefcase, PyOxidizer (etc.)
- Conda, un univers parallèle
- Petite comparaison des résolveurs de dépendances
- Conclusion et avis personnels
Commençons par un outil dont à peu près tout utilisateur de Python a entendu parler : pip.
Pip
L’installeur de paquets pip est un outil fondamental et omniprésent. Son nom est un acronyme récursif signifiant « Pip Installs Packages ». Il permet d’installer un paquet depuis le PyPI, mais aussi depuis une copie locale du code source, ou encore depuis un dépôt Git. Il dispose, bien sûr, d’un résolveur de dépendances pour trouver des versions à installer qui soient compatibles entre elles. Il possède aussi un certain nombre de fonctionnalités standard pour un gestionnaire de paquets, comme désinstaller un paquet, lister les paquets installés, etc.
S’il y a un outil de packaging quasi universel, c’est bien pip. Par exemple, la page de chaque paquet sur PyPI (exemple) affiche une commande pour l’installer, à savoir pip install <paquet>
. Quand la documentation d’un paquet donne des instructions d’installation, elles utilisent généralement pip.
De plus, la distribution officielle de Python permet de boostraper très simplement pip avec la commande python -m ensurepip
. Cela rend pip très facile à installer, et lui donne un caractère officiel, soutenu par les développeurs de Python, caractère que n’ont pas la plupart des autres outils que je vais mentionner.
Même les autres outils qui installent aussi des paquets depuis le PyPI (comme pipx, Hatch, tox, etc.) le font presque tous en utilisant, en interne, pip (sauf Poetry qui est un peu à part).
Dans l’univers parallèle de Conda et Anaconda, les utilisateurs sont souvent obligés d’utiliser pip dans un environnement Conda parce qu’un paquet donné n’est pas disponible au format Conda (ce qui crée, d’ailleurs, des problèmes de compatibilité, mais c’est un autre sujet).
Les dangers de pip sous Linux
Malheureusement, sous Linux spécifiquement, l’interface en ligne de commande de pip a longtemps été un moyen très facile de se tirer une balle dans le pied. En effet, la commande simple
pip install <paquet>
tentait d’installer le paquet au niveau du système, de manière visible pour tous les utilisateurs de la machine (typiquement dans /usr/lib/pythonX.Y/site-packages/
). Bien sûr, il faut des permissions pour cela. Que fait Monsieur Toutlemonde quand il voit « permission denied error » ? Élémentaire, mon cher Watson :
sudo pip install <paquet>
Or, sous Linux, installer des paquets avec pip au niveau du système, c’est mal. Je répète : c’est MAL. Ou plutôt, c’est valable dans 0,1% des cas et dangereux dans 99,9% des cas.
J’insiste : ne faites JAMAIS sudo pip install
ou sudo pip uninstall
. (Sauf si vous savez parfaitement ce que vous faites et que vous avez scrupuleusement vérifié qu’il n’y a aucun conflit.)
Le souci ? Les distributions Linux contiennent, elles aussi, des paquets écrits en Python, qui sont installés au même endroit que celui dans lequel installe la commande sudo pip install
. Pip peut donc écraser un paquet installé par le système avec une version différente du même paquet, potentiellement incompatible avec le reste, ce qui peut avoir des conséquences catastrophiques. Il suffit de penser que DNF, le gestionnaire de paquets de Fedora, est écrit en Python, pour avoir une idée des dégâts potentiels !
Aujourd’hui, heureusement, la commande pip install <paquet>
(sans sudo
), au lieu d’échouer avec une erreur de permissions, installe par défaut dans un emplacement spécifique à l’utilisateur, typiquement ~/.local/lib/pythonX.Y/site-packages/
(ce qui devait auparavant se faire avec pip install --user <paquet>
, l’option --user
restant disponible si on veut être explicite). De plus, pip émet un avertissement sous Linux lorsqu’exécuté avec les droits root (source). Ainsi, pip install <paquet>
est devenu beaucoup moins dangereux.
Attention, j’ai bien dit moins dangereux… mais dangereux quand même ! Pourquoi, s’il n’efface plus les paquets du système ? Parce que si un paquet est installé à la fois par le système, et par pip au niveau de l’utilisateur, la version de pip va prendre le dessus, car le dossier utilisateur a priorité sur le dossier système. Le résultat est que le conflit, en réalité, persiste : il reste possible de casser un paquet système en installant une version incompatible avec pip au niveau utilisateur. Seulement, c’est beaucoup plus facile à corriger (il suffit d’un rm -rf ~/.local/lib/pythonX.Y/site-packages/*
, alors qu’un conflit dans le dossier système peut être quasi irréparable).
La seule option qui soit sans danger est de ne jamais rien installer en dehors d’un environnement virtuel (voir plus bas pour les instructions).
Pour finir, la PEP 668 a créé un mécanisme pour qu’une distribution Linux puisse marquer les dossiers de paquets Python qu’elle contrôle. Pip refuse (par défaut) de modifier ces dossiers et affiche un avertissement qui mentionne les environnements virtuels. Debian (à partir de Debian Bookworm), Ubuntu (à partir d’Ubuntu Lunar) et d’autres distributions Linux, ont choisi de mettre en place cette protection. Donc, désormais, sudo
ou pas, pip install
en dehors d’un environnement virtuel donne une erreur (on peut forcer l’opération avec l’option --break-system-packages
).
En revanche, Fedora n’a pas implémenté la protection, espérant réussir à créer un dossier pour pip qui soit au niveau système mais séparé du dossier de la distribution Linux, pour que pip install
soit complètement sûr et qu’il n’y ait pas besoin de cette protection. Je recommande la présentation de Miro Hrončok à la conférence PyCon polonaise en janvier 2023, qui explique le casse-tête dans les menus détails. Petite citation en avant-goût : « The fix is quite trivial when you design it, and it only strikes back when you actually try to do it ».
Pip est un outil de bas niveau
Pip a une autre chausse-trappe qui est surprenant quand on est habitué au gestionnaire de paquets d’une distribution Linux. Petite illustration :
$ python -m venv my-venv/ # crée un environnement isolé vide pour la démonstration
$ source my-venv/bin/activate # active l’environnement
$ pip install myst-parser
[...]
Successfully installed MarkupSafe-2.1.3 Pygments-2.16.1 alabaster-0.7.13 [...]
[...]
$ pip install mdformat-deflist
[...]
Installing collected packages: markdown-it-py, mdit-py-plugins, mdformat, mdformat-deflist [...]
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
myst-parser 2.0.0 requires markdown-it-py~=3.0, but you have markdown-it-py 2.2.0 which is incompatible.
myst-parser 2.0.0 requires mdit-py-plugins~=0.4, but you have mdit-py-plugins 0.3.5 which is incompatible.
Successfully installed markdown-it-py-2.2.0 mdformat-0.7.17 mdformat-deflist-0.1.2 mdit-py-plugins-0.3.5 [...]
$ echo $?
0
Comme on peut le voir, la résolution des dépendances par pip ne prend pas en compte les paquets déjà installés dans l’environnement. Autrement dit, pour installer un paquet X, pip va simplement regarder quelles sont les dépendances de X (y compris les dépendances transitives), trouver un ensemble de versions qui soient compatibles entre elles, et les installer. Pip ne vérifie pas que les versions des paquets sont aussi compatibles avec ceux qui sont déjà installés. Ou plutôt, il les vérifie, mais seulement après avoir fait l’installation, à un moment où le mal est déjà fait, et uniquement pour afficher un avertissement. Dans l’exemple ci-dessus, on installe d’abord myst-parser
, dont la dernière version dépend de markdown-it-py
version 3.x, puis on installe mdformat-deflist
, qui dépend de markdown-it-py
version 1.x ou 2.x. En installant mdformat-deflist
, Pip installe aussi, comme dépendance, markdown-it-py
2.x, ce qui casse le myst-parser
installé précédemment.
Ceci n’est naturellement pas du goût de tout le monde (je me rappelle d’ailleurs d’une enquête utilisateur faite par les développeurs de Pip il y a quelques années, où ils posaient la question de savoir ce que Pip devait faire dans cette situation). La morale est que pip est surtout un outil conçu pour créer un environnement virtuel où se trouvent toutes les dépendances dont on a besoin, pas pour s’en servir comme de apt
ou dnf
, en installant et désinstallant manuellement des dépendances. Et surtout, que pip install X; pip install Y
n’est absolument pas équivalent à pip install X Y
, et c’est la seconde forme qui est correcte.
Les environnements virtuels : venv, virtualenv, pipx
Les environnements virtuels permettent de travailler avec des ensembles de paquets différents, installés de façon indépendante entre eux. L’outil d’origine pour les créer est virtualenv. Néanmoins, le plus utilisé aujourd’hui est venv, qui est une version réduite de virtualenv intégrée à la bibliothèque standard. Malheureusement, venv est plus lent et n’a pas toutes les fonctionnalités de virtualenv, qui reste donc utilisé également…
Pour créer un environnement virtuel (avec venv), on exécute :
python -m venv nom-de-l-environnement
Cela crée un dossier nom-de-l-environnement/
. Chaque environnement est donc stocké dans un dossier. À l’intérieur de ce dossier se trouve notamment un sous-dossier bin/
avec des exécutables :
un exécutable
python
, qui ouvre un interpréteur Python ayant accès aux paquets de l’environnement virtuel (et, par défaut, seulement eux),un exécutable
pip
, qui installe les paquets à l’intérieur de l’environnement.
De plus, pour simplifier l’utilisation dans un shell, on peut « activer » l’environnement, avec une commande qui dépend du shell. Par exemple, sous les shells UNIX classiques (bash
, zsh
), on exécute
source nom-de-l-environnement/bin/activate
Cette commande modifie la variable PATH
pour y ajouter nom-de-l-environnement/bin/
afin que (par exemple) la commande python
invoque nom-de-l-environnement/bin/python
.
Malgré cela, les environnements virtuels restent un niveau de confort en dessous du Python du système, puisqu’il faut activer un environnement avant de s’en servir, ou écrire à chaque fois le chemin dossier-environnement/bin/
. Bien sûr, il faut aussi mémoriser les commandes, et puis c’est si facile de faire pip install
dans l’environnement global (non virtuel). Donc, beaucoup n’y prêtent malheureusement pas attention et installent au niveau global, ce qui cause des conflits de dépendances (c’est maintenant refusé par défaut sous Debian et dérivés, comme je l’expliquais dans la section précédente, mais c’est toujours majoritaire sous macOS et Windows).
C’est aussi pour rendre plus pratiques les environnements virtuels qu’existent pléthore d’outils qui les créent et/ou activent pour vous. Je termine avec l’un de ces outils, lié à la fois à pip et aux environnements virtuels, j’ai nommé pipx. À première vue, pipx a une interface qui ressemble à celle de pip, avec par exemple des sous-commandes pipx install
, pipx uninstall
et pipx list
. Mais, à la différence de pip, qui installe un paquet dans un environnement déjà créé, pipx va, pour chaque paquet installé, créer un nouvel environnement virtuel dédié. Pipx est principalement destiné à installer des outils dont l’interface est en ligne de commande, pas sous forme d’un module importable en Python. Pipx utilise pip, pour ne pas trop réinventer la roue quand même. Au final,
$ pipx install pycowsay
revient à quelque chose comme
$ python -m venv ~/.local/pipx/pycowsay/
$ ~/.local/pipx/pycowsay/bin/pip install pycowsay
$ ln -s ~/.local/pipx/pycowsay/bin/pycowsay ~/.local/bin/pycowsay
Pour résumer, pipx permet d’installer des outils en ligne de commande, de manière isolée, qui n’interfèrent pas avec le système ou entre eux, sans avoir à gérer les environnements virtuels soi-même.
L’invocation d’un build backend : build
Pour déposer son projet sur PyPI, il faut d’abord obtenir deux fichiers : une sdist (source distribution), qui est essentiellement une archive .tar.gz
du code avec des métadonnées ajoutées, et un paquet installable au format wheel, d’extension .whl
. L’outil build sert à générer ces deux fichiers. Il s’invoque comme ceci, dans le dossier du code source :
python -m build
Petit exemple dans le dépôt de Sphinx (l’outil de documentation le plus répandu dans le monde Python) :
$ python -m build
* Creating venv isolated environment...
* Installing packages in isolated environment... (flit_core>=3.7)
* Getting build dependencies for sdist...
* Building sdist...
* Building wheel from sdist
* Creating venv isolated environment...
* Installing packages in isolated environment... (flit_core>=3.7)
* Getting build dependencies for wheel...
* Building wheel...
Successfully built sphinx-7.3.0.tar.gz and sphinx-7.3.0-py3-none-any.whl
$ ls dist/
sphinx-7.3.0-py3-none-any.whl sphinx-7.3.0.tar.gz
Comme on peut le comprendre, build est un outil très simple. L’essentiel de sa documentation tient en une courte page. Il crée un environnement virtuel pour installer le build backend, en l’occurrence Flit, puis se contente d’invoquer celui-ci.
Le transfert sur PyPI : twine
À l’image de build, twine est un outil fort simple qui remplit une seule fonction et la remplit bien : déposer la sdist et le wheel sur PyPI (ou un autre dépôt de paquets). En continuant l’exemple précédent, on écrirait :
twine upload dist/*
Après avoir fourni un login et mot de passe, le projet est publié, il peut être installé avec pip, et possède sa page https://pypi.org/project/nom-du-projet
.
La configuration d’un projet : le fichier pyproject.toml
pyproject.toml
est le fichier de configuration adopté par à peu près tous les outils de packaging, ainsi que de nombreux outils qui ne sont pas liés au packaging (par exemple les linters comme Ruff, les auto-formateurs comme Black ou le même Ruff, etc.). Il est écrit dans le langage de configuration TOML. On a besoin d’un pyproject.toml
pour n’importe quel projet publié sur PyPI, et même, souvent, pour les projets qui ne sont pas distribués sur PyPI (comme pour configurer Ruff).
Dans ce fichier se trouvent trois sections possibles — des « tables », en jargon TOML. La table [build-system]
détermine le build backend du projet (je reviens plus bas sur le choix du build backend). La table [project]
contient les informations de base, comme le nom du projet, la version, les dépendances, etc. Quant à la table [tool]
, elle est utilisée via des sous-tables [tool.<nom de l'outil>]
: tout outil peut lire de la configuration dans sa sous-table dédiée. Rien n’est standardisé par des spécifications PyPA dans la table [tool]
, chaque outil y fait ce qu’il veut.
Avant qu’on me dise que pyproject.toml
est mal documenté, ce qui a pu être vrai, je précise que des efforts ont été faits à ce niveau dans les dernières semaines, par moi et d’autres, ce qui donne un guide du pyproject.toml normalement complet et compréhensible, ainsi qu’une explication sur ce qui est déprécié ou non concernant setup.py
et un guide sur la migration de setup.py
vers pyproject.toml
. Tout ceci réside sur packaging.python.org, qui est un site officiel de la PyPA rassemblant des tutoriels, guides et spécifications techniques.
Les build backends pour code Python pur
Le build backend est chargé de générer les sdists et les wheels que l’on peut ensuite mettre sur PyPI avec twine ou autre. Il est spécifié dans le fichier pyproject.toml
. Par exemple, pour utiliser Flit, la configuration est :
[build-system]
requires = ["flit_core>=3.7"]
build-backend = "flit_core.buildapi"
requires
est la liste des dépendances (des paquets sur PyPI), et build-backend
est le nom d’un module (qui doit suivre une interface standardisée).
Il peut sembler étrange qu’il faille, même pour un projet simple, choisir son build backend. Passons donc en revue les critères de choix : de quoi est responsable le build backend ?
D’abord, il doit traduire les métadonnées du projet. En effet, dans les sdists et wheels, les métadonnées sont encodées dans un format un peu étrange, à base de MIME, qui est conservé au lieu d’un format plus moderne comme TOML ou JSON, pour des raisons de compatibilité. La plupart des build backends se contentent de prendre les valeurs dans la table [project]
du pyproject.toml
et de les copier directement sous la bonne forme, mais setuptools permet aussi de configurer les métadonnées via setup.py
ou setup.cfg
, également pour préserver la compatibilité, et il y a aussi des build backends comme Poetry qui n’ont pas adopté la table [project]
(j’y reviens dans la section sur Poetry).
De plus, les build backends ont souvent des façons de calculer dynamiquement certaines métadonnées, typiquement la version, qui peut être lue depuis un attribut __version__
, ou déterminée à partir du dernier tag Git.
C’est aussi le build backend qui décide des fichiers du projet à inclure ou exclure dans la sdist et le wheel. En particulier, on trouve généralement des options qui permettent d’inclure des fichiers autres que .py
dans le wheel (c’est le wheel qui détermine ce qui est installé au final, alors que la sdist peut aussi contenir les tests etc.). Cela peut servir, par exemple, aux paquets qui doivent être distribués avec des icônes, des données en JSON, des templates Django…
Enfin, s’il y a des extensions en C, C++, Rust ou autre, le build backend est chargé de les compiler.
Il existe aujourd’hui de nombreux build backends. Beaucoup sont spécifiques à un type d’extensions compilées, ils sont présentés dans la troisième dépêche. Voici les build backends principaux pour du code Python pur.
setuptools
C’est le build backend historique. Il reste très largement utilisé.
Avant qu’arrive pyproject.toml
, il n’y avait qu’un build backend, setuptools
, et il était configuré soit par le setup.py
, soit par un fichier en syntaxe INI, nommé setup.cfg
(qui est l’ancêtre de pyproject.toml
). Ainsi, il existe aujourd’hui trois manières différentes de configurer setuptools
, à savoir setup.py
, setup.cfg
et pyproject.toml
. On rencontre les trois dans les projets existants. La façon recommandée aujourd’hui est pyproject.toml
pour tout ce qui est statique, sachant que setup.py
, qui est écrit en Python, peut toujours servir s’il y a besoin de configuration programmable.
Aujourd’hui, setuptools ne se veut plus qu’un build backend, mais historiquement, en tant que descendant de distutils, il a beaucoup de fonctionnalités, désormais dépréciées, pour installer des paquets ou autres. On peut se faire une idée de l’ampleur des évolutions qui ont secoué le packaging au fil des années en parcourant l’abondante documentation des fonctionnalités obsolètes, notamment cette page, celle-ci ou encore celle-là.
Flit
Flit est l’exact opposé de setuptools. C’est le build backend qui vise à être le plus simple et minimal possible. Il n’y a pratiquement pas de configuration autre que la configuration standardisée des métadonnées dans la table [project]
du pyproject.toml
.
Flit se veut volontairement inflexible (« opinionated »), pour qu’il n’y ait pas de choix à faire. Avec Flit, un projet appelé nom-projet
doit obligatoirement fournir un module et un seul, soit nom_projet.py
, soit nom_project/
. De même, il est possible d’inclure des fichiers autres que .py
, mais ils doivent obligatoirement se trouver tous dans un dossier dédié au même niveau que le pyproject.toml
.
Flit dispose aussi d’une interface en ligne de commande minimale, avec des commandes flit build
(équivalent de python -m build
), flit publish
(équivalent de twine upload
), flit install
(équivalent de pip install .
), et flit init
(qui initialise un projet).
Hatchling
Hatchling est le build backend associé à Hatch, un outil tout-en-un dont il sera question plus loin.
Contrairement à setuptools, il est plutôt facile d’utilisation, et il fait plus souvent ce qu’on veut par défaut. Contrairement à Flit, il offre aussi des options de configuration plus avancées (comme pour inclure plusieurs modules dans un paquet), ainsi que la possibilité d’écrire des plugins.
PDM-Backend
De même que hatchling est associé à Hatch, PDM-Backend est associé à PDM. Je n'en ai pas d'expérience, mais à lire sa documentation, il me semble plus ou moins équivalent en fonctionnalités à hatchling, avec des options un peu moins fines.
Poetry-core
Comme les deux précédents, Poetry-core est associé à un outil plus vaste, à savoir Poetry.
Par rapport à hatchling et PDM-backend, il est moins sophistiqué (il ne permet pas de lire la version depuis un attribut dans le code ou depuis un tag Git).
La gestion des versions de Python : pyenv
L’une des difficultés du packaging Python est que l’interpréteur Python lui-même n’est généralement pas compilé upstream et téléchargé depuis le site officiel, du moins pas sous Linux (c’est davantage le cas sous Windows, et plus ou moins le cas sous macOS). L’interpréteur est plutôt fourni de l’extérieur, à savoir, sous Linux, par le gestionnaire de paquets de la distribution, ou bien, sous macOS, par XCode, Homebrew ou MacPorts. Cela peut aussi être un Python compilé à partir du code source sur la machine de l’utilisateur.
Ce modèle est différent d’autres langages comme Rust, par exemple. Pour installer Rust, la plupart des gens utilisent Rustup, un script qui télécharge des exécutables statiques compilés upstream (le fameux curl | bash
tant décrié…).
Le but de pyenv
est de simplifier la gestion des versions de Python. On exécute, par exemple, pyenv install 3.10.2
pour installer Python 3.10.2. Comme pyenv
va compiler le code source, il faut quand même installer soi-même les dépendances (avec leurs en-têtes C).
Un outil de test et d’automatisation : tox
À partir du moment où un projet grossit, il devient souvent utile d’avoir de petits scripts qui automatisent des tâches courantes, comme exécuter les tests, mettre à jour tel fichier à partir de tel autre, ou encore compiler des catalogues de traduction en format MO à partir des fichiers PO. Il devient également nécessaire de tester le projet sur différentes versions de Python, ou encore avec différentes versions des dépendances.
Tout cela est le rôle de tox
. Il se configure avec un fichier tox.ini
. Voici un exemple tiré de Pygments:
[tox]
envlist = py
[testenv]
description =
run tests with pytest (you can pass extra arguments for pytest,
e.g., "tox -- --update-goldens")
deps =
pytest >= 7.0
pytest-cov
pytest-randomly
wcag-contrast-ratio
commands =
pytest {posargs}
use_develop = True
On peut avoir plusieurs sections [testenv:xxx]
qui définissent des environnements virtuels. Chaque environnement est créé avec une version de Python ainsi qu’une certaine liste de dépendances, et peut déclarer des commandes à exécuter. Ces commandes ne passent pas par un shell, ce qui garantit que le tox.ini
reste portable.
Interlude : vous avez dit lock file?
Pour faire simple, un lock file est un fichier qui décrit de manière exacte un environnement de sorte qu’il puisse être reproduit. Prenons un exemple. Imaginons une application Web déployée sur plusieurs serveurs, qui a besoin de la bibliothèque requests
. Elle va déclarer cette dépendance dans sa configuration. Éventuellement, elle fixera une borne sur la version (par exemple requests>=2.31
), pour être sûre d’avoir une version compatible. Mais le paquet requests
a lui-même des dépendances. On souhaiterait que l’environnement soit vraiment reproductible — que des serveurs différents n’aient pas des versions différentes des dépendances, même si les environnements sont installés à des moments différents, entre lesquels des dépendances publient des nouvelles versions. Sinon, on risque des bugs difficiles à comprendre qui ne se manifestent que sur l’un des serveurs.
La même problématique se pose pour développer une application à plusieurs. Sauf si l’application doit être distribuée dans des environnements variés (par exemple empaquetée par des distributions Linux), il ne vaut pas la peine de s’embarrasser de versions différentes des dépendances. Il est plus simple de fixer toutes les versions pour tout le monde.
Dans la vraie vie, une application peut avoir des centaines de dépendances, dont quelques-unes directes et les autres indirectes. Il devient très fastidieux de maintenir à la main une liste des versions exactes de chaque dépendance.
Avec un lock file, on s’assure de geler un ensemble de versions de tous les paquets qui sera le même pour tous les contributeurs, et pour tous les déploiements d’une application. On sépare, d’un côté, la déclaration des dépendances directes minimales supposées compatibles avec l’application, écrite à la main, et de l’autre côté, la déclaration des versions exactes de toutes les dépendances, générée automatiquement. Concrètement, à partir de la contrainte requests>=2.31
, un générateur de lock file pourrait écrire un lock file qui fixe les versions certifi==2023.11.17
, charset-normalizer==3.3.2
, idna==3.4
, requests==2.31.0
, urllib3==2.1.0
. À la prochaine mise à jour du lock file, certaines de ces versions pourraient passer à des versions plus récentes publiées entre-temps.
Le concept de lock file est en revanche beaucoup plus discutable pour une bibliothèque (par opposition à une application), étant donné qu’une bibliothèque est faite pour être utilisée dans d’autres projets, et que si un projet a besoin des bibliothèques A et B, où A demande requests==2.31.0
alors que B demande requests==2.30.0
, il n’y a aucun moyen de satisfaire les dépendances. Pour cette raison, une bibliothèque doit essayer de minimiser les contraintes sur ses dépendances, ce qui est fondamentalement opposé à l’idée d’un lock file.
Il existe plusieurs outils qui permettent de générer et d’utiliser un lock file. Malheureusement, l’un des plus gros problèmes actuels du packaging Python est le manque criant, sinon d’un outil, d’un format de lock file standardisé. Il y a eu une tentative avec la PEP 665, rejetée par manque de consensus (mais avant qu’on soupire qu’il suffisait de se mettre d’accord : il y a de vraies questions techniques qui se posent, notamment sur l’adoption d’un standard qui ne permet pas de faire tout ce que font certains outils existants, qui risquerait de fragmenter encore plus au lieu d’aider).
Un gestionnaire de lock file : pip-tools
pip-tools est un outil assez simple pour générer et utiliser un lock file. Il se compose de deux parties, pip-compile
et pip-sync
.
La commande pip-compile
, prend un ensemble de déclarations de dépendances, soit dans un pyproject.toml
, soit dans un fichier spécial requirements.in
. Elle génère un fichier requirements.txt
qui peut être installé par pip
.
Quant à la commande pip-sync
, c’est simplement un raccourci pour installer les dépendances du requirements.txt
.
Les locks files sont donc des fichiers requirements.txt
, un format pas si bien défini puisqu’un requirements.txt
est en fait essentiellement une série d’arguments et d’options en ligne de commande à passer à pip.
Les outils « tout-en-un »
Face à la prolifération d’outils à installer et mémoriser, certains ont essayé de créer une expérience plus cohérente avec des outils unifiés. Malheureusement, ce serait trop simple s’ils s’étaient accordés sur un projet commun…
Poetry
Poetry est un outil un peu à part qui fait à peu près tout par lui-même.
Poetry se destine aux développeurs de bibliothèques et d’applications. Toutefois, en pratique, il est plutôt orienté vers les applications.
La configuration se fait entièrement dans le fichier pyproject.toml
. Poetry s’utilise toujours avec son build backend Poetry-core, donc la partie [build-system]
du pyproject.toml
est configurée comme ceci :
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
En revanche, contrairement à la plupart des autres build backends, Poetry n’accepte pas la configuration dans la table [project]
. À la place, il faut écrire les métadonnées sur le projet (nom, version, mainteneurs, etc.) dans la table [tool.poetry]
, dans un format différent du format standard.
La particularité de Poetry qui le rend à part est d’être centré profondément sur le concept de lock file, d’insister fortement sur le Semantic Versioning, et d’avoir son propre résolveur de dépendances. Poetry n’installe jamais rien dans l’environnement virtuel du projet avant d’avoir généré un lock file qui trace les versions installées. Sa configuration a aussi des raccourcis typiques du semantic versioning, comme la syntaxe nom_du_paquet = "^3.4"
, qui dans la table [project]
s’écrirait plutôt "nom_du_paquet >= 3.4, < 4
.
(Ironiquement, les versions de Poetry lui-même ne suivent pas le Semantic Versioning.)
Je ne vais pas présenter les commandes de Poetry une par une car cette dépêche est déjà bien trop longue. Je renvoie à la documentation. Disons simplement qu’un projet géré avec Poetry se passe de pip, de build, de twine, de venv et de pip-tools.
PDM
Je l’avoue, je connais mal PDM. D’après ce que j’en ai compris, il est assez semblable à Poetry dans son interface, mais suit tout de même plus les standards, en mettant sa configuration dans la table [project]
, et en utilisant le résolveur de dépendances de pip. (Je parlerai tout de même, dans la quatrième dépêche, de la motivation à l’origine du développement de PDM, qui cache toute une histoire. Pour ceux qui ont compris, oui, c’est bien résumé par un nombre qui est multiple de 97.)
Hatch
Hatch est plus récent que Poetry et PDM. (Il n’est pas encore au niveau de Poetry en termes de popularité, mais loin devant PDM.) Il est encore en développement rapide.
Comparé à Poetry et PDM, il ne gère pas, pour l’instant, les lock files. (Ici aussi, il y a une histoire intéressante : l’auteur a d’abord voulu attendre que les discussions de standardisation aboutissent à un format commun, mais vu l’absence de progrès, il a fait savoir récemment qu’il allait implémenter un format non standardisé, comme Poetry et PDM le font déjà.)
En contrepartie, Hatch gère aussi les versions de Python. Il est capable d’installer ou de désinstaller une version très simplement, sachant que, contrairement à pyenv, il ne compile pas sur la machine de l’utilisateur mais télécharge des versions précompilées (beaucoup plus rapide, et aucune dépendance à installer soi-même). Il a aussi une commande fmt
pour reformater le projet (plutôt que de définir soi-même un environnement pour cela dans Poetry ou PDM), et il est prévu qu’il gagne bientôt des commandes comme hatch test
et hatch doc
également.
De plus, dans Poetry, lorsque l’on déclare, par exemple, un environnement pour compiler la documentation, avec une dépendance sur sphinx >= 7
, cette dépendance est résolue en même temps que les dépendances principales du projet. Donc, si votre générateur de documentation demande une certaine version, mettons, de Jinja2 (ou n’importe quel autre paquet), vous êtes forcé d’utiliser la même version pour votre propre projet, même si l’environnement pour exécuter votre projet n’a rien à voir avec l’environnement pour générer sa documentation. C’est la même chose avec PDM. Je trouve cette limitation assez frustrante, et Hatch n’a pas ce défaut.
La création d’exécutables indépendants et installeurs : PyInstaller, cx_freeze, briefcase, PyOxidizer (etc.)
Distribuer son projet sur PyPI est bien beau, mais pour installer un paquet du PyPI, il faut d’abord avoir Python et savoir se servir d’un terminal pour lancer pip. Quid de la distribution d’une application graphique à des gens qui n’ont aucune connaissance technique ?
Pour cela, il existe une pléthore d’outils qui créent des installeurs, contenant à la fois Python, une application, ses dépendances, une icône d’application, et en bref, tout ce qu’il faut pour satisfaire les utilisateurs qui n’y comprennent rien et réclament juste une « application normale » avec un « installeur normal », un appli-setup.exe
ou Appli.dmg
. Les plus connus sont PyInstaller et py2exe. Plus récemment est aussi apparu briefcase.
Il y a aussi d’autres outils qui ne vont pas jusqu’à créer un installeur graphique, mais se contentent d’un exécutable qui peut être lancé en ligne de commande. Ce sont notamment cx_freeze et PyOxidizer, mais il y en a bien d’autres.
Malheureusement, toute cette classe d’usages est l’un des gros points faibles de l’écosystème actuel. PyInstaller, par exemple, est fondé sur des principes assez douteux qui datent d’une époque où le packaging était beaucoup moins évolué qu’aujourd’hui (voir notamment ce commentaire). Pour faire simple, PyInstaller détecte les import
dans le code pour trouver les fichiers à inclure dans l’application, au lieu d’inclure toutes les dépendances déclarées par les mainteneurs. Il semble que briefcase soit meilleur de ce point de vue.
De manière plus générale, embarquer un interpréteur Python est techniquement compliqué, notamment à cause de l’interaction avec des bibliothèques système (comme OpenSSL), et chacun de ces projets résout ces difficultés d’une manière différente qui a ses propres limitations.
Conda, un univers parallèle
Comme expliqué dans la première dépêche sur l’historique du packaging, Conda est un outil entièrement indépendant de tout le reste. Il ne peut pas installer de paquets du PyPI, son format de paquet est différent, ses environnements virtuels sont différents. Il est développé par une entreprise, Anaconda Inc (mais publié sous une licence libre). Et surtout, bien que chacun puisse publier des paquets sur anaconda.org, il reste principalement utilisé à travers des dépôts de paquets comprenant plusieurs milliers de paquets, qui sont gérés non pas par les auteurs du code concerné, mais par des mainteneurs propres au dépôt de paquets, à la manière d’une distribution Linux, ou de Homebrew et MacPorts sous macOS. En pratique, les deux dépôts principaux sont Anaconda, qui est maintenu par Anaconda Inc, et conda-forge, maintenu par une communauté de volontaires.
Quelques outils gravitent autour de Conda (mais beaucoup moins que les outils compatibles PyPI, car Conda est plus unifié). Je pense notamment à Condax, qui est à Conda ce que pipx est à pip. Il y a aussi conda-lock pour les lock files.
Grâce à son modèle, Conda permet une distribution très fiable des extensions C et C++, ce qui constitue son atout principal. Un inconvénient majeur est le manque de compatibilité avec PyPI, qui reste la source unique pour la plupart des paquets, Conda n’ayant que les plus populaires.
Petite comparaison des résolveurs de dépendances
Les résolveurs de dépendances sont des composants invisibles, mais absolument cruciaux des systèmes de packaging. Un résolveur de dépendances prend un ensemble de paquets, et de contraintes sur ces paquets, de la forme « l’utilisateur demande le paquet A version X.Y au minimum et version Z.T au maximum », ou « le paquet A version X.Y dépend du paquet B version Z.T au minimum et U.V au maximum ». Il est chargé de déterminer un ensemble de versions compatibles, si possible récentes.
Cela paraît simple, et pourtant, le problème de la résolution de dépendances est NP-complet (c’est facile à démontrer), ce qui signifie que, sauf à prouver fausse l'hypothèse du temps exponentiel (et si vous le faites, vous deviendrez célèbre et couronné de gloire et du prix Turing), il n’existe pas d’algorithme pour le résoudre qui ait une complexité meilleure qu’exponentielle. Les algorithmes utilisés en pratique se fondent soit sur des heuristiques, soit sur une traduction en problème SAT et appel d’un SAT-solveur. Le bon résolveur est celui qui réussira à résoudre efficacement les cas rencontrés en pratique. Pour revenir à Python, il y a aujourd’hui trois résolveurs de dépendances principaux pour les paquets Python.
Le premier est celui de pip, qui est implémenté dans resolvelib. Il utilise des heuristiques relativement simples. Historiquement, il s’est construit sur une contrainte forte : jusqu’à récemment (PEP 658), il n’y avait aucun moyen sur PyPI de télécharger seulement les métadonnées d’un paquet sans télécharger le paquet entier. Donc, il n’était pas possible d’obtenir tout le graphe de dépendances entier avant de commencer la résolution, car cela aurait nécessité de télécharger le code entier de toutes les versions de chaque dépendance. Or, il n’y a aucun solveur SAT existant (à ma connaissance) qui permette de modifier incrémentalement le problème. Par conséquent, pip était de toute façon forcé d’adopter une stratégie ad-hoc. La contrainte a été levée, mais l’algorithme est resté.
Le deuxième résolveur est celui de Conda. (En fait, le résolveur est en train de changer, mais l’ancien et le nouveau sont similaires sur le principe.) Contrairement à pip, Conda télécharge à l’avance un fichier qui donne les dépendances de chaque version de chaque paquet, ce qui lui permet de traduire le problème de résolution entier en problème SAT et d’appliquer un solveur SAT.
Enfin, le troisième résolveur fait partie de Poetry. Si j’ai bien compris ceci, il utilise l’algorithme PubGrub, qui ne traduit pas le problème en SAT, mais le résout plutôt avec une méthode inspirée de certains solveurs SAT.
En pratique, dans mon expérience, le solveur de pip se montre rapide la plupart du temps (sauf dans les cas vraiment complexes avec beaucoup de dépendances et de contraintes).
Toujours dans mon expérience, la résolution de dépendances dans Conda est en revanche franchement lente. À sa décharge, je soupçonne que le résolveur lui-même n’est pas spécialement lent (voire, plus rapide que celui de pip ? je ne sais pas), mais comme Conda a pour principe de ne prendre quasiment rien dans le système, en ayant des paquets comme wget
, freetype
, libxcb
, pcre2
, etc. etc. etc., certains paquets ont un nombre absolument effrayant de dépendances. Par exemple, il y a quelque temps, j’ai eu à demander à conda-lock
un environnement satisfaisant les contraintes suivantes :
- pyqt=5.15.9
- sip=6.7.11
- pyqt-builder=1.15.2
- cmake=3.26.4
- openjpeg=2.5.0
- jpeg=9e
- compilers=1.6.0
- boost-cpp
- setuptools=68.0.0
- wheel
Sur mon ordinateur, il faut environ 7 minutes pour que Conda calcule l’environnement résultant — j’insiste sur le fait que rien n’est installé, ce temps est passé uniquement dans le résolveur de dépendances. Le lock file créé contient environ 250 dépendances (!).
À titre illustratif : « Conda has gotten better by taking more shortcuts and guessing things (I haven't had a 25+ hour solve in a while) » — Henry Schreiner
Quant au résolveur de Poetry, même si je n’ai jamais utilisé sérieusement Poetry, je crois savoir que sa lenteur est l’une des objections les plus fréquentes à cet outil. Voir par exemple ce bug avec 335 👍. (Je trouve aussi révélateur que sur les premiers résultats renvoyés par une recherche Google de « poetry dependency resolver », une moitié environ se plaigne de la lenteur du résolveur.)
D’un autre côté, le solveur de Poetry n’est appelé que lorsque le lock file est mis à jour, donc beaucoup moins souvent que celui de pip ou même Conda. Il y a un vrai compromis à faire : le résolveur de Poetry se veut plus précis (il est censé trouver plus souvent une solution avec des versions récentes), mais en contrepartie, la mise à jour du lock file peut prendre, apparemment, une dizaine de minutes dans certains cas.
Conclusion et avis personnels
Je termine en donnant mes choix très personnels et partiellement subjectifs, avec lesquels tout le monde ne sera pas forcément d’accord.
D’abord, il faut une manière d’installer des outils en ligne de commande distribués sous forme de paquets Python. Il est sage de donner à chacun son environnement virtuel pour éviter que leurs dépendances n’entrent en conflit, ce qui peut arriver très vite. Pour cela, on a essentiellement le choix entre pipx, ou créer à la main un environnement virtuel à chaque fois. Sans hésiter, je choisis pipx.
(Il y a un problème de boostrap parce que pipx est lui-même un outil du même genre. La solution consiste à l’installer avec un paquet système, que la plupart des distributions fournissent, normalement sous le nom python3-pipx
.)
Ensuite, pour travailler sur un projet, on a le choix entre utiliser build et twine à la main pour générer la sdist et le wheel et les distribuer sur PyPI, ou bien utiliser un outil plus unifié, soit Flit, soit Poetry, soit PDM, soit Hatch. Dans le premier cas, on peut utiliser n’importe quel build backend, dans le deuxième, on est potentiellement restreint au build backend associé à l’outil unifié (c’est le cas avec Flit et Poetry, mais pas avec PDM, et plus avec Hatch depuis très récemment).
Parlons d’abord du build backend. À vrai dire, lorsque les builds backends ont été introduits (par la PEP 517, voir dépêche précédente), la motivation était largement de permettre l’émergence d’alternatives à setuptools au vu du triste état de setuptools. L’objectif est atteint, puisqu’il y a désormais des alternatives mille fois meilleures. L’ennui, c’est qu’il y a aussi un peu trop de choix. Donc, comparons.
D’abord, il y a setuptools. Sa configuration est franchement compliquée, par exemple je ne comprends pas précisément les douze mille options de configuration qui contrôlent les modules qui sont inclus dans les wheels. De plus, setuptools est vraiment excessivement verbeux. Dans le dépôt de Pygments, un module dont je suis mainteneur, python -m build | wc -l
comptait 4190 lignes de log avec setuptools, à comparer à 10 lignes depuis que nous sommes passés à hatchling. Mais surtout, le problème avec setuptools, c’est qu’il y a mille et une fonctionnalités mutantes dont on ne sait pas très bien si elles sont obsolètes, et la documentation est, pour moi, tout simplement incompréhensible. Entendons-nous bien : j’ai beaucoup de respect pour les gens qui maintiennent setuptools, c’est absolument essentiel vu le nombre de paquets qui utilisent setuptools parce que c’était historiquement le seul outil, mais aujourd’hui, peu contestent que setuptools est moins bon qu’à peu près n’importe quel build backend dans la concurrence, et on ne peut pas le simplifier, justement à cause de toute la compatibilité à garder.
Alors… Flit ? Pour les débutants, ce n’est pas mal. Mais la force de Flit, son inflexibilité, est aussi son défaut. Exemple ici et là où, suite à un commentaire de Ploum sur la dépêche précédente, je l’ai aidé à résoudre son problème de packaging, dont la racine était que Flit ne permet tout simplement pas d’avoir plusieurs modules Python dans un même paquet.
Alors… Poetry-core ? PDM-backend ? Hatchling ? Les différences sont moins marquées, donc parlons un peu des outils unifiés qui leur sont associés.
D’abord, que penser de Poetry ? Je précise que ne l’ai jamais utilisé moi-même sur un vrai projet. À ce que j’en ai entendu, la plupart de ceux qui l’utilisent l’apprécient et le trouvent plutôt intuitif. Par ailleurs, comme décrit plus haut, il a son propre résolveur de dépendances, et celui-ci est particulièrement lent au point que la génération du lock file peut prendre du temps. Soit. Mais je suis un peu sceptique à cause de points plus fondamentaux, notamment le fait que les dépendances de votre générateur de documentation contraignent celles de votre projet, ce que je trouve assez idiot. Je recommande aussi ces deux posts très détaillés sur le blog d’Henry Schreiner : Should You Use Upper Bound Version Constraints? et Poetry versions. Pour faire court, Poetry incite fortement à mettre des contraintes de version maximum (comme jinja2 < 3
), ce qui est problématique quand les utilisateurs de Poetry se mettent à en abuser sans s’en rendre compte. Et il a aussi des opinions assez spéciales sur la résolution de dépendances, par exemple il vous force à mettre une contrainte < 4
sur Python lui-même dès qu’une de vos dépendances le fait, alors que tout projet Poetry le fait par défaut. J’ajoute le fait qu’on ne peut pas utiliser un autre build backend avec Poetry que Poetry-core. En corollaire, on ne peut pas utiliser Poetry sur un projet si tout le projet n’utilise pas Poetry, ce qui implique de changer des choses pour tous les contributeurs (alors que PDM et Hatch peuvent fonctionner avec un build backend différent). C’est pour moi un gros point noir.
Alors… PDM ? Honnêtement, je n’en ai pas assez d’expérience pour juger vraiment. Je sais qu’il corrige la plupart des défauts de Poetry, mais garde le « défaut du générateur de documentation ».
Alors… Hatch ? C’est celui que j’utilise depuis quelques mois, et jusqu’ici, j’en suis plutôt satisfait. C’est un peu dommage qu’il n’ait pas encore les lock files, mais je n’en ai pas besoin pour mes projets.
Je n’utilise pas pyenv. Déjà avant Hatch, je trouvais qu’il représentait trop de travail à configurer par rapport au service rendu, que je peux facilement faire à la main avec ./configure && make
dans le dépôt de Python. Et depuis que j’utilise Hatch, il le fait pour moi, sans avoir à compiler Python.
De même, je n’utilise plus tox, je fais la même chose avec Hatch (avec la nuance que si j’ai déjà tanné mes co-mainteneurs pour remplacer make
par tox, j’hésite à re-changer pour Hatch…). J’ai fini par me méfier du format INI, qui est piégeux (c’est subjectif) et mal spécifié (il y en a mille variantes incompatibles).
Donc, en résumé, j’utilise seulement deux outils, qui sont pipx, et Hatch. (Et j’espère n’avoir bientôt plus besoin de pipx non plus.) Mais si vous avez besoin de lock files, vous pourriez remplacer Hatch par PDM.
Je termine la comparaison avec un mot sur Conda. À mon avis, entre écosystème Conda et écosystème PyPI, le choix est surtout pragmatique. Qu’on le veuille ou non, l’écosystème Python s’est historiquement construit autour de PyPI, qui reste prédominant. Malheureusement, la réponse de Conda à « Comment installer dans un environnement Conda un paquet PyPI qui n’est pas disponible au format Conda ? » est « Utilisez pip, mais à vos risques et périls, ce n’est pas supporté », ce qui n’est pas fantastique lorsque l’on a besoin d’un tel paquet (cela arrive très vite). D’un autre côté, pour le calcul scientifique, Conda peut être plus adapté, et il y a des pans entiers de ce domaine, comme le géospatial, qui fonctionnent avec Conda et ne fonctionnent pas du tout avec PyPI.
J’espère que ce très long panorama était instructif et aidait à y voir plus clair dans l’écosystème. Pour la troisième dépêche, je vous proposerai un focus sur la distribution des modules d’extension écrits dans des langages compilés, sur ses difficultés inextricables, et sur les raisons pour lesquelles le modèle de Conda en fait une meilleure plateforme que PyPI pour ces extensions.
Aller plus loin
- Guide officiel du packaging Python (133 clics)
- Liste de projets (sur ce même guide) (116 clics)
# Avis d'un utilisateur / dev
Posté par xryl669 . Évalué à 10.
À chaque fois que je dois utiliser une application en python, j'ai un arrière goût de bile dans la gorge. Je sais que quelque chose va casser, je sais que même si rien ne casse, la prochaine chose va casser, je sais que je n'aurais jamais assez de place pour stocker 40 versions différentes de la même bibliothèque (surtout en embarqué).
Honnêtement, à part pour du prototype, je trouve que l'environnement Python est une horreur.
Le python, c'est génial comme langage, c'est concis, c'est simple et facile à apprendre.
Mais l'évolution du langage qui n'est pas rétrocompatible et qui casse donc toute la base de code de la version précédente, ça, c'est mal.
Avec l'avènement de l'IA, et des notebooks Jupyter, il y a eu une explosion des utilisateurs python qui n'apprennent pas le fonctionnement du langage et de son univers.
Résultat: impossible de faire rentrer papa dans maman, le moindre projet utilisant 2 programmes Python ne fonctionnent jamais entre eux. Alors il faut les faire tourner dans un venv séparé et donc les faire communiquer via de l'IPC système. Déjà que Python c'est lent, là, c'est carrément dément.
En ce moment, je dois utiliser une librarie qui utilise un code en C (et donc du binding) sauf que la version système installé partout sur n'importe quel système de moins de 10ans est 4 ans en avance de ce que le code python attend. Si au moins python faisait comme tous les autres langages de programmation (c'est à dire de supporter nativement du FFI), la mise à niveau n'imposerait pas de réécrire tout le code du binding. Mais là, c'est l'enfer…
[^] # Re: Avis d'un utilisateur / dev
Posté par jeanas (site web personnel, Mastodon) . Évalué à 4.
Intéressant retour.
À la décharge de Python, il y a eu pas mal d'efforts pour améliorer la rétrocompatibilité sur les versions récentes, notamment suite au traumatisme de Python 3 (exemple, autre exemple).
Je n'ai jamais eu ce cas, mais je suis d'accord que si c'est nécessaire, c'est assez dément.
Je ne comprends pas. ctypes n'est pas un support natif de FFI ? Ni la C API de CPython ?
De manière générale, une grande force de Python aujourd'hui (p.ex. dans les sciences) par rapport à d'autres langages est de pouvoir être mélangé assez facilement à du C, Rust voire même C++, et c'est justement ce qui complique énormément le packaging, comme j'essaierai de l'expliquer dans la prochaine dépêche, donc je ne crois pas qu'on puisse l'accuser de manque de support FFI…
[^] # Re: Avis d'un utilisateur / dev
Posté par oau . Évalué à 5. Dernière modification le 24 décembre 2023 à 11:06.
hello
je rebondis sur la partie IA et notebooks Jupyter. Depuis 2015 je bosse avec des "data scientist". Leur principal point commun c'est d'utiliser et donc de livrer leur code sous forme de notebooks Jupyter. J'avais donc, à priori, deux voie pour passer leurs livrables en production. Soit apprendre à des ds à coder correctement et à livrer du code que l'on peut directement mettre en prod (ils adorent les csv avec des chemins en dur …).
On ne peut pas dire que ça a échoué, on dira que ça n'a pas marché. Alors on a décidé de prendre des dev pyton pour faire de la datascience. Ça a marché un temps. Mais depuis deux ans on fabrique nos propres algo de traitement du langage et là ça coince. Un dev python ne sait plus faire.
Résultat des courses j'ai deux équipes une de ds qui nous livre du code pas vraiment utilisable et une équipe de dev python qui ré-écris toute ou partie du code pour la faire rentrer sur la plateforme.
Pour limiter les réécritures on essaie de packager un maximum de chose. En particulier nos structures de données et l'accès aux dites données, pour enfin ne plus voir de chemin en dur vers un csv …
Et je termine donc : nous utilisons pyenv, pdm et docker pour le dev, comme ça on isole bien notre env dans docker. Et gitlab pour la publication de nos packages.
[^] # Re: Avis d'un utilisateur / dev
Posté par abriotde (site web personnel, Mastodon) . Évalué à 2. Dernière modification le 25 janvier 2024 à 22:52.
Python est dans les pires langages point de vue perf on lui adjoint du code C/C++/Rust pour avoir de meilleur perf mais cela complique tout.
C'est pourquoi je préfère Julia qui fait le choix de "compiler en live", ce n'est pas parfait, notamment au lancement, mais je trouve le langage plus cohérent rapport perf/simplicité. Il offre le meilleur des deux monde (un bon compromis en fait) c'est pourquoi sa cible n°1 ce sont les data-scientiste. Ce n'est pas demain qu'il aura un aussi bon écosystème que Python, c'est pourquoi Julia utilise beaucoup de biding Python…
Sous licence Creative common. Lisez, copiez, modifiez faites en ce que vous voulez.
[^] # Re: Avis d'un utilisateur / dev
Posté par abriotde (site web personnel, Mastodon) . Évalué à 1.
Pour un peu plus de détail, Julia est un langage "interprété" mais conçu pour être compilé en ASM. Il compile au départ tout le code en assembleur (Ou une grosse partie je pense). Une fonction non typé est considéré comme du template C++ et elle peut donc donné naissance à plusieurs version ASM suivant le besoin du programme et l'inférence de type.
Sous licence Creative common. Lisez, copiez, modifiez faites en ce que vous voulez.
# Rye ?
Posté par -mat . Évalué à 3.
Il y a un nouvel outil dans ce panorama : https://github.com/mitsuhiko/rye
Pour l'instant, il répond bien à mes petits besoins… Je vais continuer à le tester.
[^] # Re: Rye ?
Posté par jeanas (site web personnel, Mastodon) . Évalué à 3.
Oui, j'aurais pu le mettre dans la liste, mais la longueur de la dépêche était déjà trop délirante. J'étais très enthousiaste pour Rye au début car c'était le seul capable de gérer les versions de Python (avec des exécutables précompilés, pas comme pyenv), mais depuis que Hatch a cette fonctionnalité, je suis moins chaud, car Hatch est beaucoup plus mature, même s'il manque encore les lock files à Hatch (pas pour longtemps, espérons-le).
[^] # Re: Rye ?
Posté par Spyhawk . Évalué à 5.
Sur quel type de projet utilises-tu Hatch? J'ai du me plonger dans le sujet du packaging Python à titre professionnel, et mon opinion est que tous ces outils sont relativement simples et équivalents si on se limite à du Python pur. Dès que des bindings et du multiplateforme entre en compte, alors tous ces outils ajoutent une surcouche qui simplifie effectivement le processus de base, mais rentre très compliqué le débuggage et font perdre un temps énorme à cause de bugs internes sur des cas d'utilisation spécifiques (Poetry, qui s'avère finalement être une horreur sans nom).
[^] # Re: Rye ?
Posté par jeanas (site web personnel, Mastodon) . Évalué à 1.
J'utilise Hatch sur Pygments, dont je suis mainteneur (sauf qu'il y a du tox aussi, c'est pour ça que je disais que j'hésite à tanner mes co-mainteneurs pour passer à Hatch alors que je les ai déjà tannés pour qu'on sorte de Make). Et aussi sur d'autres projets qui n'ont pourtant pas de déclaration d'environnements Hatch, par exemple sur Sphinx, en me rajoutant mon fichier
hatch.toml
perso. Et pour tous mes scripts persos aussi.[^] # Re: Rye ?
Posté par abriotde (site web personnel, Mastodon) . Évalué à 0.
C'est ça le problème, c'est qu'à la base un langage interprété est peu performant mais plus simple à développé et tourne plus facilement sur du multi-plateforme (Pas à s'embêter avec les appels systèmes spécifiques). Mais au final ce n'est pas si simple d'en faire du multi-plateforme (Sauf à limiter la complexité) mais en plus comme Python est dans les pires langages point de vue perf on lui adjoint du code C/C++/Rust avec pour conséquence de bousiller le multi-plateforme.
C'est pourquoi je préfère Julia qui fait le choix de "compiler en live", ce n'est pas parfait, notamment au lancement, mais je trouve le langage plus cohérent. Il offre le meilleur des deux monde (un bon compromis en fait) c'est pourquoi sa cible n°1 ce sont les data-scientiste.
Sous licence Creative common. Lisez, copiez, modifiez faites en ce que vous voulez.
[^] # Re: Rye ?
Posté par Spyhawk . Évalué à 4.
Un autre utilisateur de Rye ici. Pour les projets avec du python pur, c'est vraiment pas mal. Je suis en train de migrer un projet plus complexe (avec bindings C++) depuis poetry, qui pose une quantité de problèmes dans mon cas d'utilisation, donc on verra si il tient la route. A noter que Rye utilise le backend de build de hatch.
[^] # Re: Rye ?
Posté par jeanas (site web personnel, Mastodon) . Évalué à 3.
Petite précision : Rye utilise Hatchling (le build backend de Hatch) par défaut. Mais il peut marcher avec n'importe quel build backend déclaré dans la table
[build-system]
, de même que Hatch et PDM — mais pas Poetry, qui doit toujours être utilisé avec le backend de Poetry.Pour un projet en C++, je pense que le mieux de très loin à l'heure actuelle est de prendre un build backend spécialisé, comme meson-python pour Meson ou bien scikit-build pour CMake.
# Compliquai
Posté par devnewton 🍺 (site web personnel) . Évalué à 10.
A côté Maven, c'est KISS !
Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.
[^] # Re: Compliquai
Posté par rewind (Mastodon) . Évalué à 7.
À côté de ça, même compiler à la main des sources en C ou C++ est KISS.
[^] # Re: Compliquai
Posté par xcomcmdr . Évalué à 1. Dernière modification le 24 janvier 2024 à 10:23.
A côté de Maven/Ant/Gradle/JeSaisPasQuoiEnJava, .NET c'est KISS:
Tout ce que tu as à faire pour installer un package:
Pour builder, quelle que soit la base de code:
Pour packager:
Pour publier:
Pour lancer les tests:
Pour lancer l'appli:
Pour créer un projet:
Tous les chemins mènent à
Romedotnet. ¯\(ツ)/¯"Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)
[^] # Re: Compliquai
Posté par barmic 🦦 . Évalué à 2. Dernière modification le 24 janvier 2024 à 11:30.
Java se base sur la collaboration et l'échange entre acteurs. Tu as différents outils car c'est possible. .Net est bien plus centralisé.
Sur la jvm, tu es libre de choisir ton chemin :p
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: Compliquai
Posté par xcomcmdr . Évalué à 1.
Bah c'est possible de remplacer tout en .NET, exemple avec BFlat qui enlève dotnet, MSBuild et Nuget.
La collaboration entre acteurs dans .NET… Bah c'est assuré par la licence MIT, et la .NET Fondation, par exemple…? Il est où le souci ?
C'est surtout que pour une raison technique qui m'échappe, pour packager et builder ça n'a jamais été clairement défini en Java…? Pourquoi quand j'ouvre des sources Java je sais jamais exactement comment ça va se builder ? Un coup c'est Gradle, un coup c'est Maven, un coup c'est Ant. Et je tombe toujours sur des soucis, c'est rageant.
(Je suis un noob en Java, si quelq'un peut m'expliquer les raisons techniques / historiques, ce serait très intéressant !)
Bah je peux écrire mon propre host pour le CLR, c'est même documenté.
Ou utiliser le .NET nanoFramework qui est une VM concurrente pour l'embarqué.
Ou utiliser l'AOT, ce qui vire plus ou moins la VM.
Bref… Je vois pas trop la différence en termes de possibilités techniques sur ce point, si ce n'est que y'a le moins le choix en termes de VMs existantes.
"Quand certains râlent contre systemd, d'autres s'attaquent aux vrais problèmes." (merci Sinma !)
[^] # Re: Compliquai
Posté par Yves (site web personnel) . Évalué à 3.
La raison est simple : .NET est arrivé après, longtemps après.
À ce stade, Ant était déjà largement du passé (mais encore une fois, nous sommes libres, n’est-ce pas, donc il y a quelques utilisateurs résiduels…), et Maven, après 2 itérations majeures d’améliorations, s’est stabilisé sur une 3ème itération. Mais on n’arrête pas le progrès et des propositions tentent de le détrôner. Normal.
# PyInstaller le mal nommé
Posté par Philippe F (site web personnel) . Évalué à 8.
Attention, au nom trompeur de PyInstaller, ce truc génère un .exe , pas un installeur. Le paragraphe de la dépêche est un peu ambigu sur le sujet.
PyInstaller, je comprends qu'on lui reproche beaucoup de choses, mais pour moi, ça marche super bien depuis des années. Beaucoup mieux qu'avec son prédécesseur (py2exe). Ca reste quand même une bonne idée de faire une passe de nettoyage sur les DLL et modules python inutiles après la génération du .exe pour réduire la taille du truc final. Surtout qu'avec du PyQt / PySide, ça grossit très vite (cf mon journal sur le sujet )
Après PyInstaller, un petit coup de InnoSetup et le programme s'intègre parfaitement dans un environnement Windows.
Une petite blagues des environnement virtuels quand vous faites du code portable, c'est qu'ils ne s'activent pas de la même façon sous Linux et sous Windows. Ce serait trop simple, il était essentiel de changer \bin ou /scripts pour passer de l'un à l'autre.
Autre blague des projets portables, les "lock file" qui peuvent ne pas être identiques sous Windows et sous Linux (dependance à mariadb par exemple), sauf que c'est absolument pas géré par le format ni par les outils. C'est juste la m***** en fait.
[^] # Re: PyInstaller le mal nommé
Posté par jeanas (site web personnel, Mastodon) . Évalué à 3.
Exact, merci pour la rectification.
Pas faux ; cela dit, de toute façon, l'activation doit être différente, vu qu'il faut un
source
d'un côté et pas de l'autre, comme les shells ne sont pas les mêmes.(Apparemment, l'auteur d'origine de virtualenv est d'accord sur le fait que c'était une mauvaise décision, mais la casse de compatibilité si c'était changé aujourd'hui serait monumentale.)
Qu'est-ce que tu entends par « ne peuvent pas être identiques » ? C'est vrai que les paquets peuvent déclarer des dépendances dépendant de l'OS (et heureusement), et c'est vrai que pip-tools ne permet pas de créer un lock file unique pour toutes les plateformes, mais par contre Poetry le fait toujours, et PDM le fait par défaut.
[^] # Re: PyInstaller le mal nommé
Posté par Philippe F (site web personnel) . Évalué à 4.
Mon cerveau arrive bien à gérer la charge mentale de faire source plutôt qu'un appel direct, mais le script vs bin, il y a toujours un moment où je me maille.
Carrément. Ce qui fait que c'est vite lourd. J'imagine qu'en théorie, je devrais avoir un windows-req.txt et un linux-req.txt qui tous deux viseraient un common-req.txt . Mais je ne suis pas sûr que ça survive correctement une mise à jour des dépendances Clairement, je ne suis pas assez familier avec l'outil. Je devrais lire plus de doc sur le sujet… mais il me semble qu'elles sont aux abonnés absents. Tous les tutos partent du principe que tu es sur une seule plate-forme, linux dans 99% des cas.
Au final, comme je target du linux en développant sous Windows, je commit la version linux et je garde une modif locale sous Windows qui me pète à la gueule au moins une fois par mois. Pas idéal, mais pas grave. Et une fois par an, j'ai une version des requirements Windows qui part dans le build de la prod. Comme c'est un truc que je fais très rarement, je finis par oublier tous les pièges possibles.
# Merci !
Posté par ploum (site web personnel, Mastodon) . Évalué à 9.
J’aimerais, encore une fois, remercier l’auteur de cette dépêche qui adresse une problématique vraiment importante dans le monde python: « Mais c’est quoi ce bordel ? ».
J’ai d’ailleurs pu bénéficier de l’aide de l’auteur pour mon projet Offpunk et, depuis lors, ça roule !
Bref, je suis super content de voir tout expliqué de manière précise et détaillée. Merci !
Mes livres CC By-SA : https://ploum.net/livres.html
[^] # Re: Merci !
Posté par jeanas (site web personnel, Mastodon) . Évalué à 4.
Merci pour les remerciements, ça fait plaisir !
# Pipenv?
Posté par slashdev . Évalué à 3.
Un avis sur pipenv?
Il fait partie des outils recommandés sur packaging.python.org.
Les +
- la creation et navigation dans plusieurs venv en fonction du dossier courant
- le lock et le sync portable
Les -
- ne partage pas les credentials pypi avec pip lors de l'accès à un repo privé
[^] # Re: Pipenv?
Posté par jeanas (site web personnel, Mastodon) . Évalué à 4.
Pipenv, c'est une longue histoire. Pour la faire courte, Kenneth Reitz, dans une recherche malsaine de popularité, l'a vendu comme un outil miracle et plus officiel qu'il ne l'était vraiment, alors que Pipenv était encore trop limité, en particulier au niveau de la rapidité. Cela a causé un véritable fiasco, qui est l'une des raisons pour lesquelles toute proposition du type « rendons l'outil X officiel » rencontre beaucoup de résistance aujourd'hui.
À un moment, il n'a plus été maintenu pendant un bon moment. Il a trouvé de nouveaux mainteneurs depuis, donc je ne sais pas trop quoi en penser maintenant.
Quant aux recommandations sur packaging.python.org, elles ne sont malheureusement pas à jour. Cf. https://github.com/pypa/packaging.python.org/issues/1468, issue que j'ai justement ouverte hier.
[^] # Re: Pipenv?
Posté par slashdev . Évalué à 1.
J'ai récemment reçu un bon support sur mes issues et tout fonctionne correctement pour mes usages, même si la durée du lock laisse à désirer.
Votre issue est symptomatique des problèmes de packaging de Python. Ça me donne vraiment l'impression d'un sabordage. Encore une fois c'est un ressenti, mais je trouve que la popularité grandissante de Python auprès d'une population moins bas niveau, utilisateurs de notebook en tête, se heurte systématiquement au mur du packaging quand vient l'heure du passage en production.
[^] # Re: Pipenv?
Posté par slashdev . Évalué à 4.
Vivement la partie 4, qui je l'imagine nous éclairera sur pypa, la gouvernance sur les sujets de packaging et par ricochet l'exclusion de pipenv de votre article.
Et pour les lecteurs qui comme moi déplore cette complexité: faites comme tout le monde, choisissez au hasard et iterez tous les 6 mois.
Merci pour ce gros travail de synthèse, hâte de lire la suite.
# Guix ?
Posté par Apichat (site web personnel) . Évalué à 1.
L'utilisation de Guix pour gérer les paquets ne permettrait-elle pas de simplifier tout ce bazar en apportant de meilleurs solutions à tous ces problèmes ?
https://guix.gnu.org/
[^] # Re: Guix ?
Posté par jeanas (site web personnel, Mastodon) . Évalué à 5. Dernière modification le 23 décembre 2023 à 14:28.
Désolé, je ne comprends pas la pertinence de Guix dans ce contexte. Guix est un gestionnaire de paquets générique, tourné vers aucun langage en particulier. Il ne sait pas lire ou installer les formats de paquets Python, il ne sait pas créer ou gérer des environnements virtuels, installer un paquet en mode éditable, résoudre les dépendances depuis PyPI, ou que sais-je. Je ne vois pas quel problème il peut résoudre dans ce contexte.
(En plus, Guix ne fonctionne que sous Linux.)
[^] # Re: Guix ?
Posté par barmic 🦦 . Évalué à 5.
La tradition veut qu'il y ait toujours un commentaire sur guix ou nix.
Le principe c'est qu'ils permettent de créer des environnements. C'est comme venv, mais c'est pas limité à python. Une fois dans l'environnement tu peux utiliser pip comme tu le souhaites (y compris pour lui faire installer en system wide).
Certe mais si j'ai bien compris, tu as évité le problème des dépendances systèmes. Si tu as 2 outils qui dépendent de versions différentes de bibliothèques natives, si j'ai bien compris venv ne fera rien pour toi.
Guix et nix feront le travail de la même façon pour python, perl, ruby ou n'importe quel autre écosystème
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: Guix ?
Posté par Guillaum (site web personnel) . Évalué à 4.
Concernant nix (Je ne connais pas guix). Il y a pleins de module dans nixpkgs qui sont destiné au travail avec des packages python. Tous les packages de PyPI sont importable. La résolution de dépendance depuis PyPI peut se faire avec des outils tierces mais intégrés dans nix et tout cela se greffe sur different outils que tu as décrit pour faire le boulot.
Ici nix ne remplace pas forcement, mais permet d'unifier, regrouper ensemble et proposer une solution robuste.
Nix est fait pour gérer des env virtuels. Je ne sais pas ce que tu veux dire par "installer un packet en mode éditable".
Nix ne fonctionne pas non plus sous windows (enfin si, dans le WSL). Si c'est un problème pour toi, alors en effet, sinon cela marche très bien.
J'ai maintenu une base de code contenant 100+ packages python, des milliers de fichiers, dont certains auto-generés à la volée dans le processus de build. Il y avait aussi du C++ et du Haskell, et du bash et du C et du go. Le tout cross compilé (i.e. certains utilitaires python dépendant de module python natif "maison" déployé sur du ARM et buildé sur du x86_64 sur un os different). On avait des dev sous linux, macos et windows (WSL). environ 100+ libs dont on dépendait depuis PyPI, et une bonne quarantaine venant d'autres depot git arbitraire.
Et pour le coup, on avait des contraintes amusante de traçabilité / reproductibilité (aéronautique) et nix est très bon pour cela (c'est même le seul outil qui me garantie que mon interpréteur et ses dépendances ne changent pas du jour au lendemain sans me prévenir).
Bref, nix (et guix qui est très proche) est fait pour résoudre ce type de problème et le fait très bien (de mon point de vu).
Le coût d'entrée est cependant considéré comme élevé. Je n'arrive pas à juger. Je n'ai jamais vraiment trouvé.
De manière plus général, si tu as un besoin simple (i.e. avoir un environment virtuel reproductible pour bosser sur du python + packager + déployer une application, en utilisant des packages python "standards"), nix(pkgs) te propose une solution en 5 lignes. Si ton besoin est plus complexe, et bien c'est plus complexe, mais c'est à ma connaissance le seul outil qui fait cela. Si tu as un besoin intermédiaire (ou d'autres contraintes, comme bosser sous windows), et bien nix n'est pas forcement la bonne solution.
[^] # Re: Guix ?
Posté par jeanas (site web personnel, Mastodon) . Évalué à 2.
Je parle de ce que fait l'option
-e
/--editable
depip install
. Elle permet d'installer un paquet dont on a le code source en local, d'une manière qui ne copie pas les fichiers dans le dossier d'installation mais crée une sorte de symlink, afin qu'on puisse éditer le code et voir immédiatement les changements sans réinstaller. (Techniquement, ce ne sont pas des symlinks, mais c'est l'idée.)Oh, la question n'est pas tellement mon propre usage ; dans cette dépêche, j'ai présenté les outils qui sont répandus dans la communauté Python parce qu'ils sont multiplateforme, Nix n'était donc pas dans le sujet, mais je ne doute pas que c'est un super outil sous Linux.
En tous cas, merci pour les infos sur Nix, que je connais mal et qui a l'air intéressant.
[^] # Re: Guix ?
Posté par Guillaum (site web personnel) . Évalué à 2.
Il faudrait que je regarde
-
plus en détail dans pip, mais ce genre de chose est assez facile à faire avec nix. L'idée c'est que tu va plutôt changer la "source" de ta dependence pour pointer vers un répertoire à toi et ainsi les changement dans ce répertoire seront pris en compte.Le repertoire en question peut être quelque chose que tu as toi même
git clone
, mais nix peut aussi te décompresser les sources dans un repertoire de ton choix à la demande.# Publication sur Pypi
Posté par Julien Laumonier . Évalué à 6.
Suberbe dépèche que je n'ai pas eu le temps de lire tout en détail, mais que je lirai à tête reposée.
Petit point de détail avant de créer votre projet et d'aller trop loin dans le développement si vous pensez le publier sur Pypi. Ne faites pas comme moi et vérifiez sur Pypi si le nom n'est pas déjà pris par un vieux projet mis à jour une fois il y a 10 ans et abandonné depuis, et pas seulement sur test-pypi. :) Sinon vous allez vous retrouver à devoir changer le nom de votre projet alors que toute la doc et tous les tests sont faits et que vous aviez passé des jours à trouver un nom super cool !
# pip dans un environnement conda
Posté par Guillawme (site web personnel, Mastodon) . Évalué à 1.
Est-ce que cela cause beaucoup de problèmes en pratique ?
J'utilise uniquement conda car c'est pour moi la façon la plus simple d'éviter certains problèmes, mais ce que j'installe dans un environnement conda n'est pas toujours dans les dépôts conda alors j'installe avec pip quand c'est nécessaire. Je n'ai pas beaucoup d'exemples parce que j'utilise seulement une poignée de programmes python pour un domaine spécifique, mais en tout cas je n'ai absolument jamais eu de problème en utilisant pip dans des environnements conda.
[^] # Re: pip dans un environnement conda
Posté par jeanas (site web personnel, Mastodon) . Évalué à 4.
Le problème est assez simple, c'est que Conda et pip ne se parlent pas entre eux.
Si tu installes le paquet foo avec pip dans un environnement Conda, et que tu fais ensuite
conda upgrade bar
, Conda risque d'installer une version de bar incompatible avec la version de foo, et ainsi de casser foo.L'inverse vaut aussi. Si tu fais
pip install foo
et que pip se sent obligé de mettre à jour bar pour satisfaire les dépendances de foo, cela risque de casser le paquet baz de Conda qui dépendait de bar.[^] # Re: pip dans un environnement conda
Posté par Guillawme (site web personnel, Mastodon) . Évalué à 1.
Ah je vois. C'est une situation que je ne rencontre jamais : quand j'ai besoin d'une nouvelle version d'un programme, je l'installe dans un nouvel environnement vide, parce que la plupart du temps j'ai aussi besoin de conserver la version précédente un certain temps. Mais en effet je vois tout à fait comment essayer de mettre à jour pourrait mettre le bazar.
[^] # Re: pip dans un environnement conda
Posté par slashdev . Évalué à 3.
Je dirais que < 2017 conda était un passage obligatoire car pandas, numpy et autres ne s'installaient pas facilement avec pip.
Depuis que tout l'écosystème pydata est disponible en wheel est s'installe parfaitement, je n'ai plus eu recours à conda. Parfois il est toujours nécessaire de partir d'une base miniconda sur des systèmes sans compilateur.
Je suis curieux de connaître vos use cases conda que pip+venv ne sait pas résoudre.
[^] # Re: pip dans un environnement conda
Posté par Guillawme (site web personnel, Mastodon) . Évalué à 6. Dernière modification le 26 décembre 2023 à 12:26.
Mon utilisation typique est très simple.
Je ne programme presque pas en général, et encore moins avec Python, que j'essaye d'éviter précisément parce qu'il y a trop d'outils différents pour installer des paquets et gérer des versions, et je n'ai ni l'envie ni le temps de m'y intéresser.
Mais j'utilise pour mon travail plusieurs programmes écrits en Python, et comme je l'écrivais dans un message précédent, j'ai presque toujours besoin de faire cohabiter plusieurs versions d'un même programme (au moins le temps de terminer des projets démarrés plus tôt avec une version plus ancienne). Certains de ces programmes sont distribués dans les dépôts conda, d'autres dans PyPI, certains dans les deux, d'autres encore sont seulement un dépôt git à cloner mais contenant du code qui a besoin de certaines versions précises de Python et de quelques paquets. Bref, c'est le bazar, et moi ce que je veux c'est utiliser ces programmes, pas apprendre un nouvel outil pour installer chaque nouveau programme dont j'ai besoin.
Peut-être que pip+venv marche aussi pour ça, je ne sais pas. De mon expérience, conda répond à toutes ces situations en ayant besoin d'apprendre un seul outil, donc c'est ce que j'utilise. Apprendre des outils spécifiques à Python ça ne m'intéresse pas, je le fais seulement parce que c'est nécessaire pour utiliser des programmes Python mais je m'en passerais bien si c'était possible.
# Excellent article
Posté par Selso (site web personnel) . Évalué à 3.
Hello,
Encore un excellent article merci. Ci dessous un retour à chaud.
En ce qui me concerne je suis toujours indécis sur quoi utiliser finalement, enfin je sens une légère préférence vers hatch. Ce n'est pas un reproche à l'article, car ce n'est pas ce que tu proposes.
pipx : un venv par module ?! Doit-on en arriver là ? On parvient bien à faire une distri linux sans dupliquer les dépendances de chaque lib ou outil.
Le lock file pas vraiment compris, je vais devoir relire… ah ok c'est un fichier requirements.txt
Et finalement ces outils vont quand même créer un package installable avec pip ou conda ? ou conda est-il exclusif à son écosystème ?
Est-ce que l'on ne pourrait pas redistribuer les solutions selon la complexité de l'application à packager, ou se trouve-t-on trop rapidement embarquée dans les successions de dépendances ?
[^] # Re: Excellent article
Posté par jeanas (site web personnel, Mastodon) . Évalué à 2.
Oui, un venv par outil en ligne de commande installé. Et oui, on parvient à faire des distributions Linux qui ne dupliquent pas les dépendances, mais grâce à des mainteneurs dévoués qui s'occupent de trouver des versions compatibles entre elles pour toute la distribution. PyPI a un modèle social complètement différent, n'importe qui peut y publier, donc ce n'est tout simplement pas possible. Ce modèle a ses inconvénients, comme la duplication entre environnements, ou encore le risque de malware, mais le modèle des distributions a aussi ses inconvénients, comme le fait qu'un paquet relativement peu populaire ou de niche va avoir du mal à faire son chemin dans toutes les distributions, qu'on ne peut pas mettre à jour ou rétrograder arbitrairement un paquet sans changer le système entier, et qu'on ne peut pas tester avec plusieurs versions d'une dépendance à chaque fois.
En pratique, la taille des venvs séparés n'est pas si problématique, surtout que les bibliothèques Python sont pour la plupart écrites en Python, donc pas compilées, ce qui fait que la taille du code (je veux dire le poids des fichiers
.py
) est relativement petite. Par exemple, sur mon ordinateur, j'ai 15 outils différents installés avec pipx, et le tout fait 379 Mo (du --si ~/.local/pipx/
), soit ≈ 25 Mo par application. Ce n'est certes pas optimal, mais pas indécent non plus (je crois que pour les Flatpak, la taille typique est un ordre de grandeur au-dessus…).Pas forcément :
requirements.txt
est un format possible (mais limité) pour les lock files. Poetry et PDM produisent des lock files dans des formats différents qu'ils comprennent chacun.Je ne suis pas sûr de comprendre la question.
Déjà, il faut voir que c'est un peu une simplification de ma part de dire que l'écosystème Conda est complètement séparé de l'écosystème PyPA, parce que beaucoup de paquets Conda sont créés à partir de paquets PyPI (étant donné que PyPI est l'index historique et de loin le plus utilisé), donc en fait, ces paquets Conda sont générés en appelant
pip
à l'intérieur du build script Conda. Conséquence : si on a un paquet écrit en Python pur (pas en C/C++/Rust) dans les formats PyPA (sdist et wheel), on peut le convertir en paquet Conda. L'inverse n'est pas vrai : si on a un paquet Conda, on ne peut pas facilement à ma connaissance le convertir en paquet pour PyPI (ce truc a l'air d'être mort).Ensuite, les lock files servent surtout aux applications en bout de chaîne, par exemple une application Web déployée sur des serveurs, un script de data science, … Ces projets ne sont généralement pas redistribués comme paquets (Conda ou PyPI). Certes, les lock files peuvent aussi servir sur des librairies, ou des applications redistribuées largement (comme ces outils de packaging eux-mêmes, ou des outils comme Sphinx pour la documentation), mais alors c'est principalement pour fixer les dépendances utilisées pour tester l'application, et normalement jamais pour la liste des dépendances de l'application redistribuée.
Donc, en fait, il n'y a pas vraiment de lien entre lock files et redistribution. Le lien entre les lock files et la question « Conda vs. PyPI », c'est plutôt la question de la source des paquets que demande le lock file. Avec Poetry, il n'est pas possible (à ma connaissance) de générer des lock files qui prennent les paquets sur Conda. Par contre, c'est possible avec PDM.
Désolé, je ne comprends pas la question, pourrais-tu reformuler ?
[^] # Re: Excellent article
Posté par Selso (site web personnel) . Évalué à 2.
Salut.
Grand merci pour prendre le temps de me répondre.
Je vais essayer de clarifier mes questions.
D'accord ok mais du coup cela ne pose-t-il pas un pb d'orchestrer l'ensemble qui fonctionne dans des environnements séparés ? si mon application dépend de lib1 dans env1 (et qui partagerait cet environnement, pour faire plus simple), et de lib2 dans env2, combien ai-je d’interpréteurs lancés ? Faut-il des mécanismes particuliers pour partager les données s'il y en a plusieurs ? Ou alors je suis a côté de la plaque et l'env est complètement transparent pour au runtime.
Par ailleurs comme tu le soulignes pour les distributions linux, on n'utilise pas directement pip pour installer les paquets mais la distribution qui bénéficient du travail de ces mainteneurs. Cela rendrait l'utilisation de pipx superflue dans ce cas non ?
Quand à l'espace disque utilisé : il me semble que python laisse un fichier compilé des modules lancés, ce qui multiplie l'espace utilisé.
Je viens de l'embarqué, l'optimisation de l'espace disque reste un critère dans les choix de design. C'est moins le cas pour des applications desktops.
Alors le lock file est il un fichier pour le "packageur" ou l'utilisateur ?
Est-ce que la finalité de ces différents backend c'est de pousser les paquets sur pypi ? Il me semble que pour anaconda il s'agit d'une autre distribution python ?
A quel point ces outils sont interopérables sur les distributions ?
Mais tu as déjà répondu pour conda : "oui mais dans un seul sens et si…"
Tu réponds un peu déjà à la question juste au dessus. Le choix du build backend dépend aussi de la source des paquets.
En fait j'essaie de compiler en tête les critères pour sélectionner un backend plutôt qu'un autre, dans une sorte de table tu vois.
Bêtement je pensais utiliser pyinstaller pour un truc très simple à fournir à un utilisateur non-developpeur. Mais visiblement on propose cet outil pour freezer plus que pour distribuer un package, même si pyinstaller génèrerait les bons fichiers pour.
Ensuite dans la plupart des cas on utiliserait hatching car il est proposé par défaut dans le tuto du site pypi.
J'ai une connaissance qui a utilisé poetry, visiblement à cause de la complexité des dépendances de l'application utilisé. Je prends peut-être un raccourci, mais genre si la gestion du packaging est est complexe a cause des dépendances alors prends poetry qui t'évitera les accidents.
Ensuite j'ai l'impression qu'anaconda vise un milieu bien particulier pour les applications : une simple recherche google et dans le résumé du lien je lis : "Python/R data science and machine learning on a single machine".
[^] # Re: Excellent article
Posté par jeanas (site web personnel, Mastodon) . Évalué à 3.
Ah… mais non, il y a un environnement par application, pas un environnement par dépendance de chaque application. Si j'installe Sphinx avec
pipx install sphinx
, pipx va créer un environnement pour Sphinx, et mettre toutes les dépendances de Sphinx dans cet environnement, donc aucune interaction entre environnements, Sphinx s'exécute juste à l'intérieur de cet environnement. Par contre, si je fais ensuitepipx install hatch
, il y aura un environnement séparé pour Hatch.Oui, si on dispose d'un paquet de sa distribution et qu'on préfère l'utiliser, on n'a pas besoin de pipx. Par contre, s'il n'y a pas de paquet dans la distribution, ou si ce n'est pas la bonne version, il faut passer par pipx. (Et accessoirement, si on est sous macOS ou Windows, on n'a pas le choix.)
Ces fichiers contiennent du « bytecode », un code intermédiaire qui est de bas niveau mais pas du code natif. Ce n'est pas si lourd.
Dans mon
~/.local/pipx/
, ces fichiers font moins d'un quart de la taille totale (88 Mo / 379 Mo, les 88 Mo sont comptés pardu --si -ch $(fd -g '*.pyc')
).Ça dépend de quel utilisateur on parle, mais en tous cas, jamais pour un "packageur" lorsqu'il distribue une librairie ou application. Mettons que je développe l'application Sphinx. Il est absolument hors de question que je crée un lock file pour fixer toutes les dépendances de Sphinx et que j'en fasse les dépendances du paquet que je distribue. Par exemple, si je le fais, les mainteneurs de distributions Linux vont me détester parce que je vais forcer les versions exactes de tous les paquets dont je dépends. Par exemple, je vais exiger jinja2 3.1.2. Et il suffirait que le mainteneur d'une autre application, disons Flask , fasse la même chose mais exige jinja2 3.1.1, pour qu'il devienne impossible à la distribution de proposer à la fois Sphinx et Flask.
Par contre, si je suis développeur d'une librairie, mettons foo, et que foo utilise Sphinx comme outil de documentation, je peux avoir un lock file qui fixe les versions de Sphinx et de ses dépendances que je veux pour générer ma documentation. Mais le contenu de ce lock file n'a rien à voir avec les dépendances de ma librairie foo elle-même. Les utilisateurs de ma librairie foo ne verront jamais mon lock file, ils verront juste ma jolie documentation.
Par définition, un build backend est ce qui génère les fichiers sdist et wheel, qui sont les formats distribués sur PyPI. Donc, tous les build backends (dont Hatchling, Poetry-core, PDM-backend et Flit-core) permettent de distribuer sur PyPI, il n'y a aucun problème d'interopérabilité à ce niveau-là.
Attention à ne pas confondre le choix du build backend avec le choix d'un outil plus large qui maintient des environnements, compile des lock files et ce genre de choses. En particulier, si tu as besoin de lock files avec Conda, tu peux utiliser PDM, et PDM te laisse le choix du build backend (il possède son propre build backend PDM-backend, mais ce n'est pas une obligation de l'utiliser, tu peux aussi te servir de PDM tout en ayant Hatchling, Poetry-core ou Flit-core comme build backend).
Disons que le freeze est une première étape. Ensuite, on peut passer un coup de Inno Setup par exemple.
Le résolveur de Poetry est effectivement censé faire des choix plus malins en contrepartie du temps qu'il prend. Je n'ai pas d'expérience pour savoir si les avantages sont substantiels.
Comme je l'écrivais dans la dépêche, il résout toutes les dépendances en même temps (dépendances du projet mais aussi de ses outils de test et de documentation), ce qui peut causer des problèmes inutilement.
Oui, Conda s'adresse clairement à un public de calcul scientifique.
[^] # Re: Excellent article
Posté par Yves (site web personnel) . Évalué à 2.
C’est vite dit… et faux, à mon avis.
Sur Linux, on trouve par exemple
libssl.so.3
etlibssl.so.1.1
, et ce n’est pas pour rien : les distributions Linux se bâtissent sur une collection de programmes open-source elles aussi, et ces programmes ne sont pas tous d’accord sur quelle version de telle ou telle lib utiliser.Autre exemple : avec Nix (mentionné plus haut), chaque paquet est installé dans un répertoire portant un nom technique ± unique (un hash, je crois), permettant ainsi une cohabitation quasi-infinie.
Soit dit en passant, je crois que Nix est multi-plateformes, contrairement à Guix.
Avec Python, un mécanisme similaire pourrait être retenu, permettant d’installer autant de versions que l’on veut sans provoquer de conflits, quitte à étendre quelques syntaxes pour aider à désambiguïser.
[^] # Re: Excellent article
Posté par jeanas (site web personnel, Mastodon) . Évalué à 4.
Certes, dans une distrib Linux classique, une poignée de dépendances sont dupliquées avec plusieurs versions, comme dans ton exemple de libssl, mais c'est tout de même plutôt rare. La grande majorité des paquets sont fournis avec une seule version. D'après mon expérience, les mainteneurs de distribution rechignent généralement à avoir plusieurs versions et font le ménage dès qu'une version n'a plus lieu d'être. Ensuite, il est effectivement vrai qu'il existe des distributions moins classiques comme Nix où ce n'est pas le cas, mais je n'ai pas dit que c'était le cas dans absolument toutes les distribs (« on parvient à faire des distributions Linux »).
L'idée revient de temps en temps, mais il y a un large consensus sur le fait que ce n'est pas une bonne idée pour Python. Ça a existé par le passé, dans setuptools, et ça n'a jamais été une réussite.
https://discuss.python.org/t/allowing-multiple-versions-of-same-python-package-in-pythonpath/2219
https://discuss.python.org/t/installing-multiple-versions-of-a-package/4862
# pip install X; pip install Y vs pip install X Y
Posté par kortex . Évalué à 4.
Merci pour l'article très intéressant.
A un moment tu dis :
C'est une partie qui m'intéresse :-)
Pourrais-tu détailler stp ?
C'est quoi la différence concrètement du coup ?
[^] # Re: pip install X; pip install Y vs pip install X Y
Posté par jeanas (site web personnel, Mastodon) . Évalué à 6.
Avec
pip install X Y
, pip recherche des versions de X, de Y et de toutes leurs dépendances qui soient compatibles entre elles.Avec
pip install X
suivi depip install Y
, la première commande installe seulement X avec ses dépendances, puis la deuxième commande ne prend pas en compte X, résout uniquement les dépendances de Y, et peut casser X en installant des dépendances incompatibles.C'est ce qui se passe dans l'exemple de la dépêche où
pip install myst-parser
suivi depip install mdformat-deflist
donne un environnement cassé, alors quepip install myst-parser mdformat-deflist
fonctionnerait.[^] # Re: pip install X; pip install Y vs pip install X Y
Posté par kortex . Évalué à 2. Dernière modification le 29 décembre 2023 à 17:45.
Merci beaucoup pour les précisions :)
# Au sujet de Poetry et Hatch
Posté par François (site web personnel) . Évalué à 4.
Bonjour et merci pour ce super article. Pour ma part j'utilise poetry avec beaucoup de joie depuis deux trois ans. Je n'ai pas utilisé hatch car j'en ai entendu parlé après et poetry remplissait toutes mes attentes. J'en profite donc pour moduler ce qui en est dit.
Poetry peut gérer des groupes de dépendances différents, typiquement pour la documentation ou les tests. Ce n'est donc pas vrai que l'on soit forcé d'avoir des dépendances compatible pour le développement du code et le doc etc…
https://python-poetry.org/docs/managing-dependencies/
Poetry a également un système de plugins, plutôt récent, que je n'ai pas testé. Il y a par exemple un système pour utiliser pyenv ce qui intègre la gestion de la version de python dans poetry si on le souhaite.
https://pypi.org/project/poetry-plugin-pyenv/
Sur la lenteur du solveur il semble que ce soit principalement du au téléchargement parfois nécessaire des paquets insuffisamment bien packagé sur pypi pour résoudre plus précisément le dag
https://python-poetry.org/docs/faq/#why-is-the-dependency-resolution-process-slow
En ce qui me concerne c'est le cas de matplotlib. Cela dit Poetry met en cache, les résolutions suivantes sont souvent bien plus rapide.
Enfin Poetry a un développement que je trouve stable, solide, tranquille qui respecte ses utilisateurs.
Avoir un lockfile me semble indispensable pour la reproductibilité. Sans avoir essayé hatch, je me demande pourquoi vouloir faire les fonctionnalités de type pipx ou fmt, déjà fait très bien par d'autres équipes.
[^] # Re: Au sujet de Poetry et Hatch
Posté par jeanas (site web personnel, Mastodon) . Évalué à 4. Dernière modification le 30 décembre 2023 à 00:12.
Bonjour,
Désolé de te contredire, mais c'est sur cette même page de la documentation Poetry que j'ai vérifié mes affirmations, et il est bien écrit explicitement : « All dependencies must be compatible with each other across groups since they will be resolved regardless of whether they are required for installation or not (see Installing group dependencies). »
Je viens de faire un test concret et je confirme ce que j'ai écrit dans la dépêche :
Pour la postérité, il est écrit « While the dependency resolver at the heart of Poetry is highly optimized and should be fast enough for most cases, with certain sets of dependencies it can take time to find a valid solution. This is due to the fact that not all libraries on PyPI have properly declared their metadata and, as such, they are not available via the PyPI JSON API. At this point, Poetry has no choice but to download the packages and inspect them to get the necessary information. This is an expensive operation, both in bandwidth and time, which is why it seems this is a long process. »
En dehors du fait que je ne comprends pas ce qu'ils entendent précisément par « have not properly declared their metadata », j'ai l'impression que ce n'est plus vrai depuis 6 mois, maintenant que PyPI expose les métadonnées des sdists et wheels de manière indépendante de leur téléchargement (PEP 658). pip en tire parti, par contre apparemment Poetry ne va pas le faire. Mais j'ai peut-être mal compris (je regarderai demain un peu plus en détail).
Je crois qu'il y a eu quelques controverses à ce sujet (ici par exemple). Après, c'est très subjectif, mon propos n'est pas de donner une opinion là-dessus. Si tu es content de Poetry, tant mieux.
[^] # Re: Au sujet de Poetry et Hatch
Posté par François (site web personnel) . Évalué à 4. Dernière modification le 30 décembre 2023 à 12:03.
Ah oui autant pour moi :) C'est dommage d'imposer la compatibilité entre groupe, espérons qu'ils changent ça à l'avenir. J'ai un usage à petite échelle, je suis assez insensible à tous ça… Merci pour les précisions !
# A propos de Conda ...
Posté par kif . Évalué à 2.
Salut,
Super article, on attend le tome 3 avec impatience …
Concernant la résolution des dépendances, il faut citer le projet
mamba
qui est une version deconda
avec le même solveur ré-écrit en C(++?) et parallèlisé. Ça s'utilise tout pareil queconda
mais c'est au moins 3x plus rapide. Pour ceux qui ne veulent pas changer du tout, on peut même utiliserconda
avec le solveur demamba
…Meilleurs vœux pour 2024
Jérôme
[^] # Re: A propos de Conda ...
Posté par jeanas (site web personnel, Mastodon) . Évalué à 2.
Oui, c'est vrai que mamba est normalement meilleur. Après, dans le cas que j'ai cité dans la dépêche (l'environnement avec poppler-qt5 et autres), je l'ai essayé et il gagnait environ 30 secondes sur les 7 minutes. Mais bon, je ne vais pas faire des généralités, je ne l'ai testé que sur une seule résolution.
[^] # Re: A propos de Conda ...
Posté par abriotde (site web personnel, Mastodon) . Évalué à 1.
Sans doute parce que comme tu dis, il doit explorer un grand arbre : la perf dépends pour parti du réseau. Pire, il ne peut pas récupérer tout en une fois il ne peut descendre que d'une génération à chaque passe.
Sous licence Creative common. Lisez, copiez, modifiez faites en ce que vous voulez.
# Distribution centralisée
Posté par ocroquette . Évalué à 5. Dernière modification le 22 janvier 2024 à 17:52.
Au boulot, on maintient notre propre distribution Python. Elle contient tous les paquets utilisés couramment par les différentes équipent. En développement, elle déployée dans le répertoire utilisateur, en production, dans un répertoire système. Déployer une application devient très facile, c’est juste le code et les données. Il y a 1-2 nouvelles versions de la distribution par an, avec tous les paquets sont mis à jour et de temps en temps des nouveaux paquets.
Les avantages:
Les inconvénients;
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.