我希望编写一个python程序来读取包含unicode文本的文件。这些文件通常用UTF-8编码,但可能不是;如果不是,则将在文件的开头显式声明备用编码。更确切地说,它将使用与Python本身使用的完全相同的规则来声明,以允许Python源代码具有显式声明的编码(如在PEP 0263中,有关更多详细信息,请参阅https://www.python.org/dev/peps/pep-0263/)。为了清楚起见,正在处理的文件实际上并不是python源,但它们使用相同的规则声明了它们的编码(当不是UTF-8时)。
如果在打开文件之前知道文件的编码,Python提供了一种非常简单的方法来自动解码文件:codecs.open
命令;例如,有人可能会这样做:
import codecs
f = codecs.open('unicode.rst', encoding='utf-8')
for line in f:
print repr(line)
我们在循环中得到的每个line
都是一个unicode字符串。是否有一个Python库做类似的事情,但根据上面的规则选择编码(我认为这是Python 3.0的规则)? (例如,Python是否公开了'用于自我声明编码的'读取文件'来读取语言的来源?)如果没有,那么实现所需效果的最简单方法是什么?
一种想法是使用通常的open
打开文件,读取前两行,将它们解释为UTF-8,使用PEP中的regexp查找编码声明,如果找到一个开头使用声明的编码解码所有后续行。为了确保这一点,我们需要知道,对于Python在Python源代码中允许的所有编码,通常的Python readline
会正确地将文件拆分成行 - 也就是说,我们需要知道所有这些Python允许在Python源代码中编码,字节字符串'\ n'总是真正意味着换行符,而不是编码另一个字符的多字节序列的一部分。 (事实上我也需要担心'\ r \ n'。)有人知道这是否属实?文档不是很具体。
另一个想法是查看Python源代码。有谁知道在源代码中源代码编码处理的位置在哪里?
答案 0 :(得分:7)
您应该能够在Python中使用自己的解码器。如果您只支持8位编码,这些编码是ASCII的超集,则下面的代码应该按原样运行。
如果您需要支持2字节encodings like UTF-16,则需要将模式扩充为匹配\x00c\x00o..
或相反,具体取决于the byte order mark。
首先,生成一些宣传其编码的测试文件:
import codecs, sys
for encoding in ('utf-8', 'cp1252'):
out = codecs.open('%s.txt' % encoding, 'w', encoding)
out.write('# coding = %s\n' % encoding)
out.write(u'\u201chello se\u00f1nor\u201d')
out.close()
然后写解码器:
import codecs, re
def open_detect(path):
fin = open(path, 'rb')
prefix = fin.read(80)
encs = re.findall('#\s*coding\s*=\s*([\w\d\-]+)\s+', prefix)
encoding = encs[0] if encs else 'utf-8'
fin.seek(0)
return codecs.EncodedFile(fin, 'utf-8', encoding)
for path in ('utf-8.txt','cp1252.txt'):
fin = open_detect(path)
print repr(fin.readlines())
输出:
['# coding = utf-8\n', '\xe2\x80\x9chello se\xc3\xb1nor\xe2\x80\x9d']
['# coding = cp1252\n', '\xe2\x80\x9chello se\xc3\xb1nor\xe2\x80\x9d']
答案 1 :(得分:3)
我检查了tokenizer.c
的来源(感谢@Ninefingers在另一个答案中建议这个并给出源浏览器的链接)。似乎Python使用的确切算法(等效于)以下内容。在不同的地方,我会将算法描述为逐字节读取---显然,人们希望在实践中做一些缓冲,但这种方式更容易描述。文件的初始部分按如下方式处理:
现在有关'我发现编码声明'时该做什么的规则:
'utf-8'
或以'utf-8-'
开头的内容。)codecs
模块中给定编码关联的解码器读取文件的其余部分。特别是,将文件中其余字节划分为行是新编码的工作。codecs
模块中解码器的行。另一方面,如果编码是某种形式的'utf-8'或某种形式的'latin-1',则将'\ r'或'\ r \ n'结尾的行转换为以'\ n'结尾的行。 (''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' {1}},'latin-1'
或'iso-latin-1'
,或以'iso-8859-1'
,'latin-1-'
或'iso-latin-1-'
之一开头的任何字符串。对于我正在做的事情,忠实于Python的行为非常重要。我的计划是在Python中推出上面算法的实现,并使用它。感谢所有回答的人!
答案 2 :(得分:2)
来自PEP (0268):
Python的tokenizer / compiler组合将会 需要更新才能按如下方式工作:
- 假设每个文件编码固定 ,
阅读文件
将其解码为Unicode
将其转换为UTF-8字节字符串
将UTF-8内容标记为
- 醇>
编译它,从给定的Unicode数据创建Unicode对象 并从Unicode文字数据创建字符串对象 首先将UTF-8数据重新编码为8位字符串数据 使用给定的文件编码
的确,如果您在Python源代码中检查Parser/tokenizer.c
,您将找到函数get_coding_spec
和check_coding_spec
,它们负责在{{3}中正在检查的行上查找此信息}。
看起来这个功能不会作为python API暴露给你(至少这些特定功能不是Py
前缀 - 所以你的选择是第三方库和/或重新将这些功能作为扩展。我个人不知道任何第三方库 - 我在标准库中也看不到这个功能。
答案 3 :(得分:1)
从Python 3.4开始,有一个功能可以让你做你想要的事情 - importlib.util.decode_source
importlib.util.decode_source(source_bytes)
解码代表源代码的给定字节,并将其作为带有通用换行符的字符串返回(根据importlib.abc.InspectLoader.get_source()
的要求)。
Brett Cannon talks在他的演讲中谈到了这个功能从源代码:CPython的编译器如何工作。
答案 4 :(得分:1)
标准库中支持这一点,即使在Python 2中也是如此。以下是您可以使用的代码:
try:
# Python 3
from tokenize import open as open_with_encoding_check
except ImportError:
# Python 2
from lib2to3.pgen2.tokenize import detect_encoding
import io
def open_with_encoding_check(filename):
"""Open a file in read only mode using the encoding detected by
detect_encoding().
"""
fp = io.open(filename, 'rb')
try:
encoding, lines = detect_encoding(fp.readline)
fp.seek(0)
text = io.TextIOWrapper(fp, encoding, line_buffering=True)
text.mode = 'r'
return text
except:
fp.close()
raise
然后我个人需要解析并编译这个源代码。在Python 2中,编译包含编码声明的unicode文本是一个错误,因此必须首先将包含声明的行留空(不删除,因为这会更改行号)。所以我也做了这个功能:
def read_source_file(filename):
from lib2to3.pgen2.tokenize import cookie_re
with open_with_encoding_check(filename) as f:
return ''.join([
'\n' if i < 2 and cookie_re.match(line)
else line
for i, line in enumerate(f)
])