programing

PyInstaller로 데이터 파일 번들(--파일 하나)

bestprogram 2023. 6. 6. 10:25

PyInstaller로 데이터 파일 번들(--파일 하나)

PyInstaller로 이미지와 아이콘을 포함하는 단일 파일 EXE를 구축하려고 합니다.아무리 해도 그것을 작동시킬 수 없습니다.--onefile.

내가 하면,--onedir모든 것이 매우 잘 작동합니다.를 할 때--onefile컴파일된 EXE를 실행할 때 참조된 추가 파일을 찾을 수 없습니다.DLL과 다른 모든 것이 정상이며, 두 이미지만 정상이 아닙니다.

할 때 EXE를 할 때 생성되는 temp-dir)에 \Temp\_MEI95642\예를 들어) 파일이 실제로 있습니다.EXE를 임시 디렉토리에 놓으면 해당 디렉토리가 나타납니다.매우 당황스럽습니다.

이 제가 이이제추것다에 입니다..spec

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

하위 폴더에도 넣지 않으려고 노력했지만 아무런 차이가 없었습니다.

편집: PyInstaller 업데이트로 인해 새로운 답변이 올바른 것으로 표시되었습니다.

에서는 "PyInstaller"를 .env더 이상 변수가 되지 않기 때문에 시쉬의 훌륭한 답변은 통하지 않을 것입니다.이제 경로는 다음과 같이 설정됩니다.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)

폴더에 하고 이 를 pyinstaller에 합니다._MEIPASS2환경 변수입니다.▁the를 _MEIPASS2 (모드에서 packed-mode에서 unpacked (개발) 모드에서 사용합니다.

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

출력:

# 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"

다른 모든 답변은 응용 프로그램이 PyInstalled(즉, PyInstalled)가 아닌 경우 현재 작업 디렉토리를 사용합니다.sys._MEIPASS설정되지 않음).스크립트가 있는 디렉토리가 아닌 다른 디렉토리에서 응용프로그램을 실행할 수 없도록 하기 때문에 잘못된 것입니다.

더 나은 솔루션:

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)

는 이 문제를 오랫동안 다루었습니다.저는 거의 모든 출처를 찾아봤지만, 제 머릿속에 어떤 패턴이 떠오르지 않았습니다.

마지막으로, 저는 제가 따라야 할 정확한 단계를 알아냈다고 생각합니다, 저는 공유하고 싶었습니다.

참고로, 내 대답은 이 질문에 대한 다른 사람들의 대답에 대한 정보를 사용합니다.

파이썬 프로젝트의 독립 실행형 실행 파일을 만드는 방법.

project_folder가 있고 파일 트리가 다음과 같다고 가정합니다.

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

우선, 다음과 같은 경로를 정의했다고 가정해 보겠습니다.sound/그리고.img/를 변수로 지정합니다.sound_dir그리고.img_dir다음과 같이:

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

다음과 같이 변경해야 합니다.

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

어에디,resource_path()스크립트의 맨 위에 다음과 같이 정의됩니다.

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)

venv를 사용하는 경우 가상 환경을 활성화합니다.

하지 않은 다음을 를 설치합니다.pip3 install pyinstaller.

실행:pyi-makespec --onefile main.py컴파일 및 빌드 프로세스를 위한 사양 파일을 만듭니다.

파일 계층이 다음으로 변경됩니다.

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

) 기에열 (디어포함포)함)main.spec:

맨 위에 다음을 삽입합니다.

added_files = [

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

]

다음,▁line다의 행을 합니다.datas=[],datas=added_files,

다음날 에수작세정에서 수행된 에 대한 .main.spec여기를 보세요.

려달을 합니다.pyinstaller --onefile main.spec

은 그다야게수넌, 있어갈도망고그리,있▁run,mainproject_folder/dist폴더에 다른 것을 가지고 있지 않고 어디서든.은 그 배포할 수 .main파일입니다. 이제 진정한 독립 실행형입니다.

아마도 내가 한 단계를 놓쳤거나 잘못한 것 같지만 위의 방법들은 PyInstaller가 포함된 데이터 파일을 하나의 exe 파일로 번들하지 않았습니다.제가 한 단계를 공유하겠습니다.

  1. 단계:sys 및 os 모듈 가져오기를 사용하여 위의 방법 중 하나를 py 파일에 기록합니다.둘 다 해봤어요.마지막은 다음과 같습니다.

    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. 단계: 콘솔에 pyi-interspec file.py 를 작성하여 file.spec 파일을 만듭니다.

  3. 단계: 아래와 같은 데이터 파일을 추가하려면 메모장++로 file.spec을 엽니다.

    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. step: 위의 단계에 따라 사양 파일을 저장했습니다.마침내 콘솔을 열고 pyinstaller file.spec(내 경우 file=Syslog-GUI)을 씁니다.

결론:아직 dist 폴더에 파일이 두 개 이상 있습니다.

참고: 저는 파이썬 3.5를 사용하고 있습니다.

편집: 마지막으로 조나단 라인하트의 방법과 함께 작동합니다.

  1. 단계: sys와 os를 가져오는 python 파일에 아래 코드를 추가합니다.

    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. 단계: 파일 경로를 추가하여 위 함수를 호출합니다.

    image_path = resource_path("Converter-GUI.ico")
    
  3. step: 코드가 경로를 필요로 하는 곳으로 함수를 호출하는 위의 변수를 작성합니다.제 경우에는 다음과 같습니다.

        self.window.iconbitmap(image_path)
    
  4. 단계: Python 파일의 동일한 디렉토리에서 콘솔을 열고 아래와 같은 코드를 작성합니다.

        pyinstaller --onefile your_file.py
    
  5. step: python 파일의 .spec 파일을 열고 a.datas 배열을 추가한 후 위의 exe 클래스에 아이콘을 추가합니다. 이것은 3번째 단계에서 편집하기 전에 주어졌습니다.
  6. 단계: 경로 파일을 저장하고 종료합니다.사양 및 py 파일이 포함된 폴더로 이동합니다.콘솔 창을 다시 열고 다음 명령을 입력합니다.

        pyinstaller your_file.spec
    

6. 단계 후에 한 파일을 사용할 준비가 되었습니다.

이미지나 사운드와 같은 추가 데이터 파일을 추가하는 것에 대한 Max와 이 게시물의 훌륭한 답변을 사용하여, 저는 이러한 파일을 추가하는 가장 쉬운 방법이 무엇이라고 생각하는지 알아냈습니다.

라이브 예제를 보려면 GitHub에 있는 저장소를 확인하십시오.

참고: 이는 다음을 사용하여 컴파일하기 위한 것입니다.--onefile또는-Fpyinstaller를 사용한 명령입니다.

저의 환경은 다음과 같습니다.

  • 파이썬 3.3.7
  • Tkinter 8.6(버전 확인)
  • 파이 설치 프로그램 3.6

2단계로 문제 해결

이 문제를 해결하려면 Pyinstaller에게 애플리케이션과 함께 "번들"해야 하는 추가 파일이 있음을 구체적으로 알려야 합니다.

또한 애플리케이션이 Python 스크립트 또는 Frozen EXE로 실행될 때 제대로 실행될 수 있도록 '상대적' 경로를 사용해야 합니다.

그런 말이 나왔으니 상대적인 경로를 가질 수 있는 기능이 필요합니다.Max Posted 기능을 사용하면 상대 경로 지정을 쉽게 해결할 수 있습니다.

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)

우리는 위의 기능을 이렇게 사용하여 앱이 Script 또는 Frozen EXE로 실행될 때 애플리케이션 아이콘이 표시되도록 할 것입니다.

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

다음 단계는 Pyinstaller에게 컴파일할 때 추가 파일을 찾는 위치를 알려주어 응용 프로그램이 실행될 때 임시 디렉토리에 추가 파일이 생성되도록 해야 합니다.

이 문제는 설명서에 나와 있는 것처럼 두 가지 방법으로 해결할 수 있지만, 저는 개인적으로 .spec 파일을 관리하는 것을 선호하기 때문에 그렇게 할 것입니다.

먼저 .spec 파일이 이미 있어야 합니다.저의 경우, 실행함으로써 필요한 것을 생성할 수 있었습니다.pyinstaller여분의 알그가 있으면, 당신은 여기에서 여분의 알그를 찾을 수 있습니다.이 때문에, 제 스펙 파일은 당신의 것과 조금 다르게 보일 수도 있지만, 중요한 부분을 설명한 후 참고를 위해 모두 게시합니다.

added_files는 본질적으로 Tuple의 이미지를 포함하는 목록입니다. 저의 경우 단일 이미지만 추가하고 싶지만, 여러 개의 아이콘, png 또는 jpg를 추가할 수 있습니다.('app/img/*.ico', 'app/img')당신은 so 과음같다튜른만수있습도다니다들플과 같은 다른 .added_files = [ (), (), ()]이 여러 개인

튜플의 첫 번째 부분은 추가할 파일 또는 파일 유형과 파일을 찾을 위치를 정의합니다.CTRL+C라고 생각합니다.

튜플의 두 번째 부분은 Pyinstaller에게 경로를 'app/img/'로 만들고 .exe를 실행할 때 생성되는 임시 디렉터리와 관련하여 해당 디렉터리에 파일을 배치하라고 말합니다.이것을 CTRL+V라고 생각합니다.

아래에, 나는 설정했습니다.datas=added_files원래 그것은datas=[]하지만 우리는 수입품 목록이 수입되기를 원하기 때문에 우리는 맞춤 수입품을 전달합니다.

한 이 . 의 맨 에게 EXE에 내 을 EXE 사양 파일 하단에서 Pyinstaller에게 옵션으로 EXE에 대한 내 응용 프로그램 아이콘을 설정하라고 말합니다.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')

EXE로 컴파일 중

저는 매우 게으릅니다. 저는 타자 치는 것을 필요 이상으로 좋아하지 않습니다.그냥 클릭할 수 있는 .bat 파일을 만들었습니다.이렇게 할 필요가 없습니다. 이 코드는 명령 프롬프트 셸에서 실행됩니다. 코드 없이도 문제가 없습니다.

.spec 파일에는 컴파일 설정 및 인수(일명 옵션)가 모두 포함되어 있으므로 Pyinstaller에게 해당 .spec 파일을 제공하면 됩니다.

pyinstaller.exe "Process Killer.spec"

제안된 대로 모든 경로 코드를 다시 쓰는 대신 작업 디렉토리를 변경했습니다.

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

코드 시작 부분에 두 줄만 추가하면 나머지는 그대로 둘 수 있습니다.

승인된 답변에 약간의 수정이 있습니다.

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)

또 다른 해결책은 실행 파일이 저장된 디렉토리에 데이터(파일/폴더)를 복사(또는 이동)하는 런타임 후크를 만드는 것입니다.후크는 앱 실행 직전에 거의 모든 것을 할 수 있는 간단한 파이썬 파일입니다.설하려다사합니다야용해를 .--runtime-hook=my_hook.pyPyinstaller 옵션.따라서 데이터가 이미지 폴더인 경우 다음 명령을 실행해야 합니다.

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

cp_images_hook.py는 다음과 같습니다.

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.")

실행할 때마다 이미지 폴더가 현재 디렉터리(_MEIPASS 폴더에서)로 이동되므로 실행 파일은 항상 해당 디렉터리에 액세스할 수 있습니다.그런 식으로 프로젝트 코드를 수정할 필요가 없습니다.

세컨드 솔루션

런타임 후크 메커니즘을 활용하여 현재 디렉터리를 변경할 수 있는데, 이는 일부 개발자에 따르면 좋은 방법은 아니지만 잘 작동합니다.

후크 코드는 아래에 있습니다.

import sys
import os

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

PyInstaller에서 본 가장 일반적인 불만 사항/질문은 "내 코드가 번들에 확실히 포함된 데이터 파일을 찾을 수 없습니다. 어디에 있습니까?"이며, 추출된 코드가 임시 위치에 있고 코드가 종료될 때 제거되기 때문에 코드가 무엇을/어디서 검색하고 있는지 확인하기가 쉽지 않습니다.@Jonathon Reinhart's를 사용하여 이 코드를 추가하여 하나의 파일에 포함된 내용과 파일 위치를 확인합니다.resource_path()

for root, dirs, files in os.walk(resource_path("")):
    print(root)
    for file in files:
        print( "  ",file)

임시 디렉토리가 아닌 실행 파일과 관련된 파일을 저장하려는 경우 직접 복사해야 합니다.이것이 제가 그것을 끝내는 방법입니다.

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

파일 시스템을 DISTPATH 변수에 복사하는 단계를 규격 파일에 추가합니다.

도움이 되길 바랍니다.

나는 최대 솔루션을 기반으로 이것을 사용합니다.

def getPath(filename):
    import os
    import sys
    from os import chdir
    from os.path import join
    from os.path import dirname
    from os import environ
    
    if hasattr(sys, '_MEIPASS'):
        # PyInstaller >= 1.6
        chdir(sys._MEIPASS)
        filename = join(sys._MEIPASS, filename)
    elif '_MEIPASS2' in environ:
        # PyInstaller < 1.6 (tested on 1.5 only)
        chdir(environ['_MEIPASS2'])
        filename = join(environ['_MEIPASS2'], filename)
    else:
        chdir(dirname(sys.argv[0]))
        filename = join(dirname(sys.argv[0]), filename)
        
    return filename

드디어! 루카의 솔루션을 기반으로 합니다.

저는 마침내 --onefile pyinstaller에 포함된 디렉토리 내의 파일을 실행할 수 있었습니다.

그가 게시한 방법을 사용하면 파일에 전체 디렉터리 이름을 입력할 수도 있지만 여전히 작동합니다.

두 번째로 필요한 것은 실제로 pyinstaller에 원하는 디렉터리/파일을 추가하는 것입니다.다음과 같은 인수를 사용합니다.

--add-data "yourDir/And/OrFile;yourDir"

저는 기존의 답들이 혼란스러웠고, 문제가 어디에 있는지 알아내는 데 오랜 시간이 걸렸습니다.여기 제가 발견한 모든 것을 정리한 것입니다.

할 때 합니다. ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠFailed to execute script foo(만약에foo.py는 기본 파일입니다.이하려면 PyInstaller와 PyInstaller를 --noconsole 편집)main.spec 바꾸다console=False=>console=True을 사용하여 명령줄에서 실행 파일을 실행하면 오류가 나타납니다.

먼저 확인해야 할 것은 추가 파일이 올바르게 포장되어 있는지 확인하십시오.다음과 같은 튜플을 추가해야 합니다.('x', 'x') 폴더를 .x포함될 것입니다.

충돌 후에는 확인을 클릭하지 않습니다.Windows(윈도우)에 있는 경우 모두 검색을 사용할 수 있습니다.파일 중 하나를 찾습니다(예: sword.png파일의 압축을 푼 임시 경로를 찾아야 합니다(예: C:\Users\ashes999\AppData\Local\Temp\_MEI157682\images\sword.png이 디렉터리를 검색하여 모든 디렉터리가 포함되었는지 확인할 수 있습니다.이런 식으로 찾을 수 없다면, 다음과 같은 것을 찾으십시오.main.exe.manifest (Windows) 는python35.dll(Python 3.5를 사용하는 경우).

설치 프로그램에 모든 것이 포함되어 있는 경우 다음으로 발생할 수 있는 문제는 파일 I/O입니다. Python 코드는 임시 디렉토리가 아닌 실행 파일의 디렉토리에서 파일을 찾고 있습니다.

이 문제를 해결하기 위해서는 이 질문에 대한 모든 대답이 작동합니다.개인적으로, 저는 이 모든 것이 혼합되어 있다는 것을 발견했습니다. 기본 진입점 파일에서 먼저 디렉토리를 조건부로 변경하고, 그 외의 모든 것은 그대로 작동합니다.

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

여전히 최신 답변을 찾고 있는 고객을 위해 다음과 같이 제공합니다.

설명서에는 추가된 데이터 파일에 액세스하는 방법에 대한 섹션이 있습니다.
이것이 그것의 짧고 달콤한 부분입니다.


하고 싶을 것입니다import pkgutil데이터 파일을 추가한 폴더, 즉 사양 파일에 추가된 튜플의 두 번째 문자열을 찾습니다.

datas = [("path/to/mypackage/data_file.txt", "path/to/mypackage")]

데이터 파일을 추가한 위치를 알면 이진 데이터로 읽고 원하는 대로 디코딩할 수 있습니다.다음 예를 들어 보겠습니다.

파일 구조:

mypackage
      __init__.py  # This is a MUST in order for the package to be registered
      data_file.txt  # The data file you've added

data_file.txt

Hello world!

main.py

import pkgutil

file = pkgutil.get_data("mypackage", "data_file.txt")
contents = file.decode("utf-8")
print(contents)  # Hello world!

참조:

@호기심_85

저는 평판이 좋지 않아서 직접 대답할 수 없습니다(매우 귀찮습니다)..spec에 대해서도 같은 문제가 있었고 다음과 같은 방법으로 해결했습니다.

  • ("사운드", "사운드")를 제거합니다(게임에 사운드가 없습니다).
  • 내 경우: ("imgs", "imgs")를 내 이미지가 있는 폴더의 이름("Images", "Images")으로 대체합니다.
  • 마지막으로 pyinstaller main을 사용합니다.스펙

이 단계를 따름으로써 저는 그것을 작동시킬 수 있었습니다.또한 세 번째 단계는 이제 하나의 파일이 기본값인 것처럼 보이기 때문에 마법처럼 작동했습니다(하나의 파일 또는 하나의 dir를 지정할 필요가 없습니다).

언급URL : https://stackoverflow.com/questions/7674790/bundling-data-files-with-pyinstaller-onefile