带有编解码器标头的 ASCII 安全文件的编码问题,具体取决于行数

Posted

技术标签:

【中文标题】带有编解码器标头的 ASCII 安全文件的编码问题,具体取决于行数【英文标题】:Encoding issue with ASCII-safe file with codec header, depending on line count 【发布时间】:2016-12-26 05:22:09 【问题描述】:

这是 Windows 上 Python 3.5.2 的神奇错误,它杀死了我的一天。以下文件在此系统上失败:

C:\Python35\python.exe encoding-problem-cp1252.py
 File "encoding-problem-cp1252.py", line 2
SyntaxError: encoding problem: cp1252

几乎不包含任何内容 - 除了coding 标头之外,还有一堆空行,但是删除任何行,即使是空行,它也会再次起作用。我认为这是一个本地问题,所以我设置了 job on AppVeyor 以显示相同的行为。

Python 发生了什么?

下面的文件有一个binary accurate version:

#!/usr/bin/env python
# -*- coding: cp1252 -*-


"""
There is nothing in this file, except that it is more
than 50 lines long. Running it with Python 3.5.2 on
Windows gives the following error:

    >python encoding-problem-cp1252.py
      File "encoding-problem-cp1252.py", line 2
    SyntaxError: encoding problem: cp1252

    >python
    Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:01:18) [MSC v.1900 32 bit (Intel)] on win32
    Type "help", "copyright", "credits" or "license" for more information.

If you remove any lines from this file, it will
execute successfully.
"""



def restore(dump):
  """













  """
  return



def main():
  print('ok')



if __name__ == '__main__':
  main()

【问题讨论】:

不能在windows上运行,这很难调试;该文件在 Mac OS X 和 Linux 上运行良好。听起来你发现了一个错误,请在bugs.python.org 提交一个 查看relevant source code 我怀疑那里的 CRLF 处理。如果您将文件从 Unix 行尾(现在这样)转换为 Windows 行尾,会发生什么?更长的文件肯定会解决缓冲问题。 @MartijnPieters,当文件转换为 CRLF 时,错误消失了,但我不会将所有源代码转换为 CRLF,因为 Windows 是这些脚本的辅助平台。 感谢您确认我的预感是正确的。 报告于bugs.python.org/issue27797 【参考方案1】:

这看起来像是由issue #20731 引起的回归。看起来位置计算假设总会有 CRLF 行结尾,而您的文件只有 LF 字符,导致不正确的偏移量being calculated here:

fd = fileno(tok->fp);
/* Due to buffering the file offset for fd can be different from the file
 * position of tok->fp.  If tok->fp was opened in text mode on Windows,
 * its file position counts CRLF as one char and can't be directly mapped
 * to the file offset for fd.  Instead we step back one byte and read to
 * the end of line.*/
pos = ftell(tok->fp);
if (pos == -1 ||
    lseek(fd, (off_t)(pos > 0 ? pos - 1 : pos), SEEK_SET) == (off_t)-1) 
    PyErr_SetFromErrnoWithFilename(PyExc_OSError, NULL);
    goto cleanup;

当您将文件转换为使用 Windows (CRLF) 行结尾时,问题就消失了,但我可以理解,对于跨平台脚本,这不是一个实际的解决方案。

我已提交issue #27797;这应该在 Python 本身中修复。

【讨论】:

问题是ftell(tok->fp) 返回-1。触发 bug 所需的换行数取决于 shebang 的长度和编码规范。要计算文件位置ftell,需要从实际 OS 文件位置中减去缓冲区中未读字节数。问题是缓冲区中的行尾已经被转换为 LF,而 OS 文件位置基于未翻译的行尾。所以ftell 假设文件使用 CRLF 行结尾,因此每个未读 LF 减去 2 个字节。在正确的条件下,它返回 -1,这看起来像一个错误。 其他人? ;-) 顺便说一句,我只查看了使ftell 返回-1 的这些特定情况。它还可以返回小于 -1 的位置,在这种情况下,lseek 调用会失败,因为它是 SEEK_SET @eryksun:哎呀!感谢您的分析!

以上是关于带有编解码器标头的 ASCII 安全文件的编码问题,具体取决于行数的主要内容,如果未能解决你的问题,请参考以下文章

UnicodeEncodeError:“ascii”编解码器无法编码字符

Python3'ascii'编解码器无法编码字符

Python,Docker - “ascii”编解码器无法编码字符

google gsutil:ascii 编解码器无法编码字符

“UnicodeEncodeError:‘ascii’编解码器无法编码字符”

编码给出“'ascii'编解码器无法编码字符......序数不在范围内(128)”