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,并且注释是特殊大小写的,因此它们的内容被忽略,允许注释中的垃圾字节逃避检测。