PyInstaller(--onefile)によるデータファイルのバンドル


107

画像とアイコンを含めるPyInstallerで1ファイルのEXEを構築しようとしています。私の人生でそれを使うことはできません--onefile

私がやれば、--onedirすべてうまくいきます。を使用する--onefileと、参照されている追加ファイルが見つかりません(コンパイル済みのEXEを実行している場合)。DLLと他のすべてが正常に検出されますが、2つのイメージは検出されません。

EXE(\Temp\_MEI95642\たとえば)を実行したときに生成されたtemp-dirを調べたところ、ファイルは実際にそこにあります。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の更新により、新しい回答を正解としてマークしました。


11
どうもありがとうございます!ここ(a.datas += ...)の線は、本当に役に立ちました。pyinstallerのドキュメントでは使用について説明してCOLLECTいますが、使用時にファイルをバイナリに入れることができません--onefile
Igor Serebryany

@IgorSerebryany:出向!私はまったく同じ問題を抱えていました。
Hubro 2012年

使用した場合、メニューバーをクリックすると.exeがクラッシュします
iacopo

1
実行が完了すると一時フォルダーが消えることを考慮に入れてください。その中身を確認するには、プログラムmain
hithwen

その場所の周りで見たように見えるTree()構文を使用する方法もありますか?
fatuhoku 2013年

回答:


152

PyInstallerの新しいバージョンではenv変数を設定しないため、Shishの優れた答えは機能しません。これで、パスは次のように設定され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)

10
Python 2.7でpyinstaller 2を使用していますが_MEIPASS2、envs にはありませんが、正常にsys._MEIPASS動作するため、+ 1します。私は提案します:path = getattr(sys, '_MEIPASS', os.getcwd())
kbec

2
+1。それをありがとう。しばらく_MEIPASS2環境変数を使用しようとしましたが、環境変数であるときにコードを書いたことは明らかです。最近リコンパイルしたとき、突然止まりました。
Favil Orbedios 2013年

8
AttributeErrorより一般的な代わりにキャッチすることをお勧めしExceptionます。sysインポートが見つからず、暗黙的にデフォルトのパスがになるという問題に遭遇しましたos.path.abspath
アーロン

あなたは私の問題を解決するのを助けることができるかもしれません。
Phillip

1
_MEIPASSが設定されていない場合に現在の作業ディレクトリを使用するのは間違っています。私の答えをください。
Jonathon Reinhart 2017年

51

pyinstallerはデータを一時フォルダーに解凍し、このディレクトリパスを_MEIPASS2環境変数に格納します。_MEIPASS2パックモードでディレクトリを取得し、アンパック(開発)モードでローカルディレクトリを使用するには、次のようにします。

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"

10
どのように、いつ、どこでそれを使用しますか?
gseattle 2012

4
このスクリプトは、PyInstallerでコンパイルしようとしている.pyファイルで使用する必要があります。このコードスニペットを.specファイルに記述しないでください。機能しません。通常入力するパスをに置き換えて、ファイルにアクセスしますresource_path("file_to_be_accessed.mp3")。PyInstallerの現在のバージョンではmax 'answerを使用する必要があることに注意してください。
Exeleration-G 2013

1
この回答は--one-fileオプションの使用に固有ですか?
fatuhoku 2013年

あなたは私の問題を解決するのを助けることができるかもしれません。
Phillip

_MEIPASSが設定されていない場合に現在の作業ディレクトリを使用するのは間違っています。私の答えをください。
Jonathon Reinhart 2017年

30

他のすべての回答は、アプリケーションが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)

ジョンに感謝します。あなたの機能は、今日問題を解決するのに役立ちました。質問があります。_MEIPASSの代わりに_MEIPASS2と書く人がいるのはなぜですか?以前に2を追加しようとしたが、うまくいきませんでした。
Ahmed AbdelKhalek 2018年

os.env['_MEIPASS2'](OS環境を使用して)ディレクトリを取得する古い方法だったと思います。sys._MEIPASS現在、AFAIK が唯一の正しい方法です。
Jonathon Reinhart

こんにちは、あなたの解決策resource_path()は、メインスクリプトにあるときにうまく機能します。代わりにモジュールで書かれているときにそれを機能させる方法はありますか?現在のところ、メインではなく、モジュールがあるフォルダー内のリソースを取得しようとします。
ギモート2018

1
@Guimouteは、この回答のコードが設計どおりに機能しているように聞こえます__file__。使用するため、リソースを提供するモジュールにリソースをローカライズします。モジュールが自身のスコープ外のリソースにアクセスできるようにしたい場合は、インポートするコードがモジュールに使用するためのパスを提供するように実装する必要があります。たとえば、「set_resource_location()」呼び出しを提供するモジュールによってインポーター呼び出し-これはpyinstallerを使用しない場合の作業方法と何ら違いはないと考えてください。
Barny

@barnyヒントをありがとう!それらに沿って試してみます。
ギモート

14

多分私は一歩逃したか何か間違ったことをしましたが、上記の方法はPyInstallerでデータファイルを1つのexeファイルにバンドルしませんでした。私が行った手順を共有しましょう。

  1. ステップ:sysおよびosモジュールをインポートして、上記のメソッドの1つを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-makespec file.pyをコンソールに書き込んで、file.specファイルを作成します。

  3. ステップ:Notepad ++で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. 手順:上記の手順を実行し、スペックファイルを保存しました。最後にコンソールを開いて、pyinstaller file.spec(私の場合、file = Converter-GUI)を書き込みます。

結論:distフォルダーにはまだ複数のファイルがあります。

注:Python 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. ステップ:コードがパスを必要とする場所に関数を呼び出す上記の変数を記述します。私の場合は:

        self.window.iconbitmap(image_path)
  4. 手順:Pythonファイルと同じディレクトリでコンソールを開き、以下のようなコードを記述します。

        pyinstaller --onefile your_file.py
  5. ステップ:pythonファイルの.specファイルを開き、a.datas配列を追加して、3番目のステップで編集する前に上記で指定したexeクラスにアイコンを追加します。
  6. ステップ:パスファイルを保存して終了します。specファイルとpyファイルを含むフォルダーに移動します。もう一度コンソールウィンドウを開き、次のコマンドを入力します。

        pyinstaller your_file.spec

6.ステップの後、1つのファイルを使用する準備が整います。


@dildeに感謝!a.datas手順5 の配列は文字列のタプルを取ることに注意してください。参照としてpythonhosted.org/PyInstaller/spec-files.html#adding-data-filesを参照してください。
イアンキャンベル

申し訳ありません、私はあなたのメッセージの一部を理解することもできません。その部分は「それはa.datas ....ではありません」「a.datasに注意してください...」と書きましたか?a.datas配列に文字列を追加することについて書いた内容に問題はありますか?
dildeolupbiten

おっとっと!それは注意すべきです...ええ、タイプミスについてすみません。あなたのステップは私にとってうまくいきました:)、私は彼らのドキュメントや他のどこにもこの情報を見つけることができませんでした。
イアンキャンベル

8

代わりに、提案されているようにすべてのパスコードを書き換えるために、作業ディレクトリを変更しました。

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

これらの2行をコードの先頭に追加するだけで、残りをそのままにすることができます。


2
いいえ。良いアプリケーションが作業ディレクトリを変更することはめったにありません。より良い方法があります。
Jonathon Reinhart 2017年

3

受け入れられた回答を少し変更。

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)

_MEIPASSが設定されていない場合に現在の作業ディレクトリを使用するのは間違っています。私の答えをください。
Jonathon Reinhart 2017年

1

PyInstallerに関して私が見た最も一般的な不満/質問は、「コードがバンドルに確実に含まれているデータファイルを見つけることができない、それはどこにあるのか」というもので、コードの内容/場所を確認するのは簡単ではありません。抽出されたコードは一時的な場所にあり、終了すると削除されるため、が検索しています。するために、コードのこのビットを追加参照して、あなたのonefileに含まれていると、@Jonathonラインハルトのを使用して、それがどこにある何resource_path()

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

0

私は既存の答えが混乱していることに気づき、問題がどこにあるかを解明するのに長い時間を費やしました。これが私が見つけたすべての編集です。

アプリを実行すると、エラーが発生しますFailed to execute script foofoo.pyメインファイルの場合)。これをトラブルシューティングするには、PyInstallerを実行しないでください--noconsole(または編集main.specしてconsole=False=> を変更しますconsole=True)。これで、コマンドラインから実行可能ファイルを実行すると、失敗が表示されます。

最初に確認することは、余分なファイルが正しくパッケージ化されていることです。('x', 'x')フォルダーxを含めたい場合は、タプルを追加する必要があります。

クラッシュした後は、[OK]をクリックしないでください。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コードは、tempディレクトリーではなく実行可能ファイルのディレクトリーでファイルを探しています。

それを修正するために、この質問の答えはどれでも機能します。個人的に、私はそれらすべての組み合わせが機能することを発見しました:メインのエントリポイントファイルで最初に条件付きでディレクトリを変更し、それ以外はすべてそのまま機能します:

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


1
ごめんなさい。しかし、ディレクトリの変更は決して正しい答えではありません。ユーザーがアプリケーションへのパスを提供する場合、現在のディレクトリからの相対パスであると期待します。それを変えれば間違いです。
Jonathon Reinhart 2017

0

それでも、tempディレクトリではなく実行可能ファイルに関連するファイルを配置しようとしている場合は、自分でコピーする必要があります。これは私がそれをやり遂げた方法です。

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

DISTPATH変数にファイルシステムコピーを実行するステップをスペックファイルに追加します。

お役に立てば幸いです。


0

私はこの問題を長い間(まあ、とても長い間)扱ってきました。私はほとんどすべての情報源を検索しましたが、物事は私の頭の中にパターンで入っていませんでした。

最後に、私は従うべき正確な手順を理解したと思います。共有したかったのです。

なお、私の回答では、この質問に対する他の回答の情報を使用しています。

Pythonプロジェクトのスタンドアロン実行可能ファイルを作成する方法。

project_folderがあり、ファイルツリーが次のようになっているとします。

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

まず、変数へのパスsound/img/フォルダーを次のように定義したsound_dirimg_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を使用している場合は、仮想環境をアクティブにします。

まだインストールしていない場合は、pyinstallerをインストールします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")

]

次に、の行datas=[],datas=added_files,

実行される操作の詳細については、こちらmain.spec参照してください。

走る pyinstaller --onefile main.spec

そして、それがすべてで、あなたは実行することができますmainproject_folder/distそのフォルダ内に他に何もなくても、どこからでも。そのmainファイルのみを配布できます。これは、真のスタンドアロンです。


@JonathonReinhartあなたがまだいるなら、私はあなたの関数を私の答えで使用したことをお知らせしたいと思います。
ムユスタン

0

以下からの優秀な答えは使用マックスこの後の画像やサウンド&私自身の研究/テストのような余分なデータファイルを追加する方法については、私はそのようなファイルを追加する最も簡単な方法であると考えているものを考え出しました。

あなたはライブの例を参照したい場合は、私のリポジトリがあり、ここで GitHubの上で。

注:これは、pyinstallerで--onefileor -Fコマンドを使用してコンパイルするためのものです。

私の環境は次のとおりです。


2つのステップで問題を解決する

この問題を解決するには、アプリケーションに「バンドル」する必要のある追加のファイルがあることをPyinstallerに明確に通知する必要があります。

また、「相対」パスを使用する必要があります。これにより、PythonスクリプトまたはフリーズされたEXEとして実行されているアプリケーションを適切に実行できます。

そうは言っても、相対パスを持つことができる関数が必要です。Maxが投稿した関数を使用すると、相対パスを簡単に解決できます。

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)

上記の関数をこのように使用して、アプリがスクリプトまたはフリーズされたEXEとして実行されているときにアプリケーションアイコンが表示されるようにします。

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

次のステップは、アプリケーションの実行時にtempディレクトリに作成されるように、コンパイル時に追加ファイルを見つける場所をPyinstallerに指示する必要があることです。

ドキュメントに示されているように、この問題を2つの方法で解決できますが、私は自分の.specファイルを管理することを好みます。

まず、すでに.specファイルが必要です。私の場合、pyinstaller追加の引数を使用して実行することで、必要なものを作成できましここで追加の引数を見つけることができます。このため、スペックファイルはあなたのスペックファイルと少し異なるように見えるかもしれませんが、重要な部分を説明した後、参照用にすべてを掲載します。

added_filesは、基本的にタプルのを含むリストである私の場合、私は、単一の画像を追加したいんだけど、あなたが使用して複数のICOの、PNGのかJPGのを追加することができます('app/img/*.ico', 'app/img')ので、同様にあなたはまた、別のタプルを作成することもできadded_files = [ (), (), ()]、複数の輸入を持っています

タプルの最初の部分では、追加するファイルまたはファイルのタイプと、それらを見つける場所を定義します。これをCTRL + Cと考えてください

タプルの2番目の部分は、パスを「app / img /」にして、.exeを実行したときに作成される一時ディレクトリに相対するようにファイルを配置するようにPyinstallerに指示します。これをCTRL + Vと考えてください

の下でa = Analysis([main...、私はを設定しましたがdatas=added_files、元々はそれdatas=[]でしたが、インポートのリストをインポートしたいので、カスタムインポートを渡します。

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ファイルにはすべてのコンパイル設定と引数(別名オプション)が含まれているので、その.specファイルをPyinstallerに渡すだけです。

pyinstaller.exe "Process Killer.spec"

0

別の解決策は、実行可能ファイルが格納されているディレクトリにデータ(ファイル/フォルダー)をコピー(または移動)するランタイムフックを作成することです。フックは、アプリの実行直前に、ほとんど何でもできる単純なpythonファイルです。これを設定するには、--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.")

すべての実行前に、imagesフォルダーは現在のディレクトリ(_MEIPASSフォルダーから)に移動されるため、実行可能ファイルは常にそれにアクセスできます。この方法では、プロジェクトのコードを変更する必要はありません。

2番目のソリューション

ランタイムフックメカニズムを利用して現在のディレクトリを変更することができます。これは一部の開発者にとっては良い習慣ではありませんが、正常に動作します。

フックコードは以下にあります:

import sys
import os

path = getattr(sys, '_MEIPASS', os.getcwd())   
os.chdir(path)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.