首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >PyInstaller,如何从pip安装的外部包中包含数据文件?

PyInstaller,如何从pip安装的外部包中包含数据文件?
EN

Stack Overflow用户
提问于 2017-09-28 16:58:57
回答 7查看 10.6K关注 0票数 22

问题

我正在尝试使用PyInstaller来创建一个应用程序,供我的公司内部使用。该脚本在工作的python环境中工作得很好,但在转换到包时却丢失了一些东西。

我知道如何在包中包含和引用我自己需要的数据文件,但是我在导入时包含或引用应该输入的文件时遇到了问题。

我正在使用一个名为传统知识-工具的可点对点安装包,其中包括一些用于面板类显示器(看起来像LED)的不错的图像。问题是,当我创建一个pyinstaller脚本时,每当引用其中一个映像时,我都会得到一个错误:

代码语言:javascript
复制
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创建

代码语言:javascript
复制
# -*- 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选项,结果也是一样。

部分解

如果我使用datas = [('C:\\_virtualenv\\aspen\\Lib\\site-packages\\tk_tools\\img\\', 'tk_tools\\img\\')]datas=datasAnalysis中“手动”添加到规范文件的路径,那么所有这些都可以按照预期工作。这是可行的,但是我宁愿PyInstaller找到包数据,因为它已经清楚地安装了。我会继续寻找一个解决方案,但是--就目前而言--我可能会使用这个非理想的解决方法。

如果你控制了包裹..。

然后您可以在子包上使用绞线,但是只有当它是您自己的包时才能工作。

EN

回答 7

Stack Overflow用户

回答已采纳

发布于 2017-10-19 11:05:42

编辑以添加

为了更永久地解决这个问题,我创建了一个名为stringify的pip可安装包,它将接受一个文件或目录,并将其转换为python字符串,以便像pyinstaller这样的包将它们识别为本机python文件。

查看项目页,欢迎反馈!

原始答案

答案是有点迂回,它处理的是tk_tools的打包方式,而不是pyinstaller。

最近有人让我意识到一种技术,在这种技术中,二进制数据(如图像数据)可以存储为base64字符串:

代码语言:javascript
复制
with open(img_path, 'rb') as f:
    encoded_string = base64.encode(f.read())

编码的字符串实际上存储数据。如果原始包只是将包文件存储为字符串而不是图像文件,并创建一个python文件,该数据可作为字符串变量访问,那么可以简单地将包中的二进制数据包含在pyinstaller将在不进行干预的情况下查找和检测的表单中。

考虑以下职能:

代码语言:javascript
复制
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
EN

Stack Overflow用户

发布于 2018-01-02 22:21:23

我利用规范文件是被执行的Python代码这一事实解决了这个问题。您可以在PyInstaller构建阶段动态获取包的根,并在datas列表中使用该值。在我的例子中,我的.spec文件中有这样的内容:

代码语言:javascript
复制
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的参数。

票数 7
EN

Stack Overflow用户

发布于 2020-02-22 22:45:49

这里有一个使用转弯提到的相同想法的一行程序。在我的例子中,我需要一个在kivy_garden内部的包(zbarcam)。但我试着在这里概括一下这个过程。

代码语言:javascript
复制
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))
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/46474588

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档