Come utilizzare PyInstaller per creare eseguibili Python

Python, potente e versatile com'è, manca di alcune funzionalità chiave fuori dagli schemi. Per uno, Python non fornisce alcun meccanismo nativo per compilare un programma Python in un pacchetto eseguibile autonomo.

Per essere onesti, il caso d'uso originale di Python non richiedeva mai pacchetti standalone. I programmi Python sono stati generalmente eseguiti sul posto su sistemi in cui viveva una copia dell'interprete Python. Ma la crescente popolarità di Python ha creato una maggiore domanda per l'esecuzione di app Python su sistemi senza runtime Python installato.

Diverse terze parti hanno progettato soluzioni per la distribuzione di app Python autonome. La soluzione più popolare del gruppo e la più matura è PyInstaller. PyInstaller non rende il processo di creazione del pacchetto di un'app Python totalmente indolore, ma va molto lontano.

In questo articolo esploreremo le basi dell'utilizzo di PyInstaller incluso come funziona PyInstaller, come utilizzare PyInstaller per creare un eseguibile Python autonomo, come mettere a punto gli eseguibili Python che crei e come evitare alcune delle trappole comuni che vanno con l'utilizzo di PyInstaller.

Creazione di un pacchetto PyInstaller

PyInstaller è un pacchetto Python, installato con pip( pip install pyinstaller). PyInstaller può essere installato nell'installazione predefinita di Python, ma è meglio creare un ambiente virtuale per il progetto che desideri creare in un pacchetto e installare PyInstaller lì.

PyInstaller funziona leggendo il tuo programma Python, analizzando tutte le importazioni che fa e raggruppando copie di tali importazioni con il tuo programma. PyInstaller legge il tuo programma dal suo punto di ingresso. Ad esempio, se il punto di ingresso del tuo programma è myapp.py, corri pyinstaller myapp.pyper eseguire l'analisi. PyInstaller può rilevare e impacchettare automaticamente molti pacchetti Python comuni, come NumPy, ma in alcuni casi potrebbe essere necessario fornire suggerimenti. (Ne parleremo più avanti.)

Dopo aver analizzato il codice e aver scoperto tutte le librerie e i moduli che utilizza, PyInstaller genera un "file spec". Uno script Python con estensione .spec, questo file include dettagli su come la tua app Python deve essere impacchettata. La prima volta che esegui PyInstaller sulla tua app, PyInstaller genererà un file delle specifiche da zero e lo popolerà con alcuni valori predefiniti sani. Non eliminare questo file; è la chiave per perfezionare una distribuzione di PyInstaller!

Infine, PyInstaller tenta di produrre un eseguibile dall'app, in bundle con tutte le sue dipendenze. Al termine, una sottocartella denominata dist (per impostazione predefinita; sei libero di specificare un nome diverso) apparirà nella directory del progetto. Questo a sua volta contiene una directory che è la tua app in bundle: ha un .exefile da eseguire, insieme a tutte le librerie e altri file supplementari richiesti.

Tutto quello che devi fare per distribuire il tuo programma, quindi, è impacchettare questa directory come un .zipfile o qualche altro bundle. Il bundle dovrà in genere essere estratto in una directory in cui l'utente dispone delle autorizzazioni di scrittura per poter essere eseguito.

Testare un pacchetto PyInstaller

C'è una buona probabilità che il tuo primo tentativo di utilizzare PyInstaller per creare il pacchetto di un'app non abbia successo.

Per verificare se il tuo pacchetto PyInstaller funziona, vai alla directory contenente l'eseguibile in bundle ed esegui il .exefile da lì dalla riga di comando. Se non viene eseguito, gli errori che vedrai stampati sulla riga di comando dovrebbero fornire un suggerimento su cosa c'è che non va.

Il motivo più comune per cui un pacchetto PyInstaller fallisce è che PyInstaller non è riuscito a raggruppare un file richiesto. Tali file mancanti rientrano in alcune categorie:

  • Importazioni nascoste o mancanti : a volte PyInstaller non è in grado di rilevare l'importazione di un pacchetto o di una libreria, in genere perché viene importato dinamicamente. Il pacchetto o la libreria dovrà essere specificata manualmente.
  • File standalone mancanti : se il programma dipende da file di dati esterni che devono essere associati al programma, PyInstaller non ha modo di saperlo. Dovrai includere manualmente i file.
  • Binari mancanti : anche in questo caso, se il programma dipende da un file binario esterno come un .DLL che PyInstaller non è in grado di rilevare, sarà necessario includerlo manualmente.

La buona notizia è che PyInstaller fornisce un modo semplice per affrontare i problemi di cui sopra. Il .specfile creato da PyInstaller include campi che possiamo compilare per fornire i dettagli che PyInstaller ha mancato.

Apri il .specfile in un editor di testo e cerca la definizione Analysisdell'oggetto. Molti dei parametri passati Analysissono elenchi vuoti, ma possono essere modificati per specificare i dettagli mancanti:

  • hiddenimportsper importazioni nascoste o mancanti : aggiungi a questo elenco una o più stringhe con i nomi delle librerie che desideri includere nella tua app. Se si desidera aggiungere pandase bokeh, ad esempio, è necessario specificarlo come  ['pandas','bokeh']. Nota che le librerie in questione devono essere installate nella stessa istanza di Python in cui stai eseguendo PyInstaller.
  • datasper i file autonomi mancanti : aggiungi qui una o più specifiche per i file nell'albero del progetto che desideri includere nel progetto. Ogni file deve essere passato come una tupla che indica il percorso relativo al file nella directory del progetto e il percorso relativo all'interno della directory di distribuzione in cui si desidera posizionare il file. Ad esempio, se avessi un file ./models/mainmodel.datche desideri includere con la tua app e desideri inserirlo in una sottodirectory corrispondente nella directory di distribuzione, utilizzerai ('./models/mainmodel.dat','./models')come una voce hiddenimportsnell'elenco. Tieni presente che puoi utilizzare globcaratteri jolly in stile per specificare più di un file.
  • binariesper i file binari autonomi mancanti : come con datas, è possibile utilizzare binariesper passare un elenco di tuple che specificano le posizioni dei file binari nell'albero del progetto e le loro destinazioni nella directory di distribuzione. Di nuovo, puoi usare globcaratteri jolly in stile.

Tieni presente che qualsiasi elenco passato a Analysispuò essere generato a livello di .speccodice in precedenza nel file. Dopotutto, il .specfile è solo uno script Python con un altro nome.

Dopo aver apportato le modifiche al .specfile, eseguire nuovamente PyInstaller per ricostruire il pacchetto. Tuttavia, da ora in poi, assicurati di passare il .specfile modificato come parametro (ad esempio pyinstaller myapp.spec). Prova l'eseguibile come prima. Se qualcosa è ancora rotto, puoi modificare nuovamente il .specfile e ripetere il processo finché tutto funziona.

Infine, quando sei soddisfatto che tutto funzioni come previsto, potresti voler modificare il  .specfile per impedire alla tua app in pacchetto di presentare una finestra della riga di comando all'avvio. Nelle EXEimpostazioni dell'oggetto nel .specfile, impostare  console=False. La soppressione della console è utile se la tua app ha una GUI e non vuoi che una falsa finestra della riga di comando porti gli utenti fuori strada. Ovviamente, non modificare questa impostazione se la tua app richiede una riga di comando.

Raffinare un pacchetto PyInstaller

Una volta che la tua app è stata impacchettata con PyInstaller e che funziona correttamente, la prossima cosa che probabilmente vorrai fare è snellirla un po '. I pacchetti PyInstaller non sono noti per essere snelli.

Poiché Python è un linguaggio dinamico, è difficile prevedere esattamente cosa sarà necessario in fase di esecuzione da un determinato programma. Per questo motivo, quando PyInstaller rileva l'importazione di un pacchetto, include tutto in quel pacchetto, indipendentemente dal fatto che sia effettivamente utilizzato in fase di esecuzione dal programma. 

Ecco la buona notizia. PyInstaller include un meccanismo per escludere selettivamente interi pacchetti o singoli spazi dei nomi all'interno dei pacchetti. Ad esempio, supponiamo che il tuo programma importi il ​​pacchetto foo, che include foo.bare foo.bip. Se sai per  certo che il tuo programma utilizza solo la logica foo.bar, puoi escludere foo.bipe risparmiare spazio in tutta sicurezza.

A tale scopo, si utilizza il excludesparametro passato Analysisall'oggetto nel .specfile. Puoi passare un elenco di nomi - moduli di primo livello o spazi dei nomi puntati - da escludere dal tuo pacchetto. Ad esempio, per escludere foo.bip, devi semplicemente specificare  ['foo.bip'].

Un'esclusione comune che puoi fare è tkinterla libreria Python per la creazione di semplici interfacce utente grafiche multipiattaforma. Per impostazione predefinita,  tkintere tutti i suoi file di supporto sono compressi con un progetto PyInstaller. Se non si sta usando tkinternel vostro progetto, è possibile escludere che con l'aggiunta 'tkinter'alla excludeslista. L'omissione tkinterridurrà la dimensione del pacchetto di circa 7 MB.

Un'altra comune esclusione sono le suite di test. Se un pacchetto importato dal programma ha una suite di test, la suite di test potrebbe finire per essere inclusa nel pacchetto PyInstaller. A meno che tu non esegua effettivamente la suite di test nel programma distribuito, puoi tranquillamente escluderla.

Tieni presente che i pacchetti creati utilizzando le esclusioni devono essere accuratamente testati prima di essere utilizzati. Se finisci per escludere funzionalità che vengono utilizzate in uno scenario futuro che non avevi previsto, la tua app si guasterà.

Suggerimenti per PyInstaller

  • Crea il tuo pacchetto PyInstaller sul sistema operativo su cui intendi distribuire.  PyInstaller non supporta build multipiattaforma. Se devi distribuire la tua app Python autonoma su sistemi MacOS, Linux e Windows, dovrai installare PyInstaller e creare versioni separate dell'app su ciascuno di questi sistemi operativi. 
  • Crea il tuo pacchetto PyInstaller mentre sviluppi la tua app.  Non appena sai che distribuirai il tuo progetto con PyInstaller, crea il tuo .specfile e inizia a perfezionare il pacchetto PyInstaller parallelamente allo sviluppo della tua app. In questo modo puoi aggiungere esclusioni o inclusioni mentre procedi e testare il modo in cui le nuove funzionalità vengono distribuite con l'app mentre le scrivi.
  • Non utilizzare la --onefilemodalità PyInstaller  .  PyInstaller include un'opzione della riga di comando --onefile, che racchiude l'intera app in un singolo eseguibile autoestraente. Sembra un'ottima idea: devi solo fornire un file! - ma presenta alcune insidie. Ogni volta che esegui l'app, deve prima decomprimere tutti i file all'interno dell'eseguibile in una directory temporanea. Se l'app è grande (200 MB, ad esempio), il disimballaggio può significare un ritardo di diversi secondi. Usa invece la modalità predefinita a directory singola e impacchetta tutto come un .zipfile.
  • Crea un programma di installazione per la tua app PyInstaller.  Se desideri un modo per distribuire la tua app diverso da un file .zip, considera l'utilizzo di un'utilità di installazione come il sistema di installazione open source Nullsoft Scriptable. Aggiunge un sovraccarico minimo alla dimensione del deliverable e consente di configurare molti aspetti del processo di installazione, come la creazione di collegamenti al proprio eseguibile.
  • Non aspettarti accelerazioni.  PyInstaller è un  imballaggio sistema, non un  compilatore  o un  ottimizzatore . Il codice confezionato con PyInstaller non viene eseguito più velocemente di quanto sarebbe se eseguito sul sistema originale. Se vuoi velocizzare il codice Python, usa una libreria con accelerazione C adatta all'attività o un progetto come Cython.

Come fare di più con Python

  • Tutorial Cython: come velocizzare Python
  • Come installare Python in modo intelligente
  • Migliore gestione dei progetti Python con Poetry
  • Virtualenv e venv: spiegazione degli ambienti virtuali Python
  • Python virtualenv e venv cosa fare e cosa non fare
  • Spiegazione del threading e dei sottoprocessi di Python
  • Come utilizzare il debugger Python
  • Come usare timeit per profilare il codice Python
  • Come usare cProfile per profilare il codice Python
  • Inizia con async in Python
  • Come usare asyncio in Python
  • Come convertire Python in JavaScript (e viceversa)