Py学习  »  Python

python2和python3之间的zipfile头语言编码位设置不同

Keeely • 4 年前 • 785 次点击  

在使用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 ,设置为 0x800 对于python 2, 0x00 对于python 3。那是第11位:语言编码。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上: Python3.6.6与Python2.7.11 如果这很重要的话,我正以我的例子运行它,没有hashbang,直接调用解释器:

pythonX test.py
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/41249
 
785 次点击  
文章 [ 2 ]  |  最新文章 4 年前
Keeely
Reply   •   1 楼
Keeely    5 年前

我暂时用这样的东西:

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()似乎是将来不会改变的东西。

torek
Reply   •   2 楼
torek    5 年前

编辑: 下面的代码适用于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 ,而这只适用于Python3.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.6zipfile.py源代码)。

要获得位集,您需要一个不能直接用ascii编码的文件名,例如:

info.filename = u"sch\N{latin small letter o with diaeresis}n" # "file.txt"

(此符号适用于Python2.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 )