Python3 (v3.2.2) 写入二进制文件时多余的位

2 投票
2 回答
1338 浏览
提问于 2025-04-17 05:01

我正在写一个函数,用来把一个二进制文件里的字节映射到另一组字节上。我是从同一个文件里读数据,然后再写回去。我的问题是,每次这样做的时候,文件里总会多出一些字节,除非我在关闭文件之前先移动到文件的末尾。下面是我的代码:

with open(self._path,'r+b') as source:
    for lookAt in range(0,self._size[1]*self._size[2],1):
        source.seek(lookAt*self._size[0],0)
        readBuffer = array.array('B')
        readBuffer.fromfile(source, self._size[0])
        newLine = array.array('B',[mappingDict[mat] for mat in readBuffer])
        source.seek(lookAt*self._size[0],0)
        newLine.tofile(source)
        source.seek(0,2) # Magic line that solves stupid bug
source.close()

我使用数组模块来读写数据,因为我在用read()和write()的时候也遇到了同样的问题。我不明白为什么那条被称为“魔法行”的代码能解决这个问题,因为我从来没有用过它。我希望能得到一些关于这个问题的见解。

2 个回答

1

我稍微试了一下这个问题,我猜这可能是Python 3里的一个bug。

为了支持我的猜测,我提供了以下代码(基于@J.F. Sebastian的代码):

import os
import sys

filename = '/tmp/a'
with open(filename, 'wb') as f:
    f.write(b'1234a67b8ca')
print(open(filename, 'rb').read())

bufsize = 3

with open(filename, 'r+b') as f:
    for i in range(0, os.path.getsize(filename), bufsize):
        f.seek(i, os.SEEK_SET)
        b = f.read(bufsize)
        f.seek(i, os.SEEK_SET)
        f.write(b)
#        f.seek(0, os.SEEK_END) # magic
print(open(filename, 'rb').read())

在使用Python 2.7.1运行时,它的表现和你预期的一样,那个神奇的代码行没有影响。

在使用Python 3.1.2运行时,它莫名其妙地需要那个神奇的无操作 seek() 才能按预期工作。

在这个时候,我建议把代码展示给核心的Python 3开发者,听听他们的看法,看看这是否真的是个bug。

3

评论(答案在后面):

我看到的情况和你一样:

#!/usr/bin/env python3
import os
import sys

filename = '/tmp/a'
with open(filename, 'wb') as f:
    f.write(b'1234a67b8ca')
print(open(filename, 'rb').read())

bufsize = 3

table = bytes.maketrans(b'abcde', b'xyzzz') # mapping
with open(filename, 'r+b') as f:
    for i in range(0, os.path.getsize(filename), bufsize):
        f.seek(i, os.SEEK_SET)
        b = f.read(bufsize) # result shouldn't depend on it due to 1 -> 1
        if not b: 
            break
        f.seek(i, os.SEEK_SET)
        f.write(b.translate(table))
        f.seek(0, os.SEEK_END) # magic
print(open(filename, 'rb').read())

输出(使用魔法行或设置buffering=0,或者在f.write后调用f.flush)

b'1234a67b8ca'
b'1234x67y8zx'

输出(没有魔法行)

b'1234a67b8ca'
b'1234a67b8zx1234x67y8'

答案:

如果你的映射是1对1的关系,你可以使用 bytes.translate()

#!/usr/bin/env python3
import io
import os
import sys

filename = '/tmp/a'
data = b'1234a67b8ca'*10000
with open(filename, 'wb') as f:
    f.write(data)
assert data == open(filename, 'rb').read()
print(data[:10]+data[-10:])

bufsize = io.DEFAULT_BUFFER_SIZE

table = bytes.maketrans(b'abcde', b'xyzzz') # mapping
with open(filename, 'r+b') as f:
    while True:
        b = f.read(bufsize) # result shouldn't depend on bufsize due to 1 -> 1
        if not b: 
            break
        f.seek(-len(b), os.SEEK_CUR)
        f.write(b.translate(table))
        f.flush()

tr_data = data.translate(table)
assert tr_data  == open(filename, 'rb').read()
print(tr_data[:10]+tr_data[-10:])

看起来 io.BufferedRandom 在没有调用 flush() 的情况下,无法进行 交错的读/寻址/写入(这是Python3中的一个bug)。

撰写回答