无法使用PyInstaller可执行文件导入Geopandas - 虽然在虚拟环境中运行良好

5
当我使用PyInstaller尝试冻结我的Python应用程序并导入Geopandas时,它停止工作。
  • Windows 10
  • PyInstaller 3.3.1
  • Geopandas 0.4
这是源代码:
print("Hello, StackOverflow")
import geopandas as gpd

这是编译后的EXE文件的控制台输出结果:

Hello, StackOverflow
Traceback (most recent call last):
  File "application.py", line 3, in <module>
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "d:\documents\projecttwo\publish\harv_venv1\env\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 631, in exec_module
    exec(bytecode, module.__dict__)
  File "site-packages\geopandas\__init__.py", line 9, in <module>
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "d:\documents\projecttwo\publish\harv_venv1\env\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 631, in exec_module
    exec(bytecode, module.__dict__)
  File "site-packages\geopandas\datasets\__init__.py", line 7, in <module>
StopIteration
[6764] Failed to execute script application

当我尝试在较为复杂的应用程序中导入Geopandas时,出现了相同的控制台输出。Geopandas已经通过PIP在Python 3.6.3虚拟环境中正确安装(我也尝试过版本0.4和0.3),并且在编译之前运行良好(即python application.py顺利运行)。
我尝试从不同的来源(如Gohlke's wheel)安装geopandas和pyinstaller,结果相同。 我还尝试从头开始创建全新的虚拟环境,从Gohlke安装Fiona并从pip安装geopandas。
我怀疑可能需要进行一些隐藏的导入。由于我对PyInstaller相当陌生,因此非常感谢您的任何帮助。
2个回答

7

我遇到了同样的错误,并以不同的方式解决了它。

这个错误是因为包含在geopandas数据集中的.shp文件未被pyinstaller找到而导致的。

我在我的项目中没有使用geopandas数据集,所以我只需从以下位置注释掉import geopandas.datasets语句:File "site-packages\geopandas\__init__.py", line 9, in <module>,而不是手动将它们包含在我的.spec文件中。

这样编译后,我的程序输出了预期的结果。


1
这是一个完全有效的解决方案,我还有一个额外的建议,供未来必须更改已安装软件包源的SO用户使用。从github克隆软件包并使用以下命令进行安装: cd ; pip install -e .``` 现在开发人员可以毫不担心地更改软件包源,而不会危及其站点包的完整性。值得一提的是软件包和环境管理器(conda/virtualenv/pipenv)。这使得以后可以返回到该项目,并使用相同的软件包版本重新构建它。 - aorr
工作得很好,谢谢!你知道这个解决方案可能存在什么问题吗? - Omri

6

看起来geopandas在初始化时会主动加载其数据目录。该目录包含非Python文件,这些文件在您的软件包中被pyinstaller忽略,因此为了在加载geopandas时找到它们,必须显式打包它们。

“手动”过程让我花了一些时间来摸索,我使用conda作为我的包管理器(如果您不是,这些编辑仍然可以帮助您)。要使其正常工作,我们需要修改第一次运行pyinstaller时构建的.spec文件:

# -*- mode: python -*-

import os
from PyInstaller.utils.hooks import collect_data_files # this is very helpful
env_path = os.environ['CONDA_PREFIX']
dlls = os.path.join(env_path, 'DLLs')
bins = os.path.join(env_path, 'Library', 'bin')

paths = [
    os.getcwd(),
    env_path,
    dlls,
    bins,
]

# these binary paths might be different on your installation. 
# modify as needed. 
# caveat emptor
binaries = [
    (os.path.join(bins,'geos.dll'), ''),
    (os.path.join(bins,'geos_c.dll'), ''),
    (os.path.join(bins,'spatialindex_c-64.dll'), ''),
    (os.path.join(bins,'spatialindex-64.dll'),''),
]

hidden_imports = [
    'ctypes',
    'ctypes.util',
    'fiona',
    'gdal',
    'geos',
    'shapely',
    'shapely.geometry',
    'pyproj',
    'rtree',
    'geopandas.datasets',
    'pytest',
    'pandas._libs.tslibs.timedeltas',
]

# other fancy pyinstaller stuff...

a = Analysis(['run_it.py'],
         pathex=paths, # add all your paths
         binaries=binaries, # add the dlls you may need
         datas=collect_data_files('geopandas', subdir='datasets'), #this is the important bit for your particular error message
         hiddenimports=hidden_imports, # double tap
         hookspath=[],
         runtime_hooks=[],
         excludes=excludes,
         win_no_prefer_redirects=False,
         win_private_assemblies=False,
         cipher=block_cipher)
# remaining fancy pyinstaller stuff...

那应该收集你缺失的数据目录并将其放置在可执行文件可以找到的位置。
“自动”方式是构建一个名为hook-geopandas.py的文件,它会为您执行此操作。 pyinstaller在构建时加载这些钩子,以便保存和共享这些技巧。实际上,已经有一个非常好的shapely钩子文件,其中之一是geopandas依赖项,您可以在此处查看:here
------编辑--------
我目前正在构建一个依赖于geopandas的项目,并且我意识到上面的修复方法在此日期(2018-08-23)是不完整的,因为存在此issue
在我的run_it.py中,我包含了以下测试,以确保fionagdal都正确打包到捆绑包中:
from osgeo import gdal, ogr, osr
from fiona.ogrext import Iterator, ItemsIterator, KeysIterator
from geopandas import GeoDataFrame

如果你不是一个巫师,那么这个测试可能会失败。这个 shim 在我的 .spec 文件中起作用:

_osgeo_pyds = collect_data_files('osgeo', include_py_files=True)

osgeo_pyds = []
for p, lib in _osgeo_pyds:
    if '.pyd' in p:
        osgeo_pyds.append((p, ''))

binaries = osgeo_pyds + [
    # your other binaries
]

a = Analysis(
    # include your kwargs
)

我希望这能让答案更加完整,以及您的捆绑应用可以按预期完成其地理空间功能。


不错!经过编辑 env_path 后测试通过。必须删除 geopandas.datasets 的隐藏导入(实际上完全删除了 geopandas.datasets,因为它会抛出错误)。 - Joska
@Joska 很高兴它对你有用!我刚刚为我的项目编辑了一个 shim 以解决另一个困扰我的 bug。祝你好运! - aorr
我尝试使用上述内容扩展我的规范文件,但现在出现以下错误:File "c:\programdata\anaconda2\envs\wps_env36\lib\site-packages\PyInstaller\depend\analysis.py", line 554, in get_co_using_ctypes r_ident = r.identifier AttributeError: 'NoneType' object has no attribute 'identifier' - user32882
我把这个解决方案的变体放到了一个拉取请求中,提交给了PyInstaller社区钩子存储库: https://github.com/pyinstaller/pyinstaller-hooks-contrib/pull/400 - Sarah Messer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接