Grupowanie plików danych za pomocą PyInstaller (--onefile)

107

Próbuję zbudować jednoplikowy plik EXE z PyInstaller, który ma zawierać obraz i ikonę. Za całe życie nie mogę tego zmusić --onefile.

Jeśli to zrobię --onedir, wszystko działa bardzo dobrze. Kiedy używam --onefile, nie może znaleźć wymienionych plików dodatkowych (podczas uruchamiania skompilowanego EXE). Znajduje biblioteki DLL i wszystko inne w porządku, ale nie dwa obrazy.

Zajrzałem do katalogu tymczasowego wygenerowanego podczas uruchamiania \Temp\_MEI95642\pliku EXE ( na przykład) i pliki rzeczywiście tam są. Kiedy upuszczam EXE w tym katalogu tymczasowym, znajduje je. Bardzo kłopotliwe.

Oto, co dodałem do .specpliku

a.datas += [('images/icon.ico', 'D:\\[workspace]\\App\\src\\images\\icon.ico',  'DATA'),
('images/loaderani.gif','D:\\[workspace]\\App\\src\\images\\loaderani.gif','DATA')]     

Powinienem dodać, że próbowałem nie umieszczać ich również w podfolderach, ale nie miało to znaczenia.

Edycja: oznaczono nowszą odpowiedź jako poprawną z powodu aktualizacji PyInstaller.

nadrzewny rekin
źródło
11
Dziękuję bardzo! linia tutaj ( a.datas += ...) naprawdę mi pomogła. dokumentacja pyinstallera mówi o używaniu, COLLECTale nie udaje się umieścić plików w binarnym podczas używania--onefile
Igor Serebryany
@IgorSerebryany: Oddelegowany! Właśnie miałem ten sam problem.
Hubro
Mój
plik
1
Weź pod uwagę, że folder tymczasowy znika po zakończeniu wykonywania, więc aby sprawdzić, co jest w środku, umieść katalog list sys._MEIPASS w głównym
hithwen
Czy istnieje również sposób użycia składni Tree (), którą widziałem w tym miejscu?
fatuhoku

Odpowiedzi:

152

Nowsze wersje PyInstaller nie ustawiają envjuż zmiennej, więc doskonała odpowiedź Shisha nie będzie działać. Teraz ścieżka zostanie ustawiona jako sys._MEIPASS:

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    try:
        # PyInstaller creates a temp folder and stores path in _MEIPASS
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")

    return os.path.join(base_path, relative_path)
max
źródło
10
Używam pyinstaller 2 z pythonem 2.7 i nie mam _MEIPASS2w envs, ale sys._MEIPASSdziała dobrze, więc +1. Proponuję:path = getattr(sys, '_MEIPASS', os.getcwd())
kbec
2
+1. Dziękuję za to. Próbując używać zmiennej środowiskowej _MEIPASS2 od jakiegoś czasu, najwyraźniej napisałem swój kod, gdy wciąż była zmienną środowiskową, ponieważ pamiętam, że działała. Nagle zatrzymał się, kiedy niedawno ponownie skompilowałem.
Favil Orbedios
8
Zalecałbym łapanie AttributeErrorzamiast bardziej ogólnego Exception. Napotkałem problemy, w których brakowało mi importu sys i ścieżki po cichu domyślnie os.path.abspath.
Aaron
Możesz pomóc rozwiązać mój problem .
Phillip,
1
Używanie bieżącego katalogu roboczego w przypadku, gdy nie jest ustawiona opcja _MEIPASS, jest nieprawidłowe. Zobacz moją odpowiedź .
Jonathon Reinhart
51

pyinstaller rozpakowuje dane do folderu tymczasowego i przechowuje tę ścieżkę do katalogu w _MEIPASS2zmiennej środowiskowej. Aby pobrać _MEIPASS2katalog w trybie spakowanym i użyć katalogu lokalnego w trybie rozpakowanym (programistycznym), używam tego:

def resource_path(relative):
    return os.path.join(
        os.environ.get(
            "_MEIPASS2",
            os.path.abspath(".")
        ),
        relative
    )

Wynik:

# in development
>>> resource_path("app_icon.ico")
"/home/shish/src/my_app/app_icon.ico"

# in production
>>> resource_path("app_icon.ico")
"/tmp/_MEI34121/app_icon.ico"
Shish
źródło
10
Jak, kiedy i gdzie można tego użyć?
gseattle
4
Powinieneś użyć tego skryptu w pliku .py, który próbujesz skompilować za pomocą PyInstaller. Nie umieszczaj tego fragmentu kodu w pliku .spec, to nie zadziała. Uzyskaj dostęp do plików, zastępując ścieżkę, którą zwykle wpisujesz resource_path("file_to_be_accessed.mp3"). Uważaj, aby w aktualnej wersji PyInstaller użyć max 'answer.
Exeleration-G
1
Czy ta odpowiedź jest specyficzna dla używania --one-fileopcji?
fatuhoku
Możesz pomóc rozwiązać mój problem .
Phillip,
Używanie bieżącego katalogu roboczego w przypadku, gdy nie jest ustawiona opcja _MEIPASS, jest nieprawidłowe. Zobacz moją odpowiedź .
Jonathon Reinhart
30

Wszystkie inne odpowiedzi używają bieżącego katalogu roboczego w przypadku, gdy aplikacja nie jest PyInstalled (tj. sys._MEIPASSNie jest ustawiona). To nieprawda, ponieważ uniemożliwia uruchomienie aplikacji z katalogu innego niż ten, w którym znajduje się skrypt.

Lepsze rozwiązanie:

import sys
import os

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, relative_path)
Jonathon Reinhart
źródło
Dzięki Jon, twoja funkcja pomogła mi dzisiaj rozwiązać problem. Mam tylko pytanie. Dlaczego niektórzy piszą _MEIPASS2 zamiast _MEIPASS? Kiedy próbowałem wcześniej dodać 2, nie zadziałało.
Ahmed AbdelKhalek
Myślę, że os.env['_MEIPASS2'](przy użyciu środowiska systemu operacyjnego) była starą metodą uzyskiwania katalogu. AFAIK sys._MEIPASSto obecnie jedyna poprawna metoda.
Jonathon Reinhart,
Witaj, Twoje rozwiązanie działa dobrze, gdy resource_path()znajduje się w głównym skrypcie. Czy istnieje sposób, aby to działało, gdy jest napisane w module? Od teraz będzie próbował uzyskać zasoby w folderze, w którym znajduje się moduł, a nie w folderze głównym.
Guimoute
1
@Guimoute brzmi tak, jakby kod w tej odpowiedzi działał zgodnie z projektem: lokalizuje zasoby do modułu, który je udostępnia, ponieważ używa __file__. Jeśli chcesz, aby moduł miał dostęp do zasobów spoza jego własnego zakresu, będziesz musiał zaimplementować, że kod importujący zapewnia ścieżkę do nich dla twojego modułu, np. Przez moduł dostarczający wywołanie "set_resource_location ()", które wywołania importera - nie myśl, że to różni się od tego, jak powinieneś pracować, gdy nie używasz pyinstaller.
Barny
@barny Dzięki za podpowiedź! Spróbuję czegoś podobnego.
Guimoute
14

Być może przegapiłem krok lub zrobiłem coś źle, ale metody, które są powyżej, nie połączyły plików danych z PyInstaller w jeden plik exe. Pozwól mi podzielić się krokami, co zrobiłem.

  1. krok: Napisz jedną z powyższych metod do pliku py z importowaniem modułów sys i os. Próbowałem obu z nich. Ostatni to:

    def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
        base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
        return os.path.join(base_path, relative_path)
  2. krok: Napisz, pyi-makespec file.py , do konsoli, aby utworzyć plik file.spec.

  3. krok: Otwórz plik.spec w Notepad ++, aby dodać pliki danych, jak poniżej:

    a = Analysis(['C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.py'],
                 pathex=['C:\\Users\\TCK\\Desktop\\Projeler'],
                 binaries=[],
                 datas=[],
                 hiddenimports=[],
                 hookspath=[],
                 runtime_hooks=[],
                 excludes=[],
                 win_no_prefer_redirects=False,
                 win_private_assemblies=False,
                 cipher=block_cipher)
    #Add the file like the below example
    a.datas += [('Converter-GUI.ico', 'C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.ico', 'DATA')]
    pyz = PYZ(a.pure, a.zipped_data,
         cipher=block_cipher)
    exe = EXE(pyz,
              a.scripts,
              exclude_binaries=True,
              name='Converter-GUI',
              debug=False,
              strip=False,
              upx=True,
              #Turn the console option False if you don't want to see the console while executing the program.
              console=False,
              #Add an icon to the program.
              icon='C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.ico')
    
    coll = COLLECT(exe,
                   a.binaries,
                   a.zipfiles,
                   a.datas,
                   strip=False,
                   upx=True,
                   name='Converter-GUI')
  4. krok: wykonałem powyższe kroki, a następnie zapisałem plik specyfikacji. W końcu otworzyłem konsolę i napisz, pyinstaller file.spec (w moim przypadku file = Converter-GUI).

Wniosek: w folderze dist nadal jest więcej niż jeden plik.

Uwaga: używam Pythona 3.5.

EDYCJA: Wreszcie działa z metodą Jonathana Reinharta.

  1. krok: Dodaj poniższe kody do pliku Python z importowaniem sys i os.

    def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
        base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
        return os.path.join(base_path, relative_path)
  2. krok: Wywołaj powyższą funkcję, dodając ścieżkę do pliku:

    image_path = resource_path("Converter-GUI.ico")
  3. krok: Napisz powyższą zmienną, która wywołuje funkcję do miejsca, w którym twoje kody wymagają ścieżki. W moim przypadku jest to:

        self.window.iconbitmap(image_path)
  4. krok: Otwórz konsolę w tym samym katalogu swojego pliku pythona, zapisz kody jak poniżej:

        pyinstaller --onefile your_file.py
  5. step: Otwórz plik .spec pliku pythona i dołącz tablicę a.datas oraz dodaj ikonę do klasy exe, która została podana powyżej przed edycją w 3-tym kroku.
  6. krok: Zapisz i zamknij plik ścieżki. Przejdź do folderu zawierającego specyfikację i plik py. Otwórz ponownie okno konsoli i wpisz poniższe polecenie:

        pyinstaller your_file.spec

Po kroku 6. jeden plik jest gotowy do użycia.

dildeolupbiten
źródło
Dzięki @dilde! Nie chodzi o to, że a.datastablica z kroku 5 pobiera krotki ciągów, zobacz pythonhosted.org/PyInstaller/spec-files.html#adding-data-files w celach informacyjnych.
Ian Campbell,
Przepraszam, nie rozumiem również części Twojej wiadomości, której pragnę z powodu mojego słabego angielskiego. Ta część to "Nie to a.datas ...." , Czy chciałeś napisać 'Zauważ, że a.datas ... " Czy jest coś nie tak z tym, co napisałem o dodawaniu ciągów do tablicy a.datas?
dildeolupbiten
Ups! Należy zauważyć, że ... przepraszam za literówkę. Twoje kroki zadziałały dla mnie świetnie :), nie mogłem znaleźć tych informacji w ich dokumentacji ani nigdzie indziej.
Ian Campbell,
8

Zamiast przepisać cały mój kod ścieżki zgodnie z sugestią, zmieniłem katalog roboczy:

if getattr(sys, 'frozen', False):
    os.chdir(sys._MEIPASS)

Po prostu dodaj te dwie linie na początku kodu, resztę możesz zostawić bez zmian.

Bobslej
źródło
2
Nie. Dobre aplikacje rzadko powinny zmieniać katalog roboczy. Są lepsze sposoby.
Jonathon Reinhart
3

Nieznaczna modyfikacja zaakceptowanej odpowiedzi.

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS, relative_path)

    return os.path.join(os.path.abspath("."), relative_path)
Krishna Balan
źródło
Używanie bieżącego katalogu roboczego w przypadku, gdy nie jest ustawiona opcja _MEIPASS, jest nieprawidłowe. Zobacz moją odpowiedź .
Jonathon Reinhart
1

Najczęstszą skargą / pytaniem, które widziałem w PyInstaller, jest „mój kod nie może znaleźć pliku danych, który na pewno zawarłem w paczce, gdzie to jest?” I nie jest łatwo zobaczyć, jaki / gdzie jest twój kod wyszukuje, ponieważ wyodrębniony kod znajduje się w lokalizacji tymczasowej i jest usuwany po zakończeniu. Dodaj ten fragment kodu, aby zobaczyć, co jest zawarte w pliku onefile i gdzie się znajduje, używając @Jonathon Reinhart'sresource_path()

for root, dirs, files in os.walk(resource_path("")):
    print(root)
    for file in files:
        print( "  ",file)
Barny
źródło
0

Istniejące odpowiedzi wydawały mi się zagmatwane i zajęło mi dużo czasu ustalenie, gdzie jest problem. Oto kompilacja wszystkiego, co znalazłem.

Kiedy uruchamiam aplikację, pojawia się błąd Failed to execute script foo(jeśli foo.pyjest to plik główny). Aby rozwiązać ten problem, nie uruchamiaj PyInstaller z --noconsole(lub edytuj, main.specaby zmienić console=False=> console=True). Dzięki temu uruchom plik wykonywalny z wiersza poleceń, a zobaczysz błąd.

Pierwszą rzeczą do sprawdzenia jest prawidłowe pakowanie dodatkowych plików. Powinieneś dodać krotki, na przykład, ('x', 'x')jeśli chcesz, aby folder xzostał uwzględniony.

Po awarii nie klikaj OK. Jeśli używasz systemu Windows, możesz użyć funkcji Wyszukaj wszystko . Poszukaj jednego ze swoich plików (np. sword.png). Powinieneś znaleźć tymczasową ścieżkę, w której rozpakował pliki (np. C:\Users\ashes999\AppData\Local\Temp\_MEI157682\images\sword.png). Możesz przeglądać ten katalog i upewnić się, że zawiera wszystko. Jeśli nie możesz tego znaleźć w ten sposób, poszukaj czegoś takiego jak main.exe.manifest(Windows) lub python35.dll(jeśli używasz Pythona 3.5).

Jeśli instalator zawiera wszystko, następnym prawdopodobnym problemem jest we / wy pliku: Twój kod Pythona szuka plików w katalogu pliku wykonywalnego zamiast w katalogu tymczasowym.

Aby to naprawić, każda odpowiedź na to pytanie działa. Osobiście znalazłem ich mieszankę do pracy: najpierw zmień katalog warunkowo w głównym pliku punktu wejścia, a wszystko inne działa tak, jak jest:

if hasattr(sys, '_MEIPASS'): os.chdir(sys._MEIPASS)

popioły999
źródło
1
Przepraszam. Ale zmiana katalogów nigdy nie jest właściwą odpowiedzią. Jeśli użytkownik podaje ścieżkę do aplikacji, oczekuje, że będzie ona względna w stosunku do bieżącego katalogu. Jeśli to zmienisz, będzie źle.
Jonathon Reinhart,
0

Jeśli nadal próbujesz umieścić pliki względem swojego pliku wykonywalnego zamiast w katalogu tymczasowym, musisz skopiować go samodzielnie. W ten sposób skończyłem to zrobić.

https://stackoverflow.com/a/59415662/999943

Dodajesz krok w pliku specyfikacji, który wykonuje kopię systemu plików do zmiennej DISTPATH.

Mam nadzieję, że to pomoże.

phyatt
źródło
0

Zajmuję się tym problemem od dawna (no, bardzo długo). Przeszukałem prawie każde źródło, ale sprawy nie układały się w mojej głowie.

Wreszcie, myślę, że wymyśliłem dokładne kroki, które należy wykonać, którymi chciałem się podzielić.

Zwróć uwagę, że moja odpowiedź wykorzystuje informacje o odpowiedziach innych osób na to pytanie.

Jak utworzyć samodzielny plik wykonywalny projektu w języku Python.

Załóżmy, że mamy project_folder i drzewo plików wygląda następująco:

project_folder/
    main.py
    xxx.py # modules
    xxx.py # modules
    sound/ # directory containing the sound files
    img/ # directory containing the image files
    venv/ # if using a venv

Po pierwsze, powiedzmy, że zdefiniowałeś swoje ścieżki sound/i img/foldery w zmiennych sound_diri img_dirw następujący sposób:

img_dir = os.path.join(os.path.dirname(__file__), "img")
sound_dir = os.path.join(os.path.dirname(__file__), "sound")

Musisz je zmienić w następujący sposób:

img_dir = resource_path("img")
sound_dir = resource_path("sound")

Gdzie resource_path()jest zdefiniowane w górnej części skryptu jako:

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(base_path, relative_path)

Aktywuj wirtualne środowisko, jeśli używasz Venv,

Zainstalować pyinstaller jeśli nie jeszcze przez: pip3 install pyinstaller.

Uruchom: pyi-makespec --onefile main.pyaby utworzyć plik specyfikacji dla procesu kompilacji i kompilacji.

Spowoduje to zmianę hierarchii plików na:

project_folder/
    main.py
    xxx.py # modules
    xxx.py # modules
    sound/ # directory containing the sound files
    img/ # directory containing the image files
    venv/ # if using a venv
    main.spec

Otwórz (z edior) main.spec:

U góry wstaw:

added_files = [

("sound", "sound"),
("img", "img")

]

Następnie zmień wiersz datas=[],nadatas=added_files,

Aby uzyskać szczegółowe informacje na temat operacji wykonanych na, main.speczobacz tutaj.

Biegać pyinstaller --onefile main.spec

I to wszystko, można uruchomić mainw project_folder/distdowolnym miejscu, bez konieczności cokolwiek innego w swoim folderze. Możesz rozpowszechniać tylko ten mainplik. To jest teraz prawdziwy samodzielny .

muyustan
źródło
@JonathonReinhart, jeśli nadal jesteś w pobliżu, chciałem Cię poinformować, że wykorzystałem Twoją funkcję w mojej odpowiedzi.
muyustan
0

Korzystając z doskonałej odpowiedzi od Maxa i tego postu na temat dodawania dodatkowych plików danych, takich jak obrazy lub dźwięk oraz moich własnych badań / testów, doszedłem do wniosku, że najłatwiej jest dodać takie pliki.

Jeśli chcesz zobaczyć przykład na żywo, moje repozytorium jest tutaj na GitHub.

Uwaga: dotyczy to kompilacji przy użyciu polecenia --onefilelub -Fw programie pyinstaller.

Moje środowisko jest następujące.


Rozwiązanie problemu w 2 krokach

Aby rozwiązać ten problem, musimy wyraźnie powiedzieć Pyinstallerowi, że mamy dodatkowe pliki, które muszą być „dołączone” do aplikacji.

Musimy również używać ścieżki „względnej” , aby aplikacja działała poprawnie, gdy działa jako skrypt Pythona lub zamrożony EXE.

Mając to na uwadze, potrzebujemy funkcji, która pozwoli nam mieć ścieżki względne. Korzystając z funkcji, którą Max opublikował , możemy łatwo rozwiązać ścieżkę względną.

def img_resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    try:
        # PyInstaller creates a temp folder and stores path in _MEIPASS
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")

    return os.path.join(base_path, relative_path)

Użylibyśmy powyższej funkcji w ten sposób, aby ikona aplikacji była wyświetlana, gdy aplikacja jest uruchomiona jako skrypt LUB zamrożony plik EXE.

icon_path = img_resource_path("app/img/app_icon.ico")
root.wm_iconbitmap(icon_path)

Następnym krokiem jest to, że musimy poinstruować Pyinstaller, gdzie znaleźć dodatkowe pliki podczas kompilacji, aby po uruchomieniu aplikacji zostały utworzone w katalogu tymczasowym.

Możemy rozwiązać ten problem na dwa sposoby, jak pokazano w dokumentacji , ale ja osobiście wolę zarządzać własnym plikiem .spec, więc tak właśnie zamierzamy to zrobić.

Po pierwsze, musisz już mieć plik .spec. W moim przypadku udało mi się stworzyć to, co potrzebne, uruchamiając pyinstallerz dodatkowymi argumentami można znaleźć dodatkowe argumenty tutaj . Z tego powodu mój plik specyfikacji może wyglądać trochę inaczej niż twój, ale zamieszczam go w całości w celach informacyjnych, po tym, jak wyjaśnię ważne elementy.

added_files to zasadniczo lista zawierająca krotki, w moim przypadku chcę dodać tylko POJEDYNCZY obraz, ale możesz dodać wiele ikon ico, png lub jpg używając('app/img/*.ico', 'app/img')Możesz również utworzyć kolejną krotkę,added_files = [ (), (), ()]aby mieć wiele importów

Pierwsza część krotki określa, jaki plik lub typ pliku chcesz dodać, a także gdzie je znaleźć. Pomyśl o tym jako CTRL + C

Druga część krotki mówi Pyinstallerowi, aby utworzył ścieżkę „app / img /” i umieścił pliki w tym katalogu Względem dowolnego katalogu tymczasowego, który zostanie utworzony po uruchomieniu pliku .exe. Pomyśl o tym jako CTRL + V

Poniżeja = Analysis([main... ustawiłemdatas=added_files, pierwotnie tak było,datas=[]ale chcemy, aby lista importów była, cóż, zaimportowana, więc przekazujemy nasze niestandardowe importy.

Nie musisz tego robić, chyba że chcesz mieć określoną ikonę dla EXE, na dole pliku specyfikacji mówię Pyinstaller, aby ustawił ikonę mojej aplikacji na exe z opcją icon='app\\img\\app_icon.ico'.

added_files = [
    ('app/img/app_icon.ico','app/img/')
]
a = Analysis(['main.py'],
             pathex=['D:\\Github Repos\\Processes-Killer\\Process Killer'],
             binaries=[],
             datas=added_files,
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          [],
          name='Process Killer',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=True , uac_admin=True, icon='app\\img\\app_icon.ico')

Kompilacja do EXE

Jestem bardzo leniwy; Nie lubię pisać rzeczy bardziej niż muszę. Utworzyłem plik .bat, który mogę po prostu kliknąć. Nie musisz tego robić, ten kod będzie działał w powłoce wiersza polecenia bez niego.

Ponieważ plik .spec zawiera wszystkie nasze ustawienia i argumenty kompilacji (czyli opcje), musimy tylko przekazać ten plik .spec do Pyinstaller.

pyinstaller.exe "Process Killer.spec"
Jtonna
źródło
0

Innym rozwiązaniem jest utworzenie punktu zaczepienia środowiska wykonawczego, które kopiuje (lub przenosi) dane (pliki / foldery) do katalogu, w którym przechowywany jest plik wykonywalny. Haczyk to prosty plik w Pythonie, który może zrobić prawie wszystko, tuż przed uruchomieniem aplikacji. Aby to ustawić należy skorzystać z --runtime-hook=my_hook.pyopcji pyinstaller. Tak więc, jeśli twoje dane są folderem obrazów , powinieneś uruchomić polecenie:

pyinstaller.py --onefile -F --add-data=images;images --runtime-hook=cp_images_hook.py main.py

Plik cp_images_hook.py może wyglądać mniej więcej tak:

import sys
import os
import shutil

path = getattr(sys, '_MEIPASS', os.getcwd())

full_path = path+"\\images"
try:
    shutil.move(full_path, ".\\images")
except:
    print("Cannot create 'images' folder. Already exists.")

Przed każdym uruchomieniem folder obrazów jest przenoszony do bieżącego katalogu (z folderu _MEIPASS), więc plik wykonywalny zawsze będzie miał do niego dostęp. W ten sposób nie ma potrzeby modyfikowania kodu projektu.

Drugie rozwiązanie

Możesz skorzystać z mechanizmu runtime-hook i zmienić bieżący katalog, co według niektórych deweloperów nie jest dobrą praktyką, ale działa dobrze.

Kod haka można znaleźć poniżej:

import sys
import os

path = getattr(sys, '_MEIPASS', os.getcwd())   
os.chdir(path)
tsahmatsis
źródło