Gestione delle dipendenze Python quale strumento scegliere?

Python dependency management which tool to choose?

Un confronto approfondito tra Poetry, Pip e Conda

Immagine di Autore

Pubblicato originariamente su https://mathdatasimplified.com il 13 giugno 2023.

Motivazione

Alla crescita del progetto di data science, aumentano anche il numero di dipendenze. Per mantenere l’ambiente del progetto riproducibile e mantenibile, è importante utilizzare uno strumento efficiente di gestione delle dipendenze.

Quindi, ho deciso di confrontare tre strumenti popolari per la gestione delle dipendenze: Pip, Conda e Poetry. Dopo una valutazione attenta, sono convinto che Poetry superi gli altri due opzioni in termini di efficienza e prestazioni.

In questo articolo, esamineremo i vantaggi di Poetry e evidenzieremo le sue principali differenze rispetto a Pip e Conda.

Pacchetti disponibili

Avere una vasta selezione di pacchetti semplifica la ricerca del pacchetto e della versione specifica che meglio si adatta alle proprie esigenze.

Conda

Alcuni pacchetti, come “snscrape”, non possono essere installati con Conda. Inoltre, alcune versioni, come Pandas 2.0, potrebbero non essere disponibili per l’installazione tramite Conda.

Anche se è possibile utilizzare pip all’interno di un ambiente virtuale Conda per affrontare le limitazioni dei pacchetti, Conda non può tenere traccia delle dipendenze installate con pip, rendendo difficile la gestione delle dipendenze.

$ conda list# pacchetti nell'ambiente presso /Users/khuyentran/miniconda3/envs/test-conda:## Nome                    Versione                   Build  Channel$ conda list # pacchetti nell'ambiente presso /Users/khuyentran/miniconda3/envs/test-conda: # # Nome Versione Build Channel

Pip

Pip può installare qualsiasi pacchetto dal Python Package Index (PyPI) e da altri repository.

Poetry

Poetry consente anche l’installazione di pacchetti dal Python Package Index (PyPI) e da altri repository.

Numero di dipendenze

Ridurre il numero di dipendenze in un ambiente semplifica il processo di sviluppo.

Conda

Conda fornisce un isolamento completo dell’ambiente, gestendo sia i pacchetti Python che le dipendenze a livello di sistema. Ciò può comportare dimensioni di pacchetto più grandi rispetto ad altri gestori di pacchetti, consumando potenzialmente più spazio di archiviazione durante l’installazione e la distribuzione.

$ conda install pandas$ conda list          # pacchetti nell'ambiente presso /Users/khuyentran/miniconda3/envs/test-conda:## Nome              Versione         Build           Channel             blas                1.0             openblas                          bottleneck          1.3.5           py311ha0d4635_0                    bzip2               1.0.8           h620ffc9_4                        ca-certificates     2023.05.30      hca03da5_0                        libcxx              14.0.6          h848a8c0_0                        libffi              3.4.4           hca03da5_0                        libgfortran         5.0.0           11_3_0_hca03da5_28                 libgfortran5        11.3.0          h009349e_28                       libopenblas         0.3.21          h269037a_0                        llvm-openmp         14.0.6          hc6e5704_0                        ncurses             6.4             h313beb8_0                        numexpr             2.8.4           py311h6dc990b_1                    numpy               1.24.3          py311hb57d4eb_0                    numpy-base          1.24.3          py311h1d85a46_0                    openssl             3.0.8           h1a28f6b_0                        pandas              1.5.3           py311h6956b77_0                    pip                 23.0.1          py311hca03da5_0                    python              3.11.3          hb885b13_1                        python-dateutil     2.8.2           pyhd3eb1b0_0                      pytz                2022.7          py311hca03da5_0                    readline            8.2             h1a28f6b_0                        setuptools          67.8.0          py311hca03da5_0                    six                 1.16.0          pyhd3eb1b0_1                      sqlite              3.41.2          h80987f9_0                        tk                  8.6.12          hb8d0fd4_0                        tzdata              2023c           h04d1e81_0                        wheel               0.38.4          py311hca03da5_0                    xz                  5.4.2           h80987f9_0                        zlib                1.2.13          h5a0b063_0      

Pip

Pip installa solo le dipendenze richieste da un pacchetto.

$ pip install pandas$ pip listPackage         Version--------------- -------numpy           1.24.3pandas          2.0.2pip             22.3.1python-dateutil 2.8.2pytz            2023.3setuptools      65.5.0six             1.16.0tzdata          2023.3

Poetry

Poetry installa anche solo le dipendenze richieste da un pacchetto.

$ poetry add pandas$ poetry shownumpy           1.24.3 Pacchetto fondamentale per il calcolo degli array in Pythonpandas          2.0.2  Potenti strutture dati per l'analisi dei dati, il tempo...python-dateutil 2.8.2  Estensioni al modulo datetime standard di Pythonpytz            2023.3 Definizioni dei fusi orari mondiali, moderni e storici six             1.16.0 Utilità di compatibilità Python 2 e 3tzdata          2023.3 Fornitore di dati sui fusi orari IANA

Disinstalla pacchetti

La disinstallazione dei pacchetti e delle loro dipendenze libera spazio su disco, previene l’accumulo di spazzatura superflua e ottimizza l’utilizzo delle risorse di archiviazione.

Pip

Pip rimuove solo il pacchetto specificato, non le sue dipendenze, il che può portare all’accumulo di dipendenze inutilizzate nel tempo. Ciò può comportare un aumento dell’utilizzo dello spazio di archiviazione e potenziali conflitti.

$ pip install pandas$ pip uninstall pandas$ pip listPackage         Version--------------- -------numpy           1.24.3pip             22.0.4python-dateutil 2.8.2pytz            2023.3setuptools      56.0.0six             1.16.0tzdata          2023.3

Conda

Conda rimuove il pacchetto e le sue dipendenze.

$ conda install -c conda pandas$ conda uninstall -c conda pandasRaccolta metadati del pacchetto (repodata.json): fattoRisoluzione dell'ambiente: fatto## Piano pacchetti ##  localizzazione dell'ambiente: /Users/khuyentran/miniconda3/envs/test-conda  specifiche rimosse:    - pandasI seguenti pacchetti saranno RIMOSSI:  blas-1.0-openblas  bottleneck-1.3.5-py311ha0d4635_0  libcxx-14.0.6-h848a8c0_0  libgfortran-5.0.0-11_3_0_hca03da5_28  libgfortran5-11.3.0-h009349e_28  libopenblas-0.3.21-h269037a_0  llvm-openmp-14.0.6-hc6e5704_0  numexpr-2.8.4-py311h6dc990b_1  numpy-1.24.3-py311hb57d4eb_0  numpy-base-1.24.3-py311h1d85a46_0  pandas-1.5.3-py311h6956b77_0  python-dateutil-2.8.2-pyhd3eb1b0_0  pytz-2022.7-py311hca03da5_0  six-1.16.0-pyhd3eb1b0_1Procedere ([sì]/no)? Preparazione della transazione: fattoVerifica della transazione: fattoEsecuzione della transazione: fattoPoetry

Poetry

Poetry rimuove anche il pacchetto e le sue dipendenze.

$ poetry add pandas$ poetry remove pandas  • Rimozione di numpy (1.24.3)  • Rimozione di pandas (2.0.2)  • Rimozione di python-dateutil (2.8.2)  • Rimozione di pytz (2023.3)  • Rimozione di six (1.16.0)  • Rimozione di tzdata (2023.3)

File di dipendenza

I file di dipendenza garantiscono la riproducibilità dell’ambiente del progetto software specificando le versioni esatte o gli intervalli di versioni dei pacchetti richiesti.

Ciò aiuta a ricreare lo stesso ambiente su diversi sistemi o in momenti diversi, garantendo la collaborazione tra sviluppatori con lo stesso set di dipendenze.

Conda

Per salvare le dipendenze in un ambiente Conda, è necessario scriverle manualmente in un file. Le versioni specificate in un file environment.yml possono comportare l’installazione di versioni diverse, introducendo potenzialmente problemi di compatibilità durante la riproduzione dell’ambiente.

Supponiamo di aver installato pandas versione 1.5.3 come esempio. Ecco un file environment.yml che specifica le dipendenze:

# environment.ymlname: test-condachannels:  - defaultsdependencies:  - python=3.8  - pandas>=1.5

Se un nuovo utente cerca di riprodurre l’ambiente quando l’ultima versione di pandas è 2.0, verrà invece installato pandas 2.0.

# Crea e attiva un ambiente virtuale$ conda env create -n env$ conda activate env# Elenco dei pacchetti nell'ambiente corrente$ conda list...pandas                   2.0

Se il codice dipende dalla sintassi o dal comportamento specifico di pandas versione 1.5.3 e la sintassi è cambiata nella versione 2.0, l’esecuzione del codice con pandas 2.0 potrebbe introdurre bug.

Pip

Lo stesso problema può verificarsi con pip.

# requirements.txt pandas>=1.5

# Crea e attiva un ambiente virtuale$ python3 -m venv venv$ source venv/bin/activate# Installa le dipendenze$ pip install -r requirements.txt# Elenco dei pacchetti$ pip listPackage    Version---------- -------pandas       2.0...

Puoi fissare la versione bloccandola in un file requirements.txt:

$ pip freeze > requirements.txt

# requirements.txtnumpy==1.24.3pandas==1.5.3python-dateutil==2.8.2pytz==2023.3six==1.16.0

Tuttavia, questo rende l’ambiente di codice meno flessibile e potenzialmente più difficile da mantenere nel lungo periodo. Qualsiasi modifica alle dipendenze richiederebbe modifiche manuali al file requirements.txt, il che può essere lungo e soggetto a errori.

Poetry

Poetry aggiorna automaticamente il file pyproject.toml durante l’installazione di un pacchetto.

Nell’esempio seguente, il pacchetto “pandas” viene aggiunto con il vincolo di versione ^1.5. Questo approccio di versioning flessibile garantisce che il tuo progetto possa adattarsi alle nuove versioni senza aggiustamenti manuali.

$ poetry add 'pandas=^1.5'

# pyproject.toml[tool.poetry.dependencies]python = "^3.8"pandas = "^1.5"

Il file poetry.lock memorizza i numeri di versione precisi per ogni pacchetto e le sue dipendenze.

# poetry.lock...[[package]]name = "pandas"version = "1.5.3"description = "Powerful data structures for data analysis, time series, and statistics"category = "main"optional = falsepython-versions = ">=3.8"[package.dependencies]numpy = [    {version = ">=1.20.3", markers = "python_version < \"3.10\""},    {version = ">=1.21.0", markers = "python_version >= \"3.10\""},    {version = ">=1.23.2", markers = "python_version >= \"3.11\""},]python-dateutil = ">=2.8.2"pytz = ">=2020.1"tzdata = ">=2022.1"...

Ciò garantisce coerenza nei pacchetti installati, anche se un pacchetto ha un intervallo di versioni specificato nel file pyproject.toml. Qui, possiamo vedere che pandas 1.5.3 è installato invece di pandas 2.0

$ poetry install$ poetry show pandasname         : pandas                                                                  version      : 1.5.3                                                                   description  : Powerful data structures for data analysis, time series, and statistics dependencies - numpy >=1.20.3 - numpy >=1.21.0 - numpy >=1.23.2 - python-dateutil >=2.8.1 - pytz >=2020.1

Separare le dipendenze per dev e prod

Separando le dipendenze, puoi distinguere chiaramente tra i pacchetti richiesti per scopi di sviluppo, come i framework di testing e gli strumenti di qualità del codice, dai pacchetti necessari per l’ambiente di produzione, che includono tipicamente le dipendenze principali.

Conda

Conda non supporta intrinsecamente dipendenze separate per diversi ambienti, ma un workaround prevede la creazione di due file di ambiente: uno per l’ambiente di sviluppo e uno per la produzione. Il file di sviluppo contiene sia le dipendenze di produzione che quelle di sviluppo.

# environment.ymlname: test-condachannels:  - defaultsdependencies:  # Pacchetti di produzione  - numpy  - pandas

# environment-dev.ymlname: test-conda-devchannels:  - defaultsdependencies:  # Pacchetti di produzione  - numpy  - pandas  # Pacchetti di sviluppo  - pytest  - pre-commit

Pip

Anche Pip non supporta direttamente dipendenze separate, ma si può utilizzare un approccio simile con file di requisiti separati.

# requirements.txtnumpy pandas

# requirements-dev.txt-r requirements.txtpytestpre-commit

# Installa solo i pacchetti di produzione$ pip install -r requirements.txt# Installa sia i pacchetti di sviluppo che di produzione$ pip install -r requirements-dev.txt

Poetry

Poetry semplifica la gestione delle dipendenze supportando gruppi all’interno di un unico file. Ciò ti consente di tenere traccia di tutte le dipendenze in un unico luogo.

$ poetry add numpy pandas$ poetry add --group dev pytest pre-commit

# pyproject.toml[tool.poetry.dependencies]python = "^3.8"pandas = "^2.0"numpy = "^1.24.3"[tool.poetry.group.dev.dependencies]pytest = "^7.3.2"pre-commit = "^3.3.2"

Per installare solo le dipendenze di produzione:

$ poetry install --only main

Per installare sia le dipendenze di sviluppo che di produzione:

$ poetry install

Aggiorna un Ambiente

L’aggiornamento delle dipendenze è essenziale per beneficiare di correzioni di bug, miglioramenti delle prestazioni e nuove funzionalità introdotte nelle nuove versioni dei pacchetti.

Conda

Conda consente di aggiornare solo un pacchetto specificato.

$ conda install -c conda pandas$ conda install -c anaconda scikit-learn

# Nuove versioni disponibili$ conda update pandas$ conda update scikit-learn

In seguito, è necessario aggiornare manualmente il file environment.yaml per mantenerlo sincronizzato con le dipendenze aggiornate.

$ conda env export > environment.yml

Pip

Anche Pip consente di aggiornare solo un pacchetto specificato e richiede di aggiornare manualmente il file requirements.txt.

$ pip install -U pandas

$ pip freeze > requirements.txt

Poetry

Utilizzando Poetry, puoi usare il comando update per aggiornare tutti i pacchetti specificati nel file pyproject.toml. Questa azione aggiorna automaticamente il file poetry.lock, garantendo la coerenza tra le specifiche dei pacchetti e il file di blocco.

$ poetry add pandas scikit-learn# Nuove versioni disponibilipoetry updateAggiornamento dipendenzeRisoluzione dipendenze... (0.3s)Scrittura file di bloccoOperazioni sui pacchetti: 0 installazioni, 2 aggiornamenti, 0 rimozioni  • Aggiornamento pandas (2.0.0 -> 2.0.2)  • Aggiornamento scikit-learn (1.2.0 -> 1.2.2)

Risoluzione delle Dipendenze

I conflitti di dipendenza si verificano quando i pacchetti o le librerie necessari per un progetto hanno versioni in conflitto o dipendenze incompatibili. La corretta risoluzione dei conflitti è fondamentale per evitare errori, problemi di esecuzione o fallimenti del progetto.

Pip

Pip installa i pacchetti in modo sequenziale, il che significa che installa ciascun pacchetto uno alla volta, seguendo l’ordine specificato. Questo approccio sequenziale può talvolta portare a conflitti quando i pacchetti hanno dipendenze incompatibili o requisiti di versione.

Ad esempio, supponiamo di installare pandas==2.0.2 per primo, che richiede numpy>=1.20.3. Successivamente, si installa numpy==1.20.2 utilizzando pip. Anche se questo creerà conflitti di dipendenza, pip procederà con l’aggiornamento della versione di numpy.

$ pip install pandas==2.0.2$ pip install numpy==1.22.2Collecting numpy=1.20.2  Attempting uninstall: numpy    Found existing installation: numpy 1.24.3    Uninstalling numpy-1.24.3:      Successfully uninstalled numpy-1.24.3ERROR: 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.pandas 2.0.2 requires numpy>=1.20.3; python_version < "3.10", but you have numpy 1.20.2 which is incompatible.Successfully installed numpy-1.20.2

Conda

Conda utilizza un risolutore SAT per esplorare tutte le combinazioni di versioni di pacchetti e dipendenze al fine di trovare un insieme compatibile.

Ad esempio, se un pacchetto esistente ha una specifica restrizione per le sue dipendenze (ad es. statsmodels==0.13.2 richiede numpy>=1.21.2,<2.0a0), e il pacchetto che si desidera installare non soddisfa tale requisito (ad es. numpy<1.21.2), conda non solleverà immediatamente un errore. Invece, cercherà diligentemente le versioni compatibili di tutti i pacchetti richiesti e delle loro dipendenze, segnalando un errore solo se non viene trovata una soluzione adeguata.

$ conda install 'statsmodels==0.13.2'$ conda search 'statsmodels==0.13.2' --infodependencies:   - numpy >=1.21.2,<2.0a0  - packaging >=21.3  - pandas >=1.0  - patsy >=0.5.2  - python >=3.9,<3.10.0a0  - scipy >=1.3$ conda install 'numpy<1.21.2'...Package ca-certificates conflicts for:python=3.8 -> openssl[version='>=1.1.1t,<1.1.2a'] -> ca-certificatesopenssl -> ca-certificatesca-certificatescryptography -> openssl[version='>1.1.0,<3.1.0'] -> ca-certificatesPackage idna conflicts for:requests -> urllib3[version='>=1.21.1,<1.27'] -> idna[version='>=2.0.0']requests -> idna[version='>=2.5,<3|>=2.5,<4']idnapooch -> requests -> idna[version='>=2.5,<3|>=2.5,<4']urllib3 -> idna[version='>=2.0.0']Package numexpr conflicts for:statsmodels==0.13.2 -> pandas[version='>=1.0'] -> numexpr[version='>=2.7.0|>=2.7.1|>=2.7.3']numexprpandas==1.5.3 -> numexpr[version='>=2.7.3']Package patsy conflicts for:statsmodels==0.13.2 -> patsy[version='>=0.5.2']patsyPackage chardet conflicts for:requests -> chardet[version='>=3.0.2,<4|>=3.0.2,<5']pooch -> requests -> chardet[version='>=3.0.2,<4|>=3.0.2,<5']Package python-dateutil conflicts for:statsmodels==0.13.2 -> pandas[version='>=1.0'] -> python-dateutil[version='>=2.7.3|>=2.8.1']python-dateutilpandas==1.5.3 -> python-dateutil[version='>=2.8.1']Package setuptools conflicts for:numexpr -> setuptoolspip -> setuptoolswheel -> setuptoolssetuptoolspython=3.8 -> pip -> setuptoolspandas==1.5.3 -> numexpr[version='>=2.7.3'] -> setuptoolsPackage brotlipy conflicts for:urllib3 -> brotlipy[version='>=0.6.0']brotlipyrequests -> urllib3[version='>=1.21.1,<1.27'] -> brotlipy[version='>=0.6.0']Package pytz conflicts for:pytzpandas==1.5.3 -> pytz[version='>=2020.1']statsmodels==0.13.2 -> pandas[version='>=1.0'] -> pytz[version='>=2017.3|>=2020.1']

Sebbene questo approccio migliori le possibilità di trovare una soluzione, può essere computazionalmente intensivo, soprattutto quando si tratta di ambienti estesi.

Poesia

Focalizzandosi sulle dipendenze dirette del progetto, il risolutore deterministico di Poetry riduce lo spazio di ricerca, rendendo il processo di risoluzione più efficiente. Valuta i vincoli specificati, come intervalli di versioni o versioni specifiche, e identifica immediatamente eventuali conflitti.

$ poetry add 'seaborn==0.12.2'$ poetry add 'matplotlib<3.1' Poiché poetry shell dipende da seaborn (0.12.2) che dipende da matplotlib (>=3.1,<3.6.1 || >3.6.1), è richiesto matplotlib.Quindi, poiché poetry shell dipende da matplotlib (<3.1), la risoluzione della versione ha fallito.

Questo feedback immediato aiuta a prevenire potenziali problemi dall’escalation e consente ai developer di affrontare il problema presto nel processo di sviluppo. Ad esempio, nel seguente codice, possiamo rilassare i requisiti per seaborn per consentire l’installazione di una versione specifica di matplotlib:

poetry add 'seaborn<=0.12.2'  'matplotlib<3.1' Operazioni del pacchetto: 1 installazione, 2 aggiornamenti, 4 rimozioni  • Rimozione contourpy (1.0.7)  • Rimozione fonttools (4.40.0)  • Rimozione packaging (23.1)  • Rimozione pillow (9.5.0)  • Aggiornamento matplotlib (3.7.1 -> 3.0.3)  • Installazione scipy (1.9.3)  • Aggiornamento seaborn (0.12.2 -> 0.11.2)

Conclusione

In sintesi, Poetry offre diversi vantaggi rispetto a pip e conda:

  1. Selezione Ampia di Pacchetti: Poetry fornisce accesso a una vasta gamma di pacchetti disponibili su PyPI, consentendo di sfruttare un ecosistema diversificato per il proprio progetto.
  2. Gestione Efficienti delle Dipendenze: Poetry installa solo le dipendenze necessarie per un pacchetto specificato, riducendo il numero di pacchetti estranei nel proprio ambiente.
  3. Rimozione Semplificata dei Pacchetti: Poetry semplifica la rimozione dei pacchetti e delle loro dipendenze associate, rendendo facile mantenere un ambiente di progetto pulito ed efficiente.
  4. Risoluzione delle Dipendenze: Il risolutore deterministico di Poetry risolve efficientemente le dipendenze, identificando e affrontando prontamente eventuali incongruenze o conflitti.

Sebbene Poetry possa richiedere del tempo e degli sforzi aggiuntivi per imparare e adattarsi, l’utilizzo di un tool come Poetry può risparmiare tempo e sforzi a lungo termine.

Amo scrivere su concetti di data science e giocare con diversi strumenti di data science. È possibile rimanere aggiornati con i miei ultimi post:

  • Iscrivendosi alla mia newsletter su Data Science Semplificata.
  • Connettendosi con me su LinkedIn e Twitter.