web-dev-qa-db-ja.com

PyInstaller、pipによってインストールされた外部パッケージのデータファイルを含める方法

問題

PyInstallerを使用して、社内で使用するアプリケーションを作成しようとしています。このスクリプトは、動作中のpython=環境からはうまく機能しますが、パッケージに変換すると何かが失われます。

自分で必要なデータファイルをパッケージに含めて参照する方法を知っていますが、インポート時に取得する必要があるファイルを含めたり参照したりするのに問題があります。

私は tk-tools と呼ばれるpipインストール可能なパッケージを使用しています。これには、パネルのようなディスプレイ(LEDのように見える)用のいくつかの素晴らしいイメージが含まれています。問題は、pyinstallerスクリプトを作成するときに、これらのイメージの1つが参照されるたびにエラーが発生することです。

_DEBUG:aspen_comm.display:COM23 19200
INFO:aspen_comm.display:adding pump 1 to the pump list: [1]
DEBUG:aspen_comm.display:updating interrogation list: [1]
Exception in Tkinter callback
Traceback (most recent call last):
  File "tkinter\__init__.py", line 1550, in __call__
  File "aspen_comm\display.py", line 206, in add
  File "aspen_comm\display.py", line 121, in add
  File "aspen_comm\display.py", line 271, in __init__
  File "aspen_comm\display.py", line 311, in __init__
  File "lib\site-packages\tk_tools\visual.py", line 277, in __init__
  File "lib\site-packages\tk_tools\visual.py", line 289, in to_grey
  File "lib\site-packages\tk_tools\visual.py", line 284, in _load_new
  File "tkinter\__init__.py", line 3394, in __init__
  File "tkinter\__init__.py", line 3350, in __init__
_tkinter.TclError: couldn't open "C:\_code\tools\python\aspen_comm\dist\aspen_comm\tk_tools\img/led-grey.png": no such file or directory
_

私は最後の行でそのディレクトリ内を調べました-これは私の配布が配置されている場所です-_tk_tools_ディレクトリが存在しないことがわかりました。

質問

インポートしたパッケージのデータファイルをpyinstallerで収集するにはどうすればよいですか?

スペックファイル

現在、私のdatasは空白です。 _pyinstaller -n aspen_comm aspen_comm/__main__.py_で作成されたスペックファイル:

_# -*- mode: python -*-

block_cipher = None


a = Analysis(['aspen_comm\\__main__.py'],
             pathex=['C:\\_code\\tools\\python\\aspen_comm'],
             binaries=[],
             datas=[],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher)

pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)

exe = EXE(pyz,
          a.scripts,
          exclude_binaries=True,
          name='aspen_comm',
          debug=False,
          strip=False,
          upx=True,
          console=True )

coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               name='aspen_comm')
_

_/build/aspen_comm/out00-Analysis.toc_と_/build/aspen_comm/out00-PYZ.toc_を調べたところ、_tk_tools_パッケージで見つかったようなエントリが見つかりました。さらに、_tk_tools_パッケージには、データファイルを見つける前に完全に機能する機能があるため、どこかにインポートされていることがわかりますが、どこにあるのかわかりません。 _tk_tools_を検索すると、ファイル構造内でそれへの参照が見つかりません。

同じ結果で_--hidden-imports_オプションも試してみました。

部分的なソリューション

Analysisdatas = [('C:\\_virtualenv\\aspen\\Lib\\site-packages\\tk_tools\\img\\', 'tk_tools\\img\\')]および_datas=datas_を使用してスペックファイルへのパスを「手動」で追加すると、すべて期待どおりに機能します。これは動作しますが、パッケージデータが明確にインストールされているので、PyInstallerでパッケージデータを見つけたいと思います。私は解決策を探し続けますが、今のところ、この理想的でない回避策を使用するでしょう。

パッケージを制御できる場合...

次に、サブパッケージで stringify を使用できますが、これは自分のパッケージである場合にのみ機能します。

18
slightlynybbled

追加するために編集

この問題をより永続的に解決するために、stringifyという名前のpipインストール可能パッケージを作成しました。これは、ファイルまたはディレクトリを受け取り、python文字列に変換して、pyinstallerなどのパッケージを作成します。ネイティブpythonファイルとして認識します。

プロジェクトページ をチェックしてください。フィードバックは大歓迎です!


元の回答

答えは少し回り道であり、pyinstallerではなく_tk_tools_がパッケージ化される方法を扱います。

最近誰かが、画像データなどのバイナリデータを_base64_文字列として保存できる手法を知った。

_with open(img_path, 'rb') as f:
    encoded_string = base64.encode(f.read())
_

エンコードされた文字列は実際にデータを格納します。元のパッケージがパッケージファイルを画像ファイルではなく文字列として保存し、pythonファイルに文字列変数としてアクセス可能なデータを作成する場合、バイナリデータをpyinstallerが介入なしに見つけて検出する形式のパッケージ。

以下の関数を考えてみましょう:

_def create_image_string(img_path):
    """
    creates the base64 encoded string from the image path 
    and returns the (filename, data) as a Tuple
    """

    with open(img_path, 'rb') as f:
        encoded_string = base64.b64encode(f.read())

    file_name = os.path.basename(img_path).split('.')[0]
    file_name = file_name.replace('-', '_')

    return file_name, encoded_string


def archive_image_files():
    """
    Reads all files in 'images' directory and saves them as
    encoded strings accessible as python variables.  The image
    at images/my_image.png can now be found in tk_tools/images.py
    with a variable name of my_image
    """

    destination_path = "tk_tools"
    py_file = ''

    for root, dirs, files in os.walk("images"):
        for name in files:
            img_path = os.path.join(root, name)
            file_name, file_string = create_image_string(img_path)

            py_file += '{} = {}\n'.format(file_name, file_string)

    py_file += '\n'

    with open(os.path.join(destination_path, 'images.py'), 'w') as f:
        f.write(py_file)
_

archive_image_files()がセットアップファイル内にある場合、_<package_name>/images.py_は、セットアップスクリプトが実行されるたびに(ホイールの作成とインストール中に)自動的に作成されます。

私は近い将来このテクニックを改善するかもしれません。ご協力ありがとうございます。

j

1
slightlynybbled

スペックファイルがPython実行されるコード)であるという事実を利用してこれを解決しました。PyInstallerのビルドフェーズ中にパッケージのルートを動的に取得し、その値をdatasリスト。私の場合、私のようなものがあります.specファイル:

import os
import importlib

package_imports = [['package_name', ['file0', 'file1']]

datas = []
for package, files in package_imports:
    proot = os.path.dirname(importlib.import_module(package).__file__)
    datas.extend((os.path.join(proot, f), package) for f in files)

そして、結果のdatasリストをAnalysisのパラメーターとして使用します。

4
Turn

次のコードは、ディレクトリ内のすべてのPNGファイルを、バンドルされたアプリの最上位にあるimgsというフォルダーに配置します。

_datas=[("C:\\_code\\tools\\python\\aspen_comm\\dist\\aspen_comm\\tk_tools\\img\\*.png", "imgs")],
_

次に、コード内でos.path.join("imgs", "your_image.png")を使用してそれらを参照できます。

1
Steampunkery

Mac OS X 10.7.5 Pyinstaller; app.specファイルの画像ごとに、個別の行ではなく1行のコードを使用して画像を追加します。これが、画像をスクリプトでコンパイルするために使用したすべてのコードです。この関数をyourappname.pyの先頭に追加します。

# Path to the resources, (pictures and files) needed within this program 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)`  
_

また、appname.pyスクリプトで、次のようにこの「resource_path」を追加して、リソースから画像を取得します。

yourimage = PhotoImage(file=resource_path("yourimage.png"))

Appname.specファイルで、 'datas = []を、使用したいイメージへのパスに置き換えます。私は「* .png」画像ファイルのみを使用しましたが、これは私にとってはうまくいきました:

datas=[("/Users/rodman/PycharmProjects/tkinter/*.png", ".")],

/ Users/rodman/PycharmProjects/tkinter /を、画像が保存されているフォルダーへのパスに置き換えてください。ずさんなコードの改ざんを許し、私はこれらのコードタグに慣れていません。このMac OS Xの答えを理解するために正しい方向に導いてくれたSteampunkeryに感謝します。

0
Roddy Under

パーティーには少し遅れましたが、私がこれをどのように行ったかについてのヘルプ記事を書きました:

https://www.linkedin.com/Pulse/loading-external-module-data-during-pyinstaller-bundling-deguzis/?published=t

スニペット:

import os
import pkgutil
import PyInstaller.__main__
import platform
import shutil
import sys


# Get mypkg data not imported automagically
# Pre-create location where data is expected
if not os.path.exists('ext_module'):
    os.mkdir('ext_module')

with open ('ext_module' + os.sep + 'some-env.ini', 'w+') as f:
    data = pkgutil.get_data( 'ext_module', 'some-env.ini' ).decode('utf-8', 'ascii')
    f.write(data)

# Set terminator (PyInstaller does not provide an easy method for this)
# ':' for OS X / Linux
# ';' for Windows
if platform.system() == 'Windows':
    term = ';'
else:
    term = ':'


PyInstaller.__main__.run([
        '--name=%s' % 'mypkg',
        '--onefile',
        '--add-data=%s%smypkg' % (os.path.join('mypkg' + os.sep + 'some-env.ini'),term),
        os.path.join('cli.py'),
    ])


# Cleanup
shutil.rmtree('mypkg')
0
ProfessorKaos64

これは、言及した Turn と同じアイデアを使用したワンライナーです。私の場合、kivy_gardenの中にあるパッケージ(zbarcam)が必要でした。しかし、私はここでプロセスを一般化しようとしました。

from os.path import join, dirname, abspath, split
from os import sep
import glob
import <package>

pkg_dir = split(<package>.__file__)[0]
pkg_data = []
pkg_data.extend((file, dirname(file).split("site-packages")[1]) for file in glob.iglob(join(pkg_dir,"**{}*".format(sep)), recursive=True))
0