我希望这段代码在使用 Python 2 或 Python 3 运行时能够同样工作
from zipfile import ZipFile, ZipInfo
with ZipFile("out.zip", 'w') as zf:
content = "content"
info = ZipInfo()
info.filename = "file.txt"
info.flag_bits = 0x800
info.file_size = len(content)
zf.writestr(info, content)
但是,在 Python 2 下,out.zip 开始:
50 4b 03 04 14 00 00 08
Python3下,开始:
50 4b 03 04 14 00 00 00
不同的部分是
flag_bits
,对于 Python 2 设置为 0x800
,对于 Python 3 设置为 0x00
。这就是 BIT11:语言编码。 BIT11 似乎已设置 if filename.encode("ascii")
抛出。
我尝试在创建 ZipInfo 对象后通过设置标志来强制启用此位,但它会重置回
0x00
中的 _open_to_write()
。
不知道这里有没有人有好的解决办法。理想情况下,我希望两个输出都有设置标志,因为这反映了 jar 实用程序的功能。
编辑:更新以添加
info.flag_bits = 0x800
行只是为了阐明我想要实现的目标。我在 Windows 上重现了这个:
ActivePython 3.6.0.3600 与 ActivePython 2.7.14.2717、Windows 10。
在 Linux 上:
Python 3.6.6 与 Python 2.7.11
如果重要的话,我将按照我的示例运行它,没有 hashbang,直接调用解释器:
pythonX test.py
编辑: 下面的代码适用于 Python 2.7,但不适用于 3.6(有点神秘,今晚早些时候它似乎可以工作):
$ cat zipf.py
from __future__ import print_function
from zipfile import ZipFile, ZipInfo
with ZipFile("out.zip", 'w') as zf:
content = "content"
info = ZipInfo()
info.filename = "file.txt"
info.flag_bits = 0x800
# don't set info.file_size here: zf.writestr() does that
zf.writestr(info, content)
with open('out.zip', 'rb') as stream:
byteseq = stream.read(8)
for i in byteseq:
if isinstance(i, str): i = ord(i)
print('{:02x}'.format(i), end=' ')
print()
运行方式:
$ python2.7 zipf.py
50 4b 03 04 14 00 00 08
但是:
$ python3.6 zipf.py
50 4b 03 04 14 00 00 00
通过确保在创建 info
条目之前打开文件,当然可以
使其工作。但是,那么你必须避免
writestr
,这仅适用于 Python 3.6(并且看起来相当滥用):
from __future__ import print_function
from zipfile import ZipFile, ZipInfo
with ZipFile("out.zip", 'w') as zf:
info = ZipInfo()
info.filename = "file.txt"
content = "content"
if not isinstance(content, bytes):
content = content.encode('utf8')
info.file_size = len(content)
with zf.open(info, 'w') as stream:
info.flag_bits = 0x800
stream.write(content)
with open('out.zip', 'rb') as stream:
byteseq = stream.read(8)
for i in byteseq:
if isinstance(i, str): i = ord(i)
print('{:02x}'.format(i), end=' ')
print()
情况可能是 3.6 重置所有
info.flag_bits
(通过它所做的内部 open
)是不正确的,尽管我不太清楚。
我无法重现这一点,但你是对的,如果文件名是 Unicode 并且编码为 ASCII 失败,则设置标志位中的位 11:
def _encodeFilenameFlags(self):
if isinstance(self.filename, unicode):
try:
return self.filename.encode('ascii'), self.flag_bits
except UnicodeEncodeError:
return self.filename.encode('utf-8'), self.flag_bits | 0x800
else:
return self.filename, self.flag_bits
(Python 2.7 zipfile.py 源)或:
def _encodeFilenameFlags(self):
try:
return self.filename.encode('ascii'), self.flag_bits
except UnicodeEncodeError:
return self.filename.encode('utf-8'), self.flag_bits | 0x800
(Python 3.6 zipfile.py 源)。
要获取位设置,您需要一个无法直接以 ASCII 编码的文件名,例如:
info.filename = u"sch\N{latin small letter o with diaeresis}n" # "file.txt"
(此表示法适用于 Python 2.7 和 3.6)。
我尝试在创建 ZipInfo 对象后通过设置标志来强制启用此位,但它在 _open_to_write() 中重置回 0x00。
如果我添加:
info.filename = "file.txt"
info.flag_bits |= 0x0800
(就在将文件名设置为
u"schön"
之后)并在Python 2.7或3.6下运行它,我得到了标头中设置的位(当然zip目录中的文件名会更改回file.txt
)。
我暂时使用这样的东西:
from zipfile import ZipFile, ZipInfo
import struct
orig_function = ZipInfo.FileHeader
def new_function(self, zip64=None):
header = orig_function(self, zip64)
fmt = "B"*len(header)
blist = list(struct.unpack(fmt, header))
blist[7] |= 0x8
return struct.pack(fmt, *blist)
setattr(ZipInfo, "FileHeader", new_function)
with ZipFile("out.zip", 'w') as zf:
content = "content"
info = ZipInfo()
info.filename = "file.txt"
info.file_size = len(content)
zf.writestr(info, content)
希望它不会太快损坏,FileHeader() 似乎将来不会改变。
就我而言,如果这样做,您将获得正确的编码:
import zipfile
with zipfile.ZipFile("./your_file.zip", "r") as zf:
file_list = [file.encode("cp437").decode("cp850") for file in zf.namelist()]
file_list