Python应用程序编译为Py2Exe时抛出UnknownTimezoneError异常
我在分发一个使用 pytz 的应用程序时遇到了问题。我正在用 Py2Exe 将我的 Python 源代码打包成一个可执行文件。
为了简单说明我遇到的问题,我有一个文件叫做:
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'
我猜这可能是因为时区信息没有和可执行文件一起打包,但我不太确定该怎么做才能解决这个问题。
编辑: 一个简单的解决办法是把 pytz 模块在 Python 的 site-packages 目录下的 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()
2 个回答
手动压缩zoneinfo文件(正如Jason S所描述的那样)确实帮我在一台电脑上成功构建了这个包。不过,当我在另一台电脑上构建时,错误又回来了!找出原因花了我一段时间,所以我觉得有必要分享一下。
提出的解决方案在新的pytz版本上不管用(至少在2014.7版本上是这样)!深入研究后发现,pytz把zoneinfo文件的格式从pyc改成了一种二进制格式。对我来说,这个变化似乎“破坏”了将pytz打包成zip的选项,因为Python内置的zipimport机制无法加载二进制文件。实际上,这个问题应该由pytz来解决,但目前我找到了一种替代方案:
- 直接把整个pytz目录复制到你的dist目录里
- 在你的程序中,把主可执行文件的路径添加到Python的搜索路径中
实际上,这意味着在你的setup.py中,把pytz的压缩部分替换为:
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 模块中的 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()
(由提问者回答)