Python 编译器如何使用声明的编码预处理源文件?
How does the Python compiler preprocess the source file with the declared encoding?
假设我有一个 cp1251 编码的 Python 3 源文件,其内容如下:
# эюяьъ (some Russian comment)
print('Hehehey')
如果我 运行 文件,我会得到这个:
SyntaxError: Non-UTF-8 code starting with '\xfd' in file ... on line 1 but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
这很清楚并且符合预期 - 我知道,一般来说,cp1251 字节序列不能用 UTF-8 解码,UTF-8 是 Python 中的默认编码 3.
但是如果我按如下方式编辑文件:
# coding: utf-8
# эюяьъ (some Russian comment)
print('Hehehey')
一切都会好起来的。
这很令人困惑。
在第二个示例中,源代码中仍然有相同的 cp1251 字节序列,这在 UTF-8 中无效,我希望编译器应该使用相同的编码 (UTF-8) 来预处理文件并以相同的错误终止.
我已经阅读 PEP 263 但仍然不明白它没有发生的原因。
那么,为什么我的代码在第二种情况下有效并在第一种情况下终止?
UPD.
为了检查我的文本编辑器是否足够聪明,可以根据行 # coding: utf-8
更改文件的编码,让我们看看实际的字节数:
(第一个例子)
23 20 fd fe ff fa fc ...
(第二个例子)
23 20 63 6f 64 69 6e 67 3a 20 75 74 66 2d 38 0a
23 20 fd fe ff fa fc ...
这些 f 字节用于 cp1251 中的西里尔字母,它们在 UTF-8 中无效。
此外,如果我这样编辑源代码:
# coding: utf-8
# эюяъь (some Russian comment)
print('Hehehey')
print('эюяъь')
我会遇到错误:
SyntaxError: (unicode error) 'utf-8' codec can't decode byte 0xfd ...
所以,不幸的是我的文本编辑器不是那么聪明。
因此,在上面的示例中 源文件未从 cp1251 转换为 UTF-8。
这似乎是如何强制执行默认编码的严格行为的怪癖。在 the tokenizer function, decoding_gets
中,如果它还没有找到明确的编码声明(tok->encoding
仍然是 NULL
),它会逐个字符地检查行中是否有无效的 UTF-8 字符,并且弹出你看到的引用 PEP 263 的 SyntaxError
。
但如果指定了编码,check_coding_spec
will have defined tok->encoding
, and that default encoding strict test将被完全绕过;它不会被声明的编码测试所取代。
通常情况下,这会在实际解析代码时引起问题,但看起来注释是以精简的方式处理的:一旦注释字符 #
被识别,the tokenizer just grabs and discards characters until it sees a newline or EOF
,它根本没有尝试对它们做任何事情(这是有道理的;解析注释只是在浪费时间,而这些时间本可以花在实际运行的东西上)。
因此,您观察到的行为是:编码声明通过字符检查禁用严格的文件范围字符,以检查在未明确声明编码时应用的有效 UTF-8,并且注释是特殊大小写的,因此它们的内容被忽略,允许注释中的垃圾字节逃避检测。
假设我有一个 cp1251 编码的 Python 3 源文件,其内容如下:
# эюяьъ (some Russian comment)
print('Hehehey')
如果我 运行 文件,我会得到这个:
SyntaxError: Non-UTF-8 code starting with '\xfd' in file ... on line 1 but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
这很清楚并且符合预期 - 我知道,一般来说,cp1251 字节序列不能用 UTF-8 解码,UTF-8 是 Python 中的默认编码 3.
但是如果我按如下方式编辑文件:
# coding: utf-8
# эюяьъ (some Russian comment)
print('Hehehey')
一切都会好起来的。
这很令人困惑。
在第二个示例中,源代码中仍然有相同的 cp1251 字节序列,这在 UTF-8 中无效,我希望编译器应该使用相同的编码 (UTF-8) 来预处理文件并以相同的错误终止.
我已经阅读 PEP 263 但仍然不明白它没有发生的原因。
那么,为什么我的代码在第二种情况下有效并在第一种情况下终止?
UPD.
为了检查我的文本编辑器是否足够聪明,可以根据行 # coding: utf-8
更改文件的编码,让我们看看实际的字节数:
(第一个例子)
23 20 fd fe ff fa fc ...
(第二个例子)
23 20 63 6f 64 69 6e 67 3a 20 75 74 66 2d 38 0a
23 20 fd fe ff fa fc ...
这些 f 字节用于 cp1251 中的西里尔字母,它们在 UTF-8 中无效。
此外,如果我这样编辑源代码:
# coding: utf-8
# эюяъь (some Russian comment)
print('Hehehey')
print('эюяъь')
我会遇到错误:
SyntaxError: (unicode error) 'utf-8' codec can't decode byte 0xfd ...
所以,不幸的是我的文本编辑器不是那么聪明。
因此,在上面的示例中 源文件未从 cp1251 转换为 UTF-8。
这似乎是如何强制执行默认编码的严格行为的怪癖。在 the tokenizer function, decoding_gets
中,如果它还没有找到明确的编码声明(tok->encoding
仍然是 NULL
),它会逐个字符地检查行中是否有无效的 UTF-8 字符,并且弹出你看到的引用 PEP 263 的 SyntaxError
。
但如果指定了编码,check_coding_spec
will have defined tok->encoding
, and that default encoding strict test将被完全绕过;它不会被声明的编码测试所取代。
通常情况下,这会在实际解析代码时引起问题,但看起来注释是以精简的方式处理的:一旦注释字符 #
被识别,the tokenizer just grabs and discards characters until it sees a newline or EOF
,它根本没有尝试对它们做任何事情(这是有道理的;解析注释只是在浪费时间,而这些时间本可以花在实际运行的东西上)。
因此,您观察到的行为是:编码声明通过字符检查禁用严格的文件范围字符,以检查在未明确声明编码时应用的有效 UTF-8,并且注释是特殊大小写的,因此它们的内容被忽略,允许注释中的垃圾字节逃避检测。