在fileinput模块中结合就地过滤和编码设置

12 投票
5 回答
3956 浏览
提问于 2025-04-18 16:32

我正在尝试使用 fileinput 模块的 就地过滤功能 来直接修改一个输入文件。

我需要将编码设置为 latin-1(无论是读取还是写入),并试图将 openhook=fileinput.hook_encoded('latin-1') 传递给 fileinput.input,但遇到了错误

ValueError: FileInput cannot use an opening hook in inplace mode

仔细查看后,我发现 fileinput 的文档明确说明了这一点:你不能同时使用就地和 openhook

那我该怎么解决这个问题呢?

5 个回答

1

我对现有使用 renameremove 的解决方案不是很满意,因为它们把一些文件处理的事情简化得太过了,比如处理文件模式、处理 chmod 属性等等。

在我的情况下,因为我可以控制代码运行的环境,所以我决定唯一合理的解决办法就是把我的区域设置改成使用 UTF8 的区域:

export LC_ALL=en_US.UTF-8

这样做的效果是:

sh-4.2> python3.6 -c "import fileinput;
for line in fileinput.FileInput('DESCRIPTION', inplace=True): print(line.rstrip() + 'hi')
print('done')"
Traceback (most recent call last):
  File "<string>", line 2, in <module>
  File "/usr/lib64/python3.6/fileinput.py", line 250, in __next__
    line = self._readline()
  File "/usr/lib64/python3.6/fileinput.py", line 364, in _readline
    return self._readline()
  File "/usr/lib64/python3.6/encodings/ascii.py", line 26, in decode
    return codecs.ascii_decode(input, self.errors)[0]
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 227: ordinal not in range(128)'

sh-4.2> export LC_ALL=en_US.UTF-8
sh-4.2> python3.6 -c "import fileinput;
for line in fileinput.FileInput('DESCRIPTION', inplace=True): print(line.rstrip() + 'hi')
print('done')"
done

sh-4.2# 

可能的副作用是会影响其他文件的输入和输出,但我在这里并不担心这个。

1

这段内容和其他回答很相似,只不过是用函数的形式写的,这样可以方便地多次调用:

def inplace(orig_path, encoding='latin-1'):
    """Modify a file in-place, with a consistent encoding."""
    new_path = orig_path + '.modified'
    with codecs.open(orig_path, encoding=encoding) as orig:
        with codecs.open(new_path, 'w', encoding=encoding) as new:
            for line in orig:
                yield line, new
    os.rename(new_path, orig_path)

下面是它实际运行的样子:

for line, new in inplace(path):
    line = do_processing(line)  # Use your imagination here.
    new.write(line)

这个代码在python2和python3中都是有效的,只要你指定了正确的编码(在我的情况下,我实际上到处都需要用utf-8,但你的需求可能会有所不同)。

4

如果你不介意使用一个叫做 pip 的库的话,in_place 这个库是支持编码的。

import in_place

with in_place.InPlace(filename, encoding="utf-8") as fp:
  for line in fp:
    fp.write(line)
7

据我所知,使用 fileinput 模块是没有办法绕过这个问题的。不过,你可以通过结合使用 codecs 模块、os.rename()os.remove() 来完成同样的任务:

import os
import codecs

input_name = 'some_file.txt'
tmp_name = 'tmp.txt'

with codecs.open(input_name, 'r', encoding='latin-1') as fi, \
     codecs.open(tmp_name, 'w', encoding='latin-1') as fo:

    for line in fi:
        new_line = do_processing(line) # do your line processing here
        fo.write(new_line)

os.remove(input_name) # remove original
os.rename(tmp_name, input_name) # rename temp to original name

如果你想改变输出文件的编码格式,你可以选择一个新的编码;如果不想改变,可以在打开输出文件时保持为 latin-1

我知道这不是你想要的原地修改方式,但它能完成你想做的事情,而且非常灵活。

2

Python 3.10 开始,fileinput.input() 这个函数可以接受一个编码参数。

撰写回答