Que sont les roues Python et pourquoi devriez-vous vous en soucier? – Vrai Python

By | août 5, 2020

trouver un expert Python

Python .whl Les fichiers, ou roues, sont une partie peu discutée de Python, mais ils ont été une aubaine pour le processus d'installation des packages Python. Si vous avez installé un package Python à l'aide de pépin, alors il y a de fortes chances qu'une roue ait rendu l'installation plus rapide et plus efficace.

Les roues sont un composant de l'écosystème Python qui aide à effectuer des installations de packages travaille juste. Ils permettent des installations plus rapides et plus de stabilité dans le processus de distribution des packages. Dans ce didacticiel, vous découvrirez ce que sont les roues, à quoi elles servent et comment elles ont gagné en traction et ont rendu Python encore plus agréable à utiliser.

Vous verrez des exemples utilisant des packages Python open source populaires du point de vue de l'utilisateur et du développeur.

Installer

Pour suivre, activez un environnement virtuel et assurez-vous que vous disposez des dernières versions de pépin, roue, et setuptools installée:

$ python -m venv env && la source ./env/bin/activate
$ python -m pip install -U pip wheel setuptools
Installation réussie du pip 20.1 setuptools-46.1.3 wheel-0.34.2

C'est tout ce dont vous avez besoin pour expérimenter l'installation et la construction de roues!

L'emballage Python amélioré: une introduction aux roues Python

Avant d'apprendre à regrouper un projet dans une roue, il est utile de savoir à quoi ressemble son utilisation du côté de l'utilisateur. Cela peut sembler arriéré, mais un bon moyen d'apprendre comment fonctionnent les roues est de commencer par installer quelque chose qui n'est pas une roue.

Vous pouvez démarrer cette expérience en installant un package Python dans votre environnement comme vous le feriez normalement. Dans ce cas, installez uWSGI version 2.0.x:

    1 $ python -m pip installer «uwsgi == 2.0. *»
    2 Collecter uwsgi == 2.0. *
    3         Téléchargement de uwsgi-2.0.18.tar.gz (801 kB)
    4                     | ████████████████████████████████ | 801 Ko 1,1 Mo / s
    5 Construire des roues pour les paquets collectés: uwsgi
    6         Roue de construction pour uwsgi (setup.py) ... fait
    7         Roue créée pour uwsgi ... uWSGI-2.0.18-cp38-cp38-macosx_10_15_x86_64.whl
    8         Stocké dans le répertoire: / private / var / folders / jc / 8_hqsz0x1tdbp05 ...
    9 Uwsgi construit avec succès
dix Installation des paquets collectés: uwsgi
11 Uwsgi-2.0.18 installé avec succès

Pour installer complètement uWSGI, pépin progresse en plusieurs étapes distinctes:

  1. Sur ligne 3, il télécharge un fichier TAR (tarball) nommé uwsgi-2.0.18.tar.gz qui a été compressé avec gzip.
  2. Sur ligne 6, il prend l'archive tar et construit un .whl déposer via un appel à setup.py.
  3. Sur ligne 7, il étiquette la roue uWSGI-2.0.18-cp38-cp38-macosx_10_15_x86_64.whl.
  4. Sur ligne 10, il installe le package réel après avoir construit la roue.

le tar.gz tarball que pépin récupère est un distribution source, ou sdist, plutôt qu'une roue. À certains égards, un sdist est l'opposé d'une roue.

Une distribution source contient du code source. Cela inclut non seulement le code Python, mais également le code source de tous les modules d'extension (généralement en C ou C ++) fournis avec le package. Avec les distributions sources, les modules d'extension sont compilés du côté de l'utilisateur plutôt que du côté du développeur.

Les distributions sources contiennent également un ensemble de métadonnées situées dans un répertoire appelé .egg-info. Ces métadonnées aident à créer et à installer le package, mais l’utilisateur n’a pas vraiment besoin d’en faire quoi que ce soit.

Du point de vue du développeur, une distribution source est ce qui est créé lorsque vous exécutez la commande suivante:

Maintenant, essayez d'installer un autre package, chardet:

    1 $ python -m pip installer 'chardet == 3. *'
    2 Collecte de chardet
    3         Téléchargement de chardet-3.0.4-py2.py3-none-any.whl (133 kB)
    4                     | ████████████████████████████████ | 133 Ko 1,5 Mo / s
    5 Installation des paquets collectés: chardet
    6 Chardet-3.0.4 installé avec succès

Vous pouvez voir une sortie sensiblement différente de celle de l'installation uWSGI.

L'installation de chardet télécharge un .whl fichier directement à partir de PyPI. Le nom de la roue chardet-3.0.4-py2.py3-none-any.whl suit une convention de dénomination spécifique que vous verrez plus tard. Le plus important du point de vue de l’utilisateur est qu’il n’y a pas d’étape de création lorsque pépin trouve une roue compatible sur PyPI.

Du côté du développeur, une roue est le résultat de l'exécution de la commande suivante:

$ python setup.py bdist_wheel

Pourquoi uWSGI vous donne-t-il une distribution source alors que chardet fournit une roue? Vous pouvez voir la raison en jetant un œil à la page de chaque projet sur PyPI et en accédant à la Telecharger des fichiers zone. Cette section vous montrera ce que pépin voit réellement sur le serveur d'index PyPI:

Un autre exemple du contrôle de compatibilité utilisé pour l'installation des roues est psycopg2, qui fournit un large éventail de roues pour Windows mais n'en fournit aucune pour les clients Linux ou macOS. Cela signifie que pip installer psycopg2 pourrait récupérer une roue ou une distribution source en fonction de votre configuration spécifique.

Pour éviter ces types de problèmes de compatibilité, certains packages offrent plusieurs roues, chaque roue étant orientée vers une implémentation Python spécifique et un système d'exploitation sous-jacent.

Jusqu'à présent, vous avez vu certaines des distinctions visibles entre une roue et sdist, mais ce qui compte le plus, c'est l'impact de ces différences sur le processus d'installation.

Les roues accélèrent les choses

Ci-dessus, vous avez vu une comparaison entre une installation qui récupère une roue pré-construite et une qui télécharge un sdist. Les roues accélèrent l'installation de bout en bout des packages Python pour deux raisons:

  1. Toutes choses étant égales par ailleurs, les roues sont généralement plus petite taille que les distributions source, ce qui signifie qu'elles peuvent se déplacer plus rapidement sur un réseau.
  2. L'installation à partir de roues évite directement l'étape intermédiaire de bâtiment paquets hors de la distribution source.

Il est presque garanti que l'installation de chardet a eu lieu en une fraction du temps requis pour uWSGI. Cependant, c'est sans doute une comparaison injuste de pommes à oranges, car le chardet est un emballage beaucoup plus petit et moins complexe. Avec une commande différente, vous pouvez créer une comparaison plus directe qui montrera à quel point les roues font la différence.

Tu peux faire pépin ignorer son inclinaison vers les roues en passant le --no-binaire option:

$ temps python -m pip installer 
      --no-cache-dir 
      --force-réinstaller 
                        --no-binaire=:tout: 
                        cryptographie

Cette commande chronomètre l'installation du cryptographie paquet, dire pépin pour utiliser une distribution source même si une roue appropriée est disponible. Comprenant :tout: fait que la règle s'applique à cryptographie et toutes ses dépendances.

Sur ma machine, cela prend environ trente-deux secondes du début à la fin. Non seulement l'installation prend du temps, mais la construction cryptographie nécessite également que vous ayez les en-têtes de développement OpenSSL présents et disponibles pour Python.

Vous pouvez maintenant réinstaller cryptographie, mais cette fois, assurez-vous que pépin utilise des roues de PyPI. Car pépin préférera une roue, cela revient à appeler installation de pip sans aucun argument. Mais dans ce cas, vous pouvez rendre l'intention explicite en exigeant une roue avec --uniquement-binaire:

$ temps python -m pip installer 
      --no-cache-dir 
      --force-réinstaller 
                        --uniquement-binaire=cryptographie 
                        cryptographie

Cette option prend un peu plus de quatre secondes, ou un huit le temps qu'il a fallu pour n'utiliser que des distributions source pour cryptographie et ses dépendances.

Qu'est-ce qu'une roue Python?

Un python .whl le fichier est essentiellement un ZIP (.Zip *: français) avec un nom de fichier spécialement conçu qui indique aux installateurs quelles versions et plates-formes Python la roue prendra en charge.

Une roue est un type de distribution construite. Dans ce cas, construit signifie que la roue est livrée dans un format prêt à installer et vous permet de sauter l'étape de construction requise avec les distributions source.

Un nom de fichier de roue est divisé en parties séparées par des tirets:

dist - version (- build)? - python - abi - plateforme .whl

Chaque section de supports est un marque, ou un composant du nom de la roue qui a une signification sur ce que contient la roue et où la roue fonctionnera ou ne fonctionnera pas.

Voici un exemple illustratif utilisant un cryptographie roue:

cryptographie-2.9.2-cp35-abi3-macosx_10_9_x86_64.whl

cryptographie distribue plusieurs roues. Chaque roue est un roue de plate-forme, ce qui signifie qu'il ne prend en charge que des combinaisons spécifiques de versions Python, d'ABI Python, de systèmes d'exploitation et d'architectures de machine. Vous pouvez décomposer la convention de dénomination en plusieurs parties:

  • cryptographie est le nom du package.

  • 2.9.2 est la version du package de cryptographie. Une version est une chaîne conforme à PEP 440 telle que 2.9.2, 3.4, ou 3.9.0.a3.

  • cp35 est la balise Python et désigne l'implémentation et la version Python demandées par la roue. le cp représente CPython, l'implémentation de référence de Python, tandis que le 35 désigne Python 3.5. Cette roue ne serait pas compatible avec Jython, par exemple.

  • abi3 est la balise ABI. ABI signifie interface binaire d'application. Vous n'avez pas vraiment besoin de vous soucier de ce que cela implique, mais abi3 est une version distincte pour la compatibilité binaire de l'API C Python.

  • macosx_10_9_x86_64 est la balise de plate-forme, qui se trouve être une bouchée assez. Dans ce cas, il peut être décomposé en sous-parties:

    • Mac OS X est le système d'exploitation macOS.
    • 10_9 est la version du SDK des outils de développement macOS utilisée pour compiler le Python qui a à son tour construit cette roue.
    • x86_64 est une référence à l'architecture du jeu d'instructions x86-64.

Le composant final n’est pas techniquement un tag mais plutôt le standard .whl extension de fichier. Combinés, les composants ci-dessus indiquent à la machine cible que ce cryptographie la roue est conçue pour.

Passons maintenant à un autre exemple. Voici ce que vous avez vu dans le cas ci-dessus pour le chardet:

chardet-3.0.4-py2.py3-none-any.whl

Vous pouvez le décomposer en ses balises:

  • chardet est le nom du package.
  • 3.0.4 est la version du package de chardet.
  • py2.py3 est la balise Python, ce qui signifie que la roue prend en charge Python 2 et 3 avec toute implémentation Python.
  • aucun est la balise ABI, ce qui signifie que l'ABI n'est pas un facteur.
  • tout est la plate-forme. Cette roue fonctionnera sur pratiquement toutes les plates-formes.

le py2.py3-none-any.whl segment du nom de la roue est commun. C'est un roue universelle qui s'installera avec Python 2 ou 3 sur n'importe quelle plate-forme avec n'importe quel ABI. Si la roue se termine par none-any.whl, alors il s'agit très probablement d'un package purement Python qui ne se soucie pas d'une architecture ABI ou CPU Python spécifique.

Un autre exemple est le jinja2 moteur de création de modèles. Si vous accédez à la page de téléchargement de la version alpha de Jinja 3.x, vous verrez la roue suivante:

Jinja2-3.0.0a1-py3-none-any.whl

Remarquez le manque de py2 ici. Il s’agit d’un projet purement Python qui fonctionnera sur n’importe quelle version de Python 3.x, mais ce n’est pas une roue universelle car il ne prend pas en charge Python 2. Au lieu de cela, il s’appelle un roue pure-Python.

Voici quelques exemples supplémentaires de .whl noms distribués pour certains packages open source populaires:

Roue Ce que c'est
PyYAML-5.3.1-cp38-cp38-win_amd64.whl PyYAML pour CPython 3.8 sous Windows avec architecture AMD64 (x86-64)
numpy-1.18.4-cp38-cp38-win32.whl NumPy pour CPython 3.8 sous Windows 32 bits
scipy-1.4.1-cp36-cp36m-macosx_10_6_intel.whl SciPy pour CPython 3.6 sur le SDK macOS 10.6 avec fat binaire (jeux d'instructions multiples)

Maintenant que vous avez une compréhension approfondie de ce que sont les roues, il est temps de parler de leur utilité.

Avantages des roues Python

Voici un témoignage des roues de la Python Packaging Authority (PyPA):

Tous les développeurs n'ont pas les bons outils ou expériences pour créer ces composants écrits dans ces langages compilés, c'est pourquoi Python a créé la roue, un format de package conçu pour expédier des bibliothèques avec des artefacts compilés. En fait, le programme d'installation du package de Python, pépin, préfère toujours les roues car l'installation est toujours plus rapide, de sorte que même les packages purement Python fonctionnent mieux avec les roues. (La source)

Une description plus complète est que les roues profitent à la fois aux utilisateurs et aux responsables des packages Python de plusieurs manières:

  • Les roues s'installent plus rapidement que les distributions source pour les packages Python pur et les modules d'extension.

  • Les roues sont plus petites que les distributions source. Par exemple, le six wheel représente environ un tiers de la taille de la distribution source correspondante. Ce différentiel devient encore plus important si l'on considère qu'un installation de pip pour un seul paquet peut en fait lancer le téléchargement d'une chaîne de dépendances.

  • Roues coupées setup.py exécution hors de l'équation. L'installation à partir d'une distribution source s'exécute peu importe est contenu dans ce projet setup.py. Comme indiqué par PEP 427, cela équivaut à une exécution de code arbitraire. Les roues évitent complètement cela.

  • Il n’ya pas besoin de compilateur pour installer des roues contenant des modules d'extension compilés. Le module d'extension est inclus avec la roue ciblant une plate-forme et une version Python spécifiques.

  • pépin génère automatiquement .pyc des dossiers dans la roue qui correspondent au bon interpréteur Python.

  • Les roues assurent la cohérence en supprimant de l'équation de nombreuses variables impliquées dans l'installation d'un package.

Vous pouvez utiliser un projet Telecharger des fichiers sur PyPI pour afficher les différentes distributions disponibles. Par exemple, les pandas distribuent un large éventail de roues.

Récit pépin Que télécharger

Il est possible d’exercer un contrôle précis sur pépin et dites-lui quel format préférer ou éviter. Vous pouvez utiliser le --uniquement-binaire et --no-binaire options pour le faire. Vous avez vu ceux-ci utilisés dans une section précédente sur l'installation du cryptographie package, mais cela vaut la peine de regarder de plus près ce qu'ils font:

$ pushd "$ (mktemp -d)"
$ python -m pip télécharger --only-binary: all: --dest. --no-cache six
Collectionner six
        Téléchargement de six-1.14.0-py2.py3-none-any.whl (10 kB)
        Enregistré ./six-1.14.0-py2.py3-none-any.whl
Téléchargement réussi de six

Dans cet exemple, vous passez à un répertoire temporaire pour stocker le téléchargement avec pushd "$ (mktemp -d)". Tu utilises télécharger pip plutôt que installation de pip afin que vous puissiez inspecter la roue résultante, mais vous pouvez remplacer Télécharger avec installer tout en conservant le même ensemble d'options.

Vous téléchargez le six module avec plusieurs drapeaux:

  • --only-binary: tous: raconte pépin pour se contraindre à utiliser des roues et ignorer les distributions source. Sans cette option, pépin seront seulement préférer roues, mais reviendra aux distributions source dans certains scénarios.
  • --dest. raconte pépin Télécharger six dans le répertoire courant.
  • --no-cache raconte pépin ne pas chercher dans son cache de téléchargement local. Vous utilisez cette option uniquement pour illustrer un téléchargement en direct depuis PyPI, car il est probable que vous ayez un six cache quelque part.

J'ai mentionné plus tôt qu'un fichier de roue est essentiellement un .Zip *: français archiver. Vous pouvez prendre cette déclaration à la lettre et traiter les roues comme telles. Par exemple, si vous souhaitez afficher le contenu d’une roue, vous pouvez utiliser décompresser:

$ décompressez -l six * .whl
Archive: six-1.14.0-py2.py3-none-any.whl
        Longueur Date Heure Nom
--------- ---------- ----- ----
                34074 15/01/2020 18:10 six.py
                    1066 15/01/2020 18:10 six-1.14.0.dist-info / LICENCE
                    1795 15/01/2020 18:10 six-1.14.0.dist-info / METADATA
                        110 15/01/2020 18:10 six-1.14.0.dist-info / ROUE
                                4 15/01/2020 18:10 six-1.14.0.dist-info / top_level.txt
                        435 15/01/2020 18:10 six-1.14.0.dist-info / RECORD
--------- -------
                37484 6 fichiers

six est un cas particulier: il s'agit en fait d'un seul module Python plutôt que d'un package complet. Les fichiers Wheel peuvent également être beaucoup plus complexes, comme vous le verrez plus tard.

Contrairement à --uniquement-binaire, vous pouvez utiliser --no-binaire pour faire le contraire:

$ python -m pip télécharger --no-binary: all: --dest. --no-cache six
Collectionner six
        Téléchargement de six-1.14.0.tar.gz (33 kB)
        Enregistré ./six-1.14.0.tar.gz
Téléchargement réussi de six
$ popd

Le seul changement dans cet exemple est le passage à --no-binaire: tout:. Cela raconte pépin pour ignorer les roues même si elles sont disponibles et télécharger à la place une distribution source.

Quand pourrait --no-binaire sois utile? Voici quelques cas:

  • La roue correspondante est cassée. C'est une ironie des roues. Ils sont conçus pour que les choses se cassent moins souvent, mais dans certains cas, une roue peut être mal configurée. Dans ce cas, le téléchargement et la création de la distribution source pour vous-même peuvent être une alternative efficace.

  • Vous souhaitez appliquer un petit fichier de modification ou de correctif au projet, puis installez-le. Il s'agit d'une alternative au clonage du projet à partir de l'URL de son système de contrôle de version.

Vous pouvez également utiliser les indicateurs décrits ci-dessus avec installation de pip. De plus, au lieu de :tout:, qui appliquera le --uniquement-binaire règle non seulement au package que vous installez mais à toutes ses dépendances, vous pouvez transmettre --uniquement-binaire et --no-binaire une liste de packages spécifiques auxquels appliquer cette règle.

Voici quelques exemples d'installation de la bibliothèque d'URL hurler. Il contient du code Cython et dépend de multidict, qui contient du code C pur. Il existe plusieurs options pour utiliser strictement ou ignorer strictement les roues pour hurler et ses dépendances:

$ # Installez `yarl` et n'utilisez que des roues pour yarl et toutes les dépendances
$ python -m pip install --only-binary: all: yarl

$ # Installez `yarl` et n'utilisez les roues que pour la dépendance` multidict`
$ python -m pip install --only-binary multidict yarl

$ # Installez `yarl` et n'utilisez pas de roues pour yarl ou pour toute dépendance
$ python -m pip install --no-binary: all: yarl

$ # Installez `yarl` et n'utilisez pas de roues pour la dépendance` multidict`
$ python -m pip install --no-binary multidict yarl

Dans cette section, vous avez un aperçu de la façon d'affiner les types de distribution qui installation de pip utilisera. Alors qu'un régulier installation de pip ne devrait fonctionner sans aucune option, il est utile de connaître ces options pour des cas particuliers.

le manylinux Étiquette de roue

Linux est disponible en de nombreuses variantes et saveurs, telles que Debian, CentOS, Fedora et Pacman. Chacun de ceux-ci peut utiliser de légères variations dans les bibliothèques partagées, telles que libncurseset les bibliothèques C principales, telles que glibc.

Si vous écrivez une extension C / C ++, cela peut créer un problème. Un fichier source écrit en C et compilé sous Ubuntu Linux n'est pas garanti pour être exécutable sur une machine CentOS ou une distribution Arch Linux. Avez-vous besoin de créer une roue séparée pour chaque variante Linux?

Heureusement, la réponse est non, grâce à un ensemble de balises spécialement conçu appelé le manylinux famille de balises de plate-forme. Il existe actuellement trois variantes:

  1. manylinux1 est le format d'origine spécifié dans PEP 513.

  2. manylinux2010 est une mise à jour spécifiée dans PEP 571 qui met à niveau vers CentOS 6 en tant que système d'exploitation sous-jacent sur lequel les images Docker sont basées. La raison en est que CentOS 5.11, qui est l'endroit où la liste des bibliothèques autorisées dans manylinux1 vient de, a atteint EOL en mars 2017 et a cessé de recevoir des correctifs de sécurité et des corrections de bogues.

  3. manylinux2014 est une mise à jour spécifiée dans PEP 599 qui met à niveau vers CentOS 7 puisque CentOS 6 devrait atteindre EOL en novembre 2020.

Vous pouvez trouver un exemple de manylinux distributions au sein du projet pandas. Voici deux (sur beaucoup) de la liste des téléchargements de pandas disponibles de PyPI:

pandas-1.0.3-cp37-cp37m-manylinux1_x86_64.whl
pandas-1.0.3-cp37-cp37m-manylinux1_i686.whl

Dans ce cas, les pandas ont construit manylinux1 roues pour CPython 3.7 prenant en charge les architectures x86-64 et i686.

En son coeur, manylinux est une image Docker construite à partir d'une certaine version du système d'exploitation CentOS. Il est livré avec une suite de compilateurs, plusieurs versions de Python et pépinet un ensemble autorisé de bibliothèques partagées.

À la mi-2020, manylinux1 est toujours le prédominant manylinux marque. Une des raisons à cela pourrait bien être une habitude. Un autre pourrait être que la prise en charge côté client (utilisateur) pour manylinux2010 et ci-dessus est limité aux versions plus récentes de pépin:

Marque Exigence
manylinux1 pépin 8.1.0 ou version ultérieure
manylinux2010 pépin 19.0 ou version ultérieure
manylinux2014 pépin 19.3 ou version ultérieure

En d'autres termes, si vous êtes un développeur de packages qui crée manylinux2010 roues, alors quelqu'un utilisant votre colis aura besoin pépin 19.0 (publié en janvier 2019) ou version ultérieure pour laisser pépin trouver et installer manylinux2010 roues de PyPI.

Heureusement, les environnements virtuels sont devenus plus courants, ce qui signifie que les développeurs peuvent mettre à jour un environnement virtuel pépin sans toucher au système pépin. Ce n’est pas toujours le cas, cependant, et certaines distributions Linux sont toujours livrées avec des versions obsolètes de pépin.

Tout cela pour dire, si vous installez des packages Python sur un hôte Linux, alors considérez-vous chanceux si le responsable du package a fait tout son possible pour créer manylinux roues. Cela garantira presque une installation sans tracas du package, quelle que soit votre variante ou version Linux spécifique.

Considérations de sécurité avec les roues de plate-forme

Une caractéristique des roues qui mérite d'être prise en compte du point de vue de la sécurité de l'utilisateur est que les roues sont potentiellement sujettes à la pourriture de version car elles regroupent une dépendance binaire plutôt que de permettre à cette dépendance d'être mise à jour par votre gestionnaire de packages système.

Par exemple, si une roue incorpore le libfortran bibliothèque partagée, les distributions de cette roue utiliseront le libfortran version avec laquelle ils ont été fournis même si vous mettez à niveau la version de votre propre ordinateur libfortran avec un gestionnaire de packages tel que apte, Miam, ou brasser.

Si vous développez dans un environnement avec des précautions de sécurité accrues, cette fonctionnalité de certaines roues de plate-forme doit être prise en compte.

Appel à tous les développeurs: créez vos roues

Le titre de ce didacticiel demande "Pourquoi devriez-vous vous en soucier?" En tant que développeur, si vous prévoyez de distribuer un package Python à la communauté, vous devez vous soucier énormément de la distribution de roues pour votre projet, car elles rendent le processus d'installation plus propre et moins complexe pour les utilisateurs finaux.

Plus vous pouvez prendre en charge de plates-formes cibles avec des roues compatibles, moins vous verrez de problèmes GitHub intitulés "Installation interrompue sur la plate-forme XYZ". La distribution de roues pour votre package Python rend objectivement moins probable que les utilisateurs du package rencontrent des problèmes lors de l'installation.

La première chose à faire pour construire une roue localement est d'installer roue. Cela ne fait pas de mal de s’assurer que setuptools est également à jour:

$ python -m pip install -U roue setuptools

Les prochaines sections vous guideront à travers la construction de roues pour une variété de scénarios différents.

Différents types de roues

Comme évoqué tout au long de ce didacticiel, il existe plusieurs variantes de roues, et le type de roue est reflété dans son nom de fichier:

  • UNE roue universelle contient py2.py3-none-any.whl. Il prend en charge à la fois Python 2 et Python 3 sur n'importe quel système d'exploitation et plateforme. La majorité des roues répertoriées sur le site Web de Python Wheels sont des roues universelles.

  • UNE roue pure-Python contient soit py3-aucun-aucun.whl ou py2.none-any.whl. Il prend en charge Python 3 ou Python 2, mais pas les deux. C’est par ailleurs la même chose qu’une roue universelle, mais elle sera étiquetée soit py2 ou py3 plûtot que le py2.py3 étiquette.

  • UNE roue de plate-forme prend en charge une version et une plate-forme Python spécifiques. Il contient des segments indiquant une version de Python, une ABI, un système d'exploitation ou une architecture spécifique.

Les différences entre les types de roues sont déterminées par la ou les versions de Python qu'elles prennent en charge et si elles ciblent une plate-forme spécifique. Voici un résumé condensé des différences entre les variantes de roues:

Type de roue Prend en charge Python 2 et 3 Prend en charge chaque ABI, OS et plate-forme
Universel
Pure-Python
Plate-forme

Comme vous le verrez ci-après, vous pouvez construire des roues universelles et des roues en Python pur avec relativement peu de configuration, mais les roues de plate-forme peuvent nécessiter quelques étapes supplémentaires.

Construire une roue Pure-Python

Vous pouvez créer une roue Python pure ou une roue universelle pour tout projet en utilisant setuptools avec une seule commande:

$ python setup.py sdist bdist_wheel

Cela créera à la fois une distribution source (sdist) et une roue (bdist_wheel). Par défaut, les deux seront placés dans dist / sous le répertoire courant. Pour voir par vous-même, vous pouvez créer une roue pour HTTPie, un client HTTP en ligne de commande écrit en Python, à côté d'un sdist.

Voici le résultat de la création des deux types de distributions pour le package HTTPie:

$ git clone -q git@github.com: jakubroztocil / httpie.git
$ CD httpie
$ python setup.py -q sdist bdist_wheel
$ ls -1 dist /
httpie-2.2.0.dev0-py3-none-any.whl
httpie-2.2.0.dev0.tar.gz

C’est tout ce qu’il faut. Vous clonez le projet, placez-vous dans son répertoire racine, puis appelez python setup.py sdist bdist_wheel. Tu peux voir ça dist / contient à la fois une roue et une distribution source.

Les distributions résultantes sont insérées dist / par défaut, mais vous pouvez changer cela avec le -ré/--dist-dir option. Vous pouvez les placer dans un répertoire temporaire à la place pour l'isolation de construction:

$ tempdir="$ (mktemp -d)"  # Créer un répertoire temporaire
$ fichier "$ tempdir"
/var/folders/jc/8_kd8uusys7ak09_lpmn30rw0000gk/T/tmp.GIXy7XKV: répertoire

$ python setup.py sdist -d "$ tempdir"
$ python setup.py bdist_wheel --dist-dir "$ tempdir"
$ ls -1 "$ tempdir"
httpie-2.2.0.dev0-py3-none-any.whl
httpie-2.2.0.dev0.tar.gz

Vous pouvez combiner les sdist et bdist_wheel en un car setup.py peut prendre plusieurs sous-commandes:

$ python setup.py sdist -d "$ tempdir" bdist_wheel -d "$ tempdir"

Comme indiqué ici, vous devrez passer des options telles que -ré à chaque sous-commande.

Spécification d'une roue universelle

Une roue universelle est une roue pour un projet pur-Python qui prend en charge à la fois Python 2 et 3. Il existe plusieurs façons de dire setuptools et distutils qu'une roue doit être universelle.

L'option 1 consiste à spécifier l'option dans votre projet setup.cfg fichier:

[bdist_wheel]
universel = 1

L'option 2 est de passer le bien nommé --universel drapeau sur la ligne de commande:

$ python setup.py bdist_wheel --universal

L'option 3 est de dire installer() lui-même sur le drapeau en utilisant son options paramètre:

# setup.py
de setuptools importer installer

installer(
    # ....
    options="bdist_wheel": "universel": Vrai
    # ....
)

Alors que l'une de ces trois options devrait fonctionner, les deux premières sont utilisées le plus fréquemment. Vous pouvez voir un exemple de ceci dans la configuration d'installation de chardet. Après cela, vous pouvez utiliser le bdist_wheel commande comme indiqué précédemment:

$ python setup.py sdist bdist_wheel

La roue résultante sera équivalente quelle que soit l'option choisie. Le choix dépend en grande partie des préférences du développeur et du flux de travail qui vous convient le mieux.

Construire une roue de plate-forme (macOS et Windows)

Distributions binaires sont un sous-ensemble de distributions construites qui contiennent des extensions compilées. Extensions sont des dépendances ou des composants non-Python de votre package Python.

Habituellement, cela signifie que votre package contient un module d'extension ou dépend d'une bibliothèque écrite dans un langage à typage statique tel que C, C ++, Fortran ou même Rust ou Go. Roues de plate-forme existent pour cibler des plates-formes individuelles principalement parce qu'elles contiennent ou dépendent de modules d'extension.

Cela dit, il est grand temps de construire une roue plate-forme!

En fonction de votre environnement de développement existant, vous devrez peut-être passer par une ou deux étapes préalables supplémentaires pour construire des roues de plate-forme. Les étapes ci-dessous vous aideront à vous préparer à la création de modules d'extension C et C ++, qui sont de loin les types les plus courants.

Sous macOS, vous aurez besoin des outils de développement en ligne de commande disponibles via xcode:

Sous Windows, vous devez installer Microsoft Visual C ++:

  1. Ouvrez la page de téléchargement de Visual Studio dans votre navigateur.
  2. Sélectionner Outils pour Visual Studio → Créer des outils pour Visual Studio → Télécharger.
  3. Exécutez le résultat .EXE installateur.
  4. Dans le programme d'installation, sélectionnez Outils de construction C ++ → Installer.
  5. Redémarrez votre machine.

Sous Linux, vous avez besoin d'un compilateur tel que gcc ou g ++/c ++.

Avec cela au carré, vous êtes prêt à construire une roue plate-forme pour UltraJSON (ujson), un encodeur et décodeur JSON écrit en C pur avec des liaisons Python 3. En utilisant ujson est un bon exemple de jouet car il couvre quelques bases:

  1. Il contient un module d'extension, ujson.
  2. Cela dépend des en-têtes de développement Python à compiler (#comprendre ) mais n'est pas autrement trop compliqué. ujson est conçu pour faire une chose et bien le faire, c'est-à-dire lire et écrire JSON!

Vous pouvez cloner le projet depuis GitHub, naviguer dans son répertoire et le construire:

$ git clone -q --branch 2.0.3 git@github.com: ultrajson / ultrajson.git
$ CD ultrajson
$ python setup.py bdist_wheel

Vous devriez voir beaucoup de sortie. Voici une version réduite sur macOS, où le pilote du compilateur Clang est utilisé:

clang -Waucun-résultat-inutilisé -Wsign-compare -Wunreachable-code -DNDEBUG -g ...
...
création de 'dist / ujson-2.0.3-cp38-cp38-macosx_10_15_x86_64.whl'
ajout de 'ujson.cpython-38-darwin.so'

Les lignes commençant par bruit affiche l'appel réel au compilateur avec une multitude d'indicateurs de compilation. Vous pouvez également voir des outils tels que MSVC (Windows) ou gcc (Linux) selon le système d'exploitation.

Si vous rencontrez un erreur fatale après avoir exécuté le code ci-dessus, ne vous inquiétez pas. Vous pouvez développer la zone ci-dessous pour savoir comment résoudre ce problème.

Ce setup.py bdist_wheel demander ujson nécessite les fichiers d'en-tête de développement Python car ujson.c tire . Si vous ne les avez pas dans un emplacement de recherche, vous pouvez voir une erreur comme celle-ci:

erreur fatale: fichier 'Python.h' introuvable
#comprendre 

Pour compiler des modules d'extension, vous aurez besoin des en-têtes de développement enregistrés à un endroit où votre compilateur pourra les trouver.

Si vous utilisez une version récente de Python 3 et un outil d'environnement virtuel tel que venv, alors il est probable que les en-têtes de développement Python soient inclus dans la compilation et la liaison par défaut.

Si ce n'est pas le cas, une erreur peut s'afficher, indiquant qu'un fichier d'en-tête est introuvable:

erreur fatale: fichier 'Python.h' introuvable
#comprendre 

Dans ce cas, vous pouvez dire setup.py où chercher les fichiers d'en-tête en définissant CFLAG. Pour trouver le fichier d'en-tête lui-même, vous pouvez utiliser python3-config:

$ python3-config --include
-I / Utilisateurs //.pyenv/versions/3.8.2/include/python3.8

Cela vous indique que les en-têtes de développement Python se trouvent dans le répertoire indiqué, que vous pouvez maintenant utiliser avec python setup.py bdist_wheel:

$ CFLAG="$ (python3-config --include)" python setup.py bdist_wheel

Plus généralement, vous pouvez passer n'importe quel chemin dont vous avez besoin:

$ CFLAG='-I / chemin / vers / inclure' python setup.py bdist_wheel

Sous Linux, vous devrez peut-être également installer les en-têtes séparément:

$ apt-get install -y python3-dev  # Debian, Ubuntu
$ yum install -y python3-devel  # CentOS, Fedora, RHEL

Si vous inspectez les UltraJSON setup.py, alors vous verrez qu'il personnalise certains indicateurs de compilateur tels que -D_GNU_SOURCE. Les subtilités du contrôle du processus de compilation via setup.py n'entrent pas dans le cadre de ce didacticiel, mais sachez qu'il est possible d'avoir un contrôle précis sur la façon dont la compilation et la liaison se produisent.

Si vous regardez dist, alors vous devriez voir la roue créée:

$ ls dist /
ujson-2.0.3-cp38-cp38-macosx_10_15_x86_64.whl

Notez que le nom peut varier en fonction de votre plate-forme. Par exemple, vous verriez win_amd64.whl sur Windows 64 bits.

Vous pouvez jeter un œil dans le fichier de roue et voir qu'il contient l'extension compilée:

$ décompressez -l dist / ujson - *. whl
...
        Longueur Date Heure Nom
--------- ---------- ----- ----
            105812 05-10-2020 19:47 ujson.cpython-38-darwin.so
            ...

Cet exemple montre une sortie pour macOS, ujson.cpython-38-darwin.so, qui est un objet partagé (.alors), également appelé bibliothèque dynamique.

Linux: Construction manylinux roues

En tant que développeur de packages, vous souhaiterez rarement créer des roues pour une seule variante Linux. Les roues Linux nécessitent un ensemble spécialisé de conventions et d'outils pour pouvoir fonctionner dans différents environnements Linux.

Contrairement aux roues pour macOS et Windows, les roues construites sur une variante Linux n'ont aucune garantie de fonctionner sur une autre variante Linux, même une avec la même architecture de machine. En fait, si vous construisez une roue sur un conteneur Linux prêt à l'emploi, PyPI n'acceptera même pas cette roue si vous essayez de la télécharger!

If you want your package to be available across a range of Linux clients, then you want a manylinux wheel. UNE manylinux wheel is a particular type of a platform wheel that is accepted by most Linux variants. It must be built in a specific environment, and it requires a tool called auditwheel that renames the wheel file to indicate that it’s a manylinux wheel.

Building a manylinux wheel allows you to target a wider range of user platforms. PEP 513 specifies a particular (and archaic) version of CentOS with an array of Python versions available. The choice between CentOS and Ubuntu or any other distribution doesn’t carry any special distinction. The point is for the build environment to consist of a stock Linux operating system with a limited set of external shared libraries that are common to different Linux variants.

Thankfully, you don’t have to do this yourself. PyPA provides a set of Docker images that give you this environment with a few mouse clicks:

  • Option 1 is to run docker from your development machine and mount your project using a Docker volume so that it’s is accessible in the container filesystem.
  • Option 2 is to use a CI/CD solution such as CircleCI, GitHub Actions, Azure DevOps, or Travis-CI, which will pull your project and run the build on an action such as a push or tag.

The Docker images are provided for the different manylinux flavors:

To get started, PyPA also provides an example repository, python-manylinux-demo, which is a demo project for building manylinux wheels in conjunction with Travis-CI.

While it’s common to build wheels as a part of a remote-hosted CI solution, you can also build manylinux wheels locally. To do so, you’ll need Docker installed. Docker Desktop is available for macOS, Windows, and Linux.

First, clone the demo project:

$ git clone -q git@github.com:pypa/python-manylinux-demo.git
$ CD python-manylinux-demo

Next, define a few shell variables for the manylinux1 Docker image and platform, respectively:

$ DOCKER_IMAGE='quay.io/pypa/manylinux1_x86_64'
$ PLAT='manylinux1_x86_64'

le DOCKER_IMAGE variable is the image maintained by PyPA for building manylinux wheels, hosted at Quay.io. The platform (PLAT) is a necessary piece of information to feed to auditwheel, letting it know what platform tag to apply.

Now you can pull the Docker image and run the wheel-builder script within the container:

$ docker pull "$DOCKER_IMAGE"
$ docker container run -t --rm 
      -e PLAT=$PLAT 
      -v "$(pwd)":/io 
      "$DOCKER_IMAGE" /io/travis/build-wheels.sh

This tells Docker to run the build-wheels.sh shell script inside the manylinux1_x86_64 Docker container, passing PLAT as an environment variable available in the container. Since you used -v (or --volume) to bind-mount a volume, the wheels produced in the container will now be accessible on your host machine in the wheelhouse directory:

$ ls -1 wheelhouse
python_manylinux_demo-1.0-cp27-cp27m-manylinux1_x86_64.whl
python_manylinux_demo-1.0-cp27-cp27mu-manylinux1_x86_64.whl
python_manylinux_demo-1.0-cp35-cp35m-manylinux1_x86_64.whl
python_manylinux_demo-1.0-cp36-cp36m-manylinux1_x86_64.whl
python_manylinux_demo-1.0-cp37-cp37m-manylinux1_x86_64.whl
python_manylinux_demo-1.0-cp38-cp38-manylinux1_x86_64.whl

In a few short commands, you have a set of manylinux1 wheels for CPython 2.7 through 3.8. A common practice is also to iterate over different architectures. For instance, you could repeat this process for the quay.io/pypa/manylinux1_i686 Docker image. This would build manylinux1 wheels targeting 32-bit (i686) architecture.

If you’d like to dive deeper into building wheels, then a good next step is to learn from the best. Start at the Python Wheels page, pick a project, navigate to its source code (on a place like GitHub, GitLab, or Bitbucket), and see for yourself how it builds wheels.

Many of the projects on the Python Wheels page are pure-Python projects and distribute universal wheels. If you’re looking for more complex cases, then keep an eye out for packages that use extension modules. Here are two examples to whet your appetite:

  1. lxml uses a separate build script that’s invoked from within the manylinux1 Docker container.
  2. ultrajson does the same and uses GitHub Actions to call the build script.

Both of these are reputable projects that offer a great examples to learn from if you’re interested in building manylinux wheels.

Bundling Shared Libraries

One other challenge is building wheels for packages that depend on external shared libraries. le manylinux images contain a prescreened set of libraries such as libpthread.so.0 et libc.so.6. But what if you rely on something outside that list, such as ATLAS or GFortran?

In that case, several solutions come to the rescue:

  • auditwheel will bundle external libraries into an already-built wheel.
  • delocate does the same on macOS.

Conveniently, auditwheel is present on the manylinux Docker images. En utilisant auditwheel et delocate takes just one command. Just tell them about the wheel file(s) and they’ll do the rest:

$ auditwheel repair   # For manylinux
$ delocate-wheel   # For macOS

This will detect the needed external libraries through your project’s setup.py and bundle them in to the wheel as if they were part of the project.

An example of a project that takes advantage of auditwheel et delocate est pycld3, which provides Python bindings for the Compact Language Detector v3 (CLD3).

le pycld3 package depends on libprotobuf, which is not a commonly installed library. If you peek inside a pycld3 macOS wheel, then you’ll see that libprotobuf.22.dylib is included there. This is a dynamically linked shared library that’s bundled into the wheel:

$ unzip -l pycld3-0.20-cp38-cp38-macosx_10_15_x86_64.whl
...
                            51  04-10-2020 11:46   cld3/__init__.py
            939984  04-10-2020 07:50   cld3/_cld3.cpython-38-darwin.so
        2375836  04-10-2020 07:50   cld3/.dylibs/libprotobuf.22.dylib
---------                     -------
        3339279                     8 files

The wheel comes prepackaged with libprotobuf. UNE .dylib is similar to a Unix .so file or Windows .dll file, but I admittedly don’t know the nitty-gritty of the difference beyond that.

auditwheel et delocate know to include libprotobuf because setup.py tells them to through the libraries argument:

installer(
    # ...
    libraries=[[[["protobuf"],
    # ...
)

This means that auditwheel et delocate save users the trouble of installing protobuf as long as they’re installing from a platform and Python combination that has a matching wheel.

If you’re distributing a package that has external dependencies like this, then you can do your users a favor by using auditwheel ou delocate to save them the extra step of installing the dependencies themselves.

Building Wheels in Continuous Integration

An alternative to building wheels on your local machine is to build them automatically within your project’s CI pipeline.

There are myriad CI solutions that integrate with the major code hosting services. Among them are Appveyor, Azure DevOps, BitBucket Pipelines, Circle CI, GitLab, GitHub Actions, Jenkins, and Travis CI, to name just a few.

The purpose of this tutorial isn’t to render a judgment as to which CI service is best for building wheels, and any listing of which CI services support which containers would quickly become outdated given the speed at which CI support is evolving. However, this section can help to get you started.

If you’re developing a pure-Python package, the bdist_wheel step is a blissful one-liner: it’s largely irrelevant which container OS and platform you build the wheel on. Virtually all major CI services should enable you to do this in a no-frills fashion by defining steps in a special YAML file within the project.

For example, here’s the syntax you could use with GitHub Actions:

    1 Nom: Python wheels
    2 sur:
    3   release:
    4     types:
    5       - établi
    6 jobs:
    7   wheels:
    8     runs-on: ubuntu-latest
    9     steps:
dix     - uses: actions/checkout@v2
11     - Nom: Set up Python 3.x
12       uses: actions/setup-python@v2
13       avec:
14         python-version: '3.x'
15     - Nom: Install dependencies
16       run: python -m pip install --upgrade setuptools wheel
17     - Nom: Build wheels
18       run: python setup.py bdist_wheel
19     - uses: actions/upload-artifact@v2
20       avec:
21         Nom: dist
22         path: dist

In this configuration file, you build a wheel using the following steps:

  1. Dans line 8, you specify that the job should run on an Ubuntu machine.
  2. Dans line 10, you use the checkout action to set up your project repository.
  3. Dans line 14, you tell the CI runner to use the latest stable version of Python 3.
  4. Dans line 21, you request that the resulting wheel be available as an artifact that you can download from the UI once the job completes.

However, if you have a complex project (maybe one with C extensions or Cython code) and you’re working to craft a CI/CD pipeline to automatically build wheels, then there will likely be additional steps involved. Here are a few projects through which you can learn by example:

Many projects roll their own CI configuration. However, some solutions have emerged for reducing the amount of code specified in configuration files to build wheels. You can use the cibuildwheel tool directly on your CI server to cut down on the lines of code and configuration that it takes to build multiple platform wheels. There’s also multibuild, which provides a set of shell scripts for assisting with building wheels on Travis CI and AppVeyor.

Making Sure Your Wheels Spin Right

Building wheels that are structured correctly can be a delicate operation. For instance, if your Python package uses a src layout and you forget to specify that properly in setup.py, then the resulting wheel might contain a directory in the wrong place.

One check that you can use after bdist_wheel is the check-wheel-contents outil. It looks for common problems such as the package directory having an abnormal structure or the presence of duplicate files:

$ check-wheel-contents dist/*.whl
dist/ujson-2.0.3-cp38-cp38-macosx_10_15_x86_64.whl: OK

In this case, check-wheel-contents indicates that everything with the ujson wheel checks out. If not, stdout will show a summary of possible issues much like a linter such as flake8.

Another way to confirm that the wheel you’ve built has the right stuff is to use TestPyPI. First, you can upload the package there:

$ python -m twine upload 
      --repository-url https://test.pypi.org/legacy/ 
      dist/*

Then, you can download the same package for testing as if it were the real thing:

$ python -m pip install 
      --index-url https://test.pypi.org/simple/ 
      

This allows you to test your wheel by uploading and then downloading your own project.

Uploading Python Wheels to PyPI

Now it’s time to upload your Python package. Since a sdist and a wheel both get put in the dist/ directory by default, you can upload them both using the twine tool, which is a utility for publishing packages to PyPI:

$ python -m pip install -U twine
$ python -m twine upload dist/*

Since both sdist et bdist_wheel output to dist/ by default, you can safely tell twine to upload everything under dist/ using a shell wildcard (dist/*).

Conclusion

Understanding the pivotal role that wheels play in the Python ecosystem can make your life easier as both a user and developer of Python packages. Furthermore, increasing your Python literacy when it comes to wheels will help you to better understand what’s happening when you install a package and when, in increasingly rare cases, that operation goes awry.

In this tutorial, you learned:

  • What wheels are and how they compare to source distributions
  • How you can use wheels to control the package installation process
  • What the differences are between universal, pure-Python, et Plate-forme wheels
  • How to create and distribute wheels for your own Python packages

You now have a solid understanding of wheels from both a user’s and a developer’s perspective. You’re well equipped to build you own wheels and make your project’s installation process quick, convenient, and stable.

See the section below for some additional reading to dive deeper into the rapidly-expanding wheel ecosystem.

Ressources

The Python Wheels page is dedicated to tracking support for wheels among the 360 most downloaded packages on PyPI. The adoption rate is pretty respectable at the time of this tutorial, at 331 out of 360, or around 91 percent.

There have been a number of Python Enhancement Proposals (PEPs) that have helped with the specification and evolution of the wheel format:

Here’s a shortlist of the various wheel packaging tools mentioned in this tutorial:

The Python documentation has several articles covering wheels and source distributions:

Finally, here are a few more useful links from PyPA: