使用 Py2Exe 编译的 Python 应用程序引发了 UnknownTimezoneError 异常

15

我遇到了一个分发应用程序的问题,该应用程序使用了pytz。我正在使用Py2Exe从Python源代码创建可执行文件。

为了简单说明我遇到的问题,我有一个pytz_test.py文件:

import pytz

tz_au = pytz.timezone("Australia/Sydney")
print tz_au

并在setup.py中:

from distutils.core import setup
import py2exe

setup(console=['pytz_test.py'], options={"py2exe" : { 'packages': ['pytz'], } })

我然后运行setup.py:

python setup.py py2exe

编译可执行文件。运行创建的pytz_test.exe,我得到了以下结果:
Traceback (most recent call last):
  File "pytz_test.py", line 3, in <module>
    tz_au = pytz.timezone("Australia/Sydney")
  File "pytz\__init__.pyc", line 185, in timezone
pytz.exceptions.UnknownTimeZoneError: 'Australia/Sydney'

我猜测问题出在时区信息没有与可执行文件捆绑在一起,但我不确定如何实现这一点。
编辑: 一个简单的解决方案是将Python的site-packages目录下的pytz模块中的zoneinfo目录添加到library.zip中。
为了自动完成此操作,我按照该项目“Google Transit Data Feed”中使用的解决方案进行了修改,具体请参见以下链接: http://code.google.com/p/googletransitdatafeed/source/browse/trunk/python/setup.py 我的修改后的setup.py现在如下:
from distutils.core import setup
import glob
import py2exe

options = {
    "py2exe" : { 
        "compressed": 1, 
        "optimize": 2,
        'packages': ['pytz'], 
     } 
}

setup(console=['pytz_test.py'], options=options)

import pytz
import os 
import zipfile
zipfile_path = os.path.join("dist/" 'library.zip')
z = zipfile.ZipFile(zipfile_path, 'a')
zoneinfo_dir = os.path.join(os.path.dirname(pytz.__file__), 'zoneinfo')
disk_basedir = os.path.dirname(os.path.dirname(pytz.__file__))
for absdir, directories, filenames in os.walk(zoneinfo_dir):
    assert absdir.startswith(disk_basedir), (absdir, disk_basedir)
    zip_dir = absdir[len(disk_basedir):]
    for f in filenames:
      z.write(os.path.join(absdir, f), os.path.join(zip_dir, f))

z.close()

您还需要安装当前版本的setuptools。具体来说,需要安装pkg_resources。 'import pkg_resources' 不能失败。 - sbaechler
pkg_resources 到底在哪里使用?只是通过导入它吗? - Tristian
2个回答

3

一个简单的解决方案是将Python site-packages目录下的pytz模块中的zoneinfo目录添加到library.zip文件夹中。

为了自动执行此操作,我遵循了项目Google Transit Data Feed使用的解决方案,来自: http://code.google.com/p/googletransitdatafeed/source/browse/trunk/python/setup.py

我的修改过的setup.py现在看起来像这样:

from distutils.core import setup
import glob
import py2exe

options = {
    "py2exe" : { 
        "compressed": 1, 
        "optimize": 2,
        'packages': ['pytz'], 
     } 
}

setup(console=['pytz_test.py'], options=options)

import pytz
import os 
import zipfile
zipfile_path = os.path.join("dist/" 'library.zip')
z = zipfile.ZipFile(zipfile_path, 'a')
zoneinfo_dir = os.path.join(os.path.dirname(pytz.__file__), 'zoneinfo')
disk_basedir = os.path.dirname(os.path.dirname(pytz.__file__))
for absdir, directories, filenames in os.walk(zoneinfo_dir):
    assert absdir.startswith(disk_basedir), (absdir, disk_basedir)
    zip_dir = absdir[len(disk_basedir):]
    for f in filenames:
      z.write(os.path.join(absdir, f), os.path.join(zip_dir, f))

z.close()

(由提问者回答)


3
手动压缩zoneinfo(如Jason S所述),确实有助于在我的一台计算机上构建软件包。但是当我在另一台计算机上构建软件包时,错误又出现了!找到原因花了我一段时间 - 所以最好分享一下。
提议的解决方案不适用于新的pytz版本(至少不适用于2014.7版本)!深入挖掘后发现,pytz将区域信息文件的格式从pyc更改为某种二进制格式。对我来说,看起来像是他们通过这种变化“破坏”了将pytz打包成zip的选项,因为Python的内置zipimport机制无法加载二进制文件。实际上,这个问题应该由pytz解决,但目前我找到了另一个解决方法:
- 直接将整个pytz目录复制到您的dist目录中 - 在程序内部,将您的主可执行文件的路径添加到Python的搜索路径中
实际上,这意味着您需要在setup.py中用这个替换掉pytz-zipping:
import pytz, os, shutil
srcDir = os.path.dirname( pytz.__file__ )
dstDir = os.path.join( 'dist', 'pytz' )
shutil.copytree( srcDir, dstDir, ignore = shutil.ignore_patterns('*.py') )

将pytz从“packages”选项移动到“excludes”:
options = {
    "py2exe" : { 
        "compressed": 1, 
        "optimize": 2,
        "packages": [],
        "excludes": ['pytz']
     } 
}

在您的程序主入口处(确保在导入pytz之前执行),您需要添加类似以下内容的代码:
import os, sys
basePath = os.path.dirname( os.path.abspath( sys.argv[0] ) )
sys.path.insert( 0, basePath )

这对我很有效(pytz 2014.10),但是我不得不从shutil.copytree()中删除所有忽略。 - Geotob
是的!它奏效了!最奇怪的是,我的pytz和py2exe一起工作。然后我修改了程序中与pytz完全无关的几个东西,突然它就停止工作并出现了UnknownTimeZoneError。 - wordsforthewise
话说编程,没错!这也是我的问题。而且由于(至少对我来说)py2exe的工作方式和收集内容并不总是显而易见,我甚至不得不在pytz中加入调试打印语句,以便在py2exe的收集阶段发现出了什么问题。 :-) - Zappotek
对我有用。谢谢 :) - Omer
我还没有添加以下这一行代码: os.environ.setdefault('PYTZ_TZDATADIR', f'{basePath}\pytz\zoneinfo') 因为它仍然找不到数据。 - Tonsic

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