Last modified: October 10, 2024

This article is written in: 🇵🇱

Pliki wykonywalne i PyInstaller

Tworzenie plików wykonywalnych z kodu Python to skuteczny sposób na dostarczenie aplikacji użytkownikom, którzy nie mają zainstalowanego interpretera Pythona na swoim komputerze. Jest to szczególnie przydatne w środowiskach korporacyjnych oraz wśród użytkowników niezwiązanych z programowaniem, gdzie instalacja Pythona może być trudna lub niewykonalna. PyInstaller pozwala na przekształcenie skryptów Python w samodzielne pliki wykonywalne dla różnych systemów operacyjnych, takich jak Windows (.exe), macOS (.app) czy Linux (.bin). Dzięki temu aplikacja może być uruchomiona w docelowym systemie bez potrzeby instalowania dodatkowych narzędzi czy bibliotek.

Dlaczego PyInstaller?

Istnieją inne narzędzia do tworzenia plików wykonywalnych z kodu Python, takie jak cx_Freeze, py2exe czy Nuitka. Jednak PyInstaller wyróżnia się prostotą użycia, szerokim wsparciem dla różnych systemów operacyjnych oraz zdolnością do obsługi złożonych aplikacji z wieloma zależnościami. Ponadto, PyInstaller automatycznie analizuje zależności skryptu i dołącza niezbędne biblioteki, co ułatwia proces pakowania aplikacji.

Instalacja PyInstaller

Aby rozpocząć korzystanie z PyInstaller, należy go najpierw zainstalować. PyInstaller jest dostępny w repozytorium PyPI, więc można go zainstalować za pomocą menedżera pakietów pip. Upewnij się, że masz zainstalowaną najnowszą wersję pip oraz Pythona.

pip install pyinstaller

Jeśli posiadasz zarówno Pythona 2, jak i Pythona 3, możesz użyć pip3, aby upewnić się, że PyInstaller zostanie zainstalowany dla Pythona 3:

pip3 install pyinstaller

Sprawdzenie poprawności instalacji:

Po zainstalowaniu PyInstaller możesz sprawdzić jego wersję, aby upewnić się, że instalacja przebiegła pomyślnie:

pyinstaller --version

Jeśli komenda zwróci numer wersji, oznacza to, że PyInstaller jest gotowy do użycia.

Rozwiązywanie problemów:

Jeśli napotkasz problemy podczas instalacji, upewnij się, że:

pip install --user pyinstaller

Tworzenie pliku wykonywalnego

Po zainstalowaniu PyInstaller możesz przystąpić do konwersji swojego skryptu Python na plik wykonywalny. Podstawowa składnia polecenia PyInstaller jest następująca:

pyinstaller [opcje] nazwa_skryptu.py

Aby stworzyć pojedynczy plik wykonywalny zawierający wszystkie zależności, użyj opcji --onefile:

pyinstaller --onefile nazwa_skryptu.py

Co dzieje się podczas tego procesu?

Struktura wynikowych plików:

Po wykonaniu polecenia PyInstaller utworzy kilka plików i folderów:

Uruchomienie pliku wykonywalnego:

Plik wykonywalny znajduje się w folderze dist/. Możesz go uruchomić bezpośrednio:

Na Windows:

dist\nazwa_skryptu.exe

Na macOS/Linux:

./dist/nazwa_skryptu

Uwaga: Na systemach Unix może być konieczne nadanie plikowi uprawnień wykonywania:

chmod +x ./dist/nazwa_skryptu

Rozwiązywanie problemów z zależnościami:

W niektórych przypadkach PyInstaller może nie wykryć wszystkich zależności, zwłaszcza jeśli używasz dynamicznego importu lub modułów ładowanych w czasie wykonywania. Może to prowadzić do błędów typu ModuleNotFoundError podczas uruchamiania pliku wykonywalnego.

Rozwiązanie:

Użyj opcji --hidden-import:

pyinstaller --onefile --hidden-import=modul_ukryty nazwa_skryptu.py

Możesz podać wiele ukrytych importów, powtarzając opcję:

pyinstaller --onefile --hidden-import=modul1 --hidden-import=modul2 nazwa_skryptu.py

Alternatywnie, możesz edytować plik specyfikacji .spec i dodać brakujące moduły do listy hiddenimports.

Dołączanie zasobów: Grafika, Dźwięk i Inne

W aplikacjach Python często korzysta się z zewnętrznych zasobów takich jak grafiki, dźwięki, pliki konfiguracyjne czy bazy danych. Aby aplikacja działała poprawnie po spakowaniu, te zasoby muszą być dołączone do pliku wykonywalnego lub dystrybuowane wraz z nim.

Jak to zrobić?

PyInstaller umożliwia dołączanie dodatkowych plików za pomocą opcji --add-data. Składnia tej opcji różni się w zależności od systemu operacyjnego:

Na Windows:

pyinstaller --add-data "ścieżka_do_zasobu;ścieżka_w_aplikacji" nazwa_skryptu.py

Na macOS/Linux:

pyinstaller --add-data "ścieżka_do_zasobu:ścieżka_w_aplikacji" nazwa_skryptu.py

Parametry:

Przykład:

Dołączenie pliku config.json znajdującego się w katalogu settings:

Windows:

pyinstaller --add-data "settings\config.json;settings" nazwa_skryptu.py

macOS/Linux:

pyinstaller --add-data "settings/config.json:settings" nazwa_skryptu.py

Dołączanie wielu zasobów

Możesz dołączyć wiele zasobów, powtarzając opcję --add-data dla każdego z nich:

Windows:

pyinstaller --add-data "images\logo.png;images" --add-data "sounds\alert.wav;sounds" nazwa_skryptu.py

macOS/Linux:

pyinstaller --add-data "images/logo.png:images" --add-data "sounds/alert.wav:sounds" nazwa_skryptu.py

Przykład

Masz aplikację z następującą strukturą:

projekt/
│   main.py
│
└───resources/
    ├───images/
    │       logo.png
    │       icon.png
    │
    └───config/
            settings.conf
            data.json

Aby dołączyć cały katalog resources, użyj:

Windows:

pyinstaller --onefile --add-data "resources;resources" main.py

macOS/Linux:

pyinstaller --onefile --add-data "resources:resources" main.py

Uwaga: Dostęp do zasobów

Aby poprawnie odnaleźć zasoby w kodzie po spakowaniu aplikacji, musisz uwzględnić fakt, że ścieżki do plików zmieniają się w zależności od tego, czy aplikacja jest uruchamiana jako skrypt, czy jako spakowany plik wykonywalny.

Funkcja resource_path:

import sys
import os

def resource_path(relative_path):
    """Zwraca absolutną ścieżkę do zasobu, obsługując PyInstaller."""
    try:
        # PyInstaller tworzy tymczasowy folder i przechowuje w nim zasoby
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")

    return os.path.join(base_path, relative_path)

Przykład użycia:

image_path = resource_path('resources/images/logo.png')
image = Image.open(image_path)

Dzięki temu kod będzie działał poprawnie zarówno w środowisku deweloperskim, jak i po spakowaniu aplikacji.

Zaawansowane Ustawienia

PyInstaller oferuje wiele opcji pozwalających na dostosowanie procesu pakowania.

Ukrywanie konsoli

Domyślnie PyInstaller tworzy aplikację konsolową. Aby stworzyć aplikację GUI bez konsoli, użyj opcji --noconsole:

pyinstaller --onefile --noconsole nazwa_skryptu.py

Przykład: Jeśli tworzysz aplikację z interfejsem graficznym (np. z użyciem Tkinter, PyQt), użyj --noconsole, aby uniknąć pojawiania się pustego okna konsoli.

Zmiana ikony pliku wykonywalnego

Aby zmienić ikonę pliku wykonywalnego, użyj opcji --icon:

pyinstaller --onefile --icon=path/to/icon.ico nazwa_skryptu.py

Uwaga: Na Windows ikona powinna być w formacie .ico. Na macOS można użyć formatu .icns.

Wykluczanie niepotrzebnych modułów

Aby zredukować rozmiar pliku wykonywalnego, możesz wykluczyć nieużywane moduły:

pyinstaller --onefile --exclude-module=modul_do_wykluczenia nazwa_skryptu.py

Przykład: Jeśli nie używasz modułu tkinter, możesz go wykluczyć:

pyinstaller --onefile --exclude-module=tkinter nazwa_skryptu.py

Optymalizacja rozmiaru aplikacji

Możesz optymalizować kod bajtowy Pythona, co może zmniejszyć rozmiar aplikacji:

Przykład:

pyinstaller --onefile --optimize=2 nazwa_skryptu.py

Uwaga: Używaj -O2 ostrożnie, ponieważ usunięcie asercji może ukryć potencjalne błędy.

Modyfikacja pliku specyfikacji (.spec)

Plik .spec zawiera szczegółowe informacje o tym, jak PyInstaller ma spakować twoją aplikację. Możesz go edytować, aby uzyskać większą kontrolę nad procesem.

Kroki:

I. Wygeneruj plik .spec:

pyinstaller --onefile --name=nazwa_aplikacji --specpath=. nazwa_skryptu.py

II. Edytuj nazwa_aplikacji.spec zgodnie z potrzebami.

III. Spakuj aplikację używając pliku specyfikacji:

pyinstaller nazwa_aplikacji.spec

Co można zmienić w pliku .spec?

Tryb katalogu

Zamiast tworzyć pojedynczy plik wykonywalny, możesz pozostawić aplikację w formie katalogu:

pyinstaller nazwa_skryptu.py

Zalety:

Zwiększenie poziomu logowania

Aby uzyskać więcej informacji podczas pakowania, możesz zwiększyć poziom logowania:

pyinstaller --onefile --log-level=DEBUG nazwa_skryptu.py

Poziomy logowania: TRACE, DEBUG, INFO, WARN, ERROR, CRITICAL.

Rozumiem, że potrzebujesz bardziej szczegółowego i rozbudowanego przykładu dotyczącego tworzenia hooków oraz obsługi niestandardowych modułów w PyInstallerze. Poniżej przedstawiam bardziej szczegółowy opis, który zawiera dokładne kroki i instrukcje.

Hooki i obsługa niestandardowych modułów

Hooki w PyInstallerze to specjalne skrypty Pythona, które pozwalają na obsługę modułów wymagających niestandardowej konfiguracji lub specjalnego traktowania podczas procesu budowania. Mogą być one potrzebne, gdy moduły dynamicznie importują inne moduły, używają zasobów zewnętrznych lub mają inne zależności, których PyInstaller nie jest w stanie wykryć automatycznie.

Kiedy potrzebujesz hooków?

Zwykle PyInstaller samodzielnie rozpoznaje zależności i moduły potrzebne do działania aplikacji. Jednak niektóre moduły korzystają z dynamicznego importowania, co może powodować, że PyInstaller nie wykryje ich poprawnie. Przykłady takich modułów to np. numpy, tensorflow, scipy czy własne niestandardowe moduły.

Tworzenie hooka: krok po kroku

I. Utworzenie katalogu hooks

Zanim stworzymy hooka, musimy przygotować odpowiednią strukturę katalogów. W tym celu utwórz katalog o nazwie hooks w głównym folderze projektu. W tym katalogu umieścisz wszystkie niestandardowe hooki, które będą obsługiwać dodatkowe moduły.

mkdir hooks

II. Stworzenie pliku hooka

Każdy hook to osobny plik o nazwie hook-<nazwa_modulu>.py, gdzie <nazwa_modulu> to nazwa modułu, który wymaga specjalnej obsługi. Załóżmy, że chcesz spakować aplikację korzystającą z modułu o nazwie moj_modul, który dynamicznie ładuje inne podmoduły.

W tym przypadku utwórz plik hook-moj_modul.py w katalogu hooks/:

touch hooks/hook-moj_modul.py

III. Konfiguracja hooka

W pliku hook-moj_modul.py musisz zdefiniować, jak PyInstaller powinien traktować moj_modul. W najprostszej formie możesz użyć funkcji collect_submodules z pakietu PyInstaller.utils.hooks, aby zebrać wszystkie podmoduły, które mogą być dynamicznie importowane. Przykład takiej konfiguracji:

# hooks/hook-moj_modul.py
from PyInstaller.utils.hooks import collect_submodules

# Zbierz wszystkie podmoduły
hiddenimports = collect_submodules('moj_modul')

W tym przykładzie hiddenimports to lista podmodułów, które są dynamicznie ładowane przez moj_modul, a które muszą być explicite uwzględnione w pakiecie generowanym przez PyInstaller.

IV. Zdefiniowanie dodatkowych zależności

Oprócz hiddenimports, możesz także zdefiniować inne typy zasobów, takie jak dane, które muszą być dodane do pakietu. Na przykład, jeśli moj_modul korzysta z plików konfiguracyjnych, grafik, czy innych zasobów, można je uwzględnić, definiując dodatkową zmienną datas:

# hooks/hook-moj_modul.py
from PyInstaller.utils.hooks import collect_submodules, collect_data_files

# Zbierz wszystkie podmoduły
hiddenimports = collect_submodules('moj_modul')

# Zbierz dane (np. pliki konfiguracyjne)
datas = collect_data_files('moj_modul')

V. Użycie hooków podczas budowania aplikacji

Aby PyInstaller uwzględnił nowo utworzone hooki, musisz przekazać ścieżkę do katalogu hooks za pomocą opcji --additional-hooks-dir. Przykład komendy budowania:

pyinstaller --onefile --additional-hooks-dir=hooks nazwa_skryptu.py

W tym przykładzie:

VI. Weryfikacja poprawności hooka

Aby upewnić się, że hook działa prawidłowo, uruchom spakowaną aplikację i sprawdź, czy wszystkie moduły działają bez problemów. Możesz to zrobić, analizując plik .spec, który PyInstaller generuje podczas kompilacji, lub ręcznie testując aplikację po spakowaniu.

Przekazywanie argumentów do aplikacji

Jeśli Twoja aplikacja korzysta z argumentów wiersza poleceń, PyInstaller zachowa tę funkcjonalność po spakowaniu. Musisz jedynie upewnić się, że obsługa argumentów jest prawidłowo zaimplementowana w skrypcie, np. za pomocą modułów takich jak argparse lub sys.argv.

Przykład prostego skryptu obsługującego argumenty wiersza poleceń:

import argparse

def main():
    parser = argparse.ArgumentParser(description="Opis aplikacji")
    parser.add_argument('--opcje', type=str, help="Przykładowa opcja")
    args = parser.parse_args()
    
    print(f"Otrzymano opcje: {args.opcje}")

if __name__ == '__main__':
    main()

Po spakowaniu aplikacji możesz ją uruchomić z argumentami wiersza poleceń:

./nazwa_aplikacji --opcje "wartość"

Spis Treści

    Pliki wykonywalne i PyInstaller
    1. Instalacja PyInstaller
    2. Tworzenie pliku wykonywalnego
    3. Dołączanie zasobów: Grafika, Dźwięk i Inne
      1. Jak to zrobić?
      2. Dołączanie wielu zasobów
      3. Przykład
      4. Uwaga: Dostęp do zasobów
    4. Zaawansowane Ustawienia
      1. Ukrywanie konsoli
      2. Zmiana ikony pliku wykonywalnego
      3. Wykluczanie niepotrzebnych modułów
      4. Optymalizacja rozmiaru aplikacji
      5. Modyfikacja pliku specyfikacji (.spec)
      6. Tryb katalogu
      7. Zwiększenie poziomu logowania
      8. Hooki i obsługa niestandardowych modułów