Read a unicode file in python which declares its encoding in the same way as python source
我想写一个python程序来读取包含unicode文本的文件。这些文件通常是用UTF-8编码的,但可能不是;如果不是,备用编码将在文件开头显式声明。更准确地说,它将使用与python本身使用的规则完全相同的规则来声明,以允许python源代码具有显式声明的编码(如pep 0263所示,有关详细信息,请参阅https://www.python.org/dev/peps/pep-0263/)。只是要明确一点,正在处理的文件实际上不是Python源文件,但它们确实使用相同的规则声明了它们的编码(不使用UTF-8时)。
如果在打开文件之前就知道它的编码,那么python提供了一种非常简单的方法,可以通过自动解码来读取文件:
1 2 3 4 | import codecs f = codecs.open('unicode.rst', encoding='utf-8') for line in f: print repr(line) |
循环中的每个
一种想法是使用通常的
')有人知道这是不是真的吗?医生不是很具体。
另一种想法是查看Python源代码。有人知道在python源代码中源代码编码处理在哪里完成吗?
您应该能够在Python中滚动自己的解码器。如果您只支持8位编码(这是ASCII的超集),那么下面的代码应该可以正常工作。
如果您需要支持2字节编码(如utf-16),则需要根据字节顺序标记增加模式以匹配
1 2 3 4 5 6 7 | import codecs, sys for encoding in ('utf-8', 'cp1252'): out = codecs.open('%s.txt' % encoding, 'w', encoding) out.write('# coding = %s ' % encoding) out.write(u'\u201chello se\u00f1nor\u201d') out.close() |
然后编写解码器:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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()) |
输出:
1 2 3 4 | ['# coding = utf-8 ', '\xe2\x80\x9chello se\xc3\xb1nor\xe2\x80\x9d'] ['# coding = cp1252 ', '\xe2\x80\x9chello se\xc3\xb1nor\xe2\x80\x9d'] |
我检查了
"或"r"(试图尽可能长地匹配字符串,这意味着如果看到"r",则必须推测地读取下一个字符,如果它不是"n",则将其放回原处)。和通常的Python实践一样,终结者包含在行中。
现在,当"我找到一个编码声明"时要做什么的规则是:
对于我所做的,忠实于Python的行为是很重要的。我的计划是在python中滚动上述算法的实现,并使用它。感谢所有回答的人!
来自所述PEP(0268):
Python's tokenizer/compiler combo will
need to be updated to work as follows:read the file
decode it into Unicode assuming a fixed per-file encoding
convert it into a UTF-8 byte string
tokenize the UTF-8 content
compile it, creating Unicode objects from the given Unicode data
and creating string objects from the Unicode literal data
by first reencoding the UTF-8 data into 8-bit string data
using the given file encoding
实际上,如果您在python源代码中检查
它看起来不像是以python API(至少这些特定的函数不是以
在标准库中,甚至在Python2中,都支持这种方法。以下是您可以使用的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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 |
然后我个人需要解析和编译这个源代码。在Python2中,编译包含编码声明的Unicode文本是一个错误,因此必须首先将包含声明的行设为空白(而不是删除,因为这样会更改行号)。所以我也做了这个功能:
1 2 3 4 5 6 7 8 9 10 | def read_source_file(filename): from lib2to3.pgen2.tokenize import cookie_re with open_with_encoding_check(filename) as f: return ''.join([ ' ' if i < 2 and cookie_re.match(line) else line for i, line in enumerate(f) ]) |
我在包中使用这些,最新的源代码(如果我发现需要更改它们)可以在这里找到,而测试在这里。
从python 3.4开始,有一个函数允许您执行您所要求的操作–
根据文件:
importlib.util.decode_source(source_bytes)
Decode the given bytes representing source code and return it as a string with universal newlines (as required byimportlib.abc.InspectLoader.get_source() ).
BrettCannon在他从源代码到代码的演讲中谈到了这个函数:cpython的编译器是如何工作的。