PyInstaller 在与 pipenv 一起使用时无法导入 distuitls
PyInstaller failing to import distuitls when used with pipenv
当 运行 在 raspbian 中使用 virtualenv (16.7.7) 在 pipenv (2018.11.26) 中截取下面的代码时,它会完美执行并且所有操作都按预期完成。
import logging
import distutils
from PIL import Image, ImageDraw, ImageFont, ImageFile, ImageOps
from pathlib import Path
logger = logging.getLogger(__name__)
logger.root.setLevel('DEBUG')
image = Path('/home/pi/tmp/4886.jpg').expanduser()
size = (100, 200)
try:
logging.info(f'opening image: {image}')
im = Image.open(image)
im.thumbnail(size)
except (PermissionError, FileNotFoundError, OSError) as e:
logging.warning(f'could not open image file: {image}')
logging.warning(f'image error: {e}')
logging.warning(f'using empty image')
print(f'image size is: {im.size}')
输出
这会产生预期的结果
INFO:root:opening image: /home/pi/tmp/4886.jpg
image size is: (100, 90)
用pipenv run python -m PyInstaller im_open.py
打包后,编译版本报错DEBUG:PIL.Image:Image: failed to import JpegImagePlugin: No module named 'distutils'
输出
INFO:root:opening image: /home/pi/tmp/4886.jpg
...
DEBUG:PIL.Image:Importing IptcImagePlugin
DEBUG:PIL.Image:Importing JpegImagePlugin
DEBUG:PIL.Image:Image: failed to import JpegImagePlugin: No module named 'distutils'
DEBUG:PIL.Image:Importing Jpeg2KImagePlugin
DEBUG:PIL.Image:Importing McIdasImagePlugin
DEBUG:PIL.Image:Importing MicImagePlugin
DEBUG:PIL.Image:Image: failed to import MicImagePlugin: No module named 'olefile'
DEBUG:PIL.Image:Importing MpegImagePlugin
DEBUG:PIL.Image:Importing MpoImagePlugin
DEBUG:PIL.Image:Image: failed to import MpoImagePlugin: No module named 'distutils'
DEBUG:PIL.Image:Importing MspImagePlugin
DEBUG:PIL.Image:Importing PalmImagePlugin
DEBUG:PIL.Image:Importing PcdImagePlugin
...
WARNING:root:could not open image file: /home/pi/tmp/4886.jpg
WARNING:root:image error: cannot identify image file '/home/pi/tmp/4886.jpg'
WARNING:root:using empty image
Traceback (most recent call last):
File "im_open.py", line 69, in <module>
print(f'image size is: {im.size}')
NameError: name 'im' is not defined
我尝试了以下方法:
- 明确包含 distutils(如上面的代码片段所示)
- 将 distutils 添加到 .spec 文件中的 hiddenimports 列表:
hiddeimports=['distutils']
相关研究
- 有一个 closed bug relating to virtualenv 16.4.0 与此相关,但我不确定它会如何影响我的问题
另一个错误报告中有针对此问题的 workaround。以下方法似乎可以通过至少 16.7.7
解决 virtualenv 16.4.0 的此问题
- 在 pipenv 中安装 PyInstaller(我不确定为什么这样可以解决问题,但目前看来是最可靠的):
pipenv install PyInstaller
- 打开由 pipenv 创建的 PyInstaller 目录:
pipenv open PyInstaller
并编辑 hooks/pre_find_module_path/hook-distutils.py
以匹配下面显示的补丁。
已修补 pre_find_module_path/hook-distutils.py
import distutils
import os
from PyInstaller.utils.hooks import logger
def pre_find_module_path(api):
# Absolute path of the system-wide "distutils" package when run from within
# a venv or None otherwise.
distutils_dir = getattr(distutils, 'distutils_path', None)
if distutils_dir is not None:
# workaround for https://github.com/pyinstaller/pyinstaller/issues/4064
if distutils_dir.endswith('__init__.py'):
distutils_dir = os.path.dirname(distutils_dir)
# end workaround patch
# Find this package in its parent directory.
api.search_dirs = [os.path.dirname(distutils_dir)]
logger.info('distutils: retargeting to non-venv dir %r' % distutils_dir)
当 运行 在 raspbian 中使用 virtualenv (16.7.7) 在 pipenv (2018.11.26) 中截取下面的代码时,它会完美执行并且所有操作都按预期完成。
import logging
import distutils
from PIL import Image, ImageDraw, ImageFont, ImageFile, ImageOps
from pathlib import Path
logger = logging.getLogger(__name__)
logger.root.setLevel('DEBUG')
image = Path('/home/pi/tmp/4886.jpg').expanduser()
size = (100, 200)
try:
logging.info(f'opening image: {image}')
im = Image.open(image)
im.thumbnail(size)
except (PermissionError, FileNotFoundError, OSError) as e:
logging.warning(f'could not open image file: {image}')
logging.warning(f'image error: {e}')
logging.warning(f'using empty image')
print(f'image size is: {im.size}')
输出 这会产生预期的结果
INFO:root:opening image: /home/pi/tmp/4886.jpg
image size is: (100, 90)
用pipenv run python -m PyInstaller im_open.py
打包后,编译版本报错DEBUG:PIL.Image:Image: failed to import JpegImagePlugin: No module named 'distutils'
输出
INFO:root:opening image: /home/pi/tmp/4886.jpg
...
DEBUG:PIL.Image:Importing IptcImagePlugin
DEBUG:PIL.Image:Importing JpegImagePlugin
DEBUG:PIL.Image:Image: failed to import JpegImagePlugin: No module named 'distutils'
DEBUG:PIL.Image:Importing Jpeg2KImagePlugin
DEBUG:PIL.Image:Importing McIdasImagePlugin
DEBUG:PIL.Image:Importing MicImagePlugin
DEBUG:PIL.Image:Image: failed to import MicImagePlugin: No module named 'olefile'
DEBUG:PIL.Image:Importing MpegImagePlugin
DEBUG:PIL.Image:Importing MpoImagePlugin
DEBUG:PIL.Image:Image: failed to import MpoImagePlugin: No module named 'distutils'
DEBUG:PIL.Image:Importing MspImagePlugin
DEBUG:PIL.Image:Importing PalmImagePlugin
DEBUG:PIL.Image:Importing PcdImagePlugin
...
WARNING:root:could not open image file: /home/pi/tmp/4886.jpg
WARNING:root:image error: cannot identify image file '/home/pi/tmp/4886.jpg'
WARNING:root:using empty image
Traceback (most recent call last):
File "im_open.py", line 69, in <module>
print(f'image size is: {im.size}')
NameError: name 'im' is not defined
我尝试了以下方法:
- 明确包含 distutils(如上面的代码片段所示)
- 将 distutils 添加到 .spec 文件中的 hiddenimports 列表:
hiddeimports=['distutils']
相关研究
- 有一个 closed bug relating to virtualenv 16.4.0 与此相关,但我不确定它会如何影响我的问题
另一个错误报告中有针对此问题的 workaround。以下方法似乎可以通过至少 16.7.7
解决 virtualenv 16.4.0 的此问题- 在 pipenv 中安装 PyInstaller(我不确定为什么这样可以解决问题,但目前看来是最可靠的):
pipenv install PyInstaller
- 打开由 pipenv 创建的 PyInstaller 目录:
pipenv open PyInstaller
并编辑hooks/pre_find_module_path/hook-distutils.py
以匹配下面显示的补丁。
已修补 pre_find_module_path/hook-distutils.py
import distutils
import os
from PyInstaller.utils.hooks import logger
def pre_find_module_path(api):
# Absolute path of the system-wide "distutils" package when run from within
# a venv or None otherwise.
distutils_dir = getattr(distutils, 'distutils_path', None)
if distutils_dir is not None:
# workaround for https://github.com/pyinstaller/pyinstaller/issues/4064
if distutils_dir.endswith('__init__.py'):
distutils_dir = os.path.dirname(distutils_dir)
# end workaround patch
# Find this package in its parent directory.
api.search_dirs = [os.path.dirname(distutils_dir)]
logger.info('distutils: retargeting to non-venv dir %r' % distutils_dir)