【问题标题】:LPTHW Exercise 17. Why the output from len() is not what the exercise says?LPTHW 练习 17. 为什么 len() 的输出不是练习所说的?
【发布时间】:2018-05-31 22:01:56
【问题描述】:

我编写了以下 Python 3 脚本:

from sys import argv
from os.path import exists

script, from_file, to_file = argv

print(f"Copying from {from_file} to {to_file}")

in_file = open(from_file)
indata = in_file.read()

print(f"The input file is {len(indata)} bytes long")

print(f"Does the output file exist? {exists(to_file)}")
print("Ready, hit RETURN to continue, CTRL-C to abort.")
input()

out_file = open(to_file, 'w')
out_file.write(indata)

print("Alright, all done.")

out_file.close()
in_file.close()

显然len(indata) 的输出应该是:

The input file is 21 bytes long

但我明白了:

The input file is 46 bytes long

from_file 是一个名为 test.txt 的文件,其中包含文本“这是一个测试文件”。

我仔细检查了 test.txt 中的文本。我认为差异可能在计算机上,因为我使用的是 Windows 而老师没有。

Expected output of the exercise according to Zed

这是我在这里的第一篇文章,我已经尝试找到有关此问题的信息。虽然我发现了关于练习 17 的一些问题,但我没有发现字节差异。

【问题讨论】:

  • 您的文件可能包含空格。哦,是的:您使用的是哪个 Python 版本?
  • Windows 和我的 Ubuntu VM 都确认这应该是 20 字节长。书中可能有勘误。
  • Ups,谢谢,@brunodesthuilliers,超级新手的错误。 Python 3,我将编辑问题。
  • 我看到@Mangohero1。您的输出最接近“正确的”。感谢您的检查。
  • 这看起来很像“这是一个测试文件”的 utf-16 编码表示 - 除了 BOM(应该是 "\xff\xfe",而不是“ÿþ”)。您必须了解字符串/字节字符串/unicode 字符串等在 Python2 和 Python3 中是完全不同的野兽。您是如何生成测试文件的?

标签: python python-3.x


【解决方案1】:

短版

你得到这个输出是因为文件被编码为 UTF-16,可能是因为你用来保存它的编辑器在 Windows 上有这种行为,而且你没有指定一个编码来读取它,所以 Python 猜错了。为避免此类问题,您应始终向 open 函数添加编码参数,无论是读取还是写入:

in_file = open(from_file, encoding='utf-16')
# ...
out_file = open(to_file, 'w', encoding='utf-16')

加长版

21 是编码为带有终止 LF 字符 ('\n') 且不带 byte order mark (BOM) 的 UTF-8 时文件中的字节数。

46 是编码为 UTF-16 并带有终止 CR+LF 组合 ('\r\n') 和 BOM(字节顺序标记)的文件中的字节数。

尽管我们想将文本视为“只是文本”,但它必须以某种方式编码为字节(有关更多信息,请参阅this Q&A)。在 Linux 上,最广泛遵循的约定是对所有内容都使用 UTF-8。在 Windows 上,UTF-16 更为常见,但您也可以获得其他编码。

Python 的 open 函数有一个 encoding 参数,您可以使用该参数告诉 Python 您打开的文件是 UTF-16,然后您会得到不同的结果:

in_file = open(from_file, encoding='utf-16')

它在做什么呢?好吧,the open function is documented to use locale.getpreferredencoding(False) if you don't specify an encoding,所以你可以通过输入import locale; locale.getpreferredencoding(False) 来查找。但是我可以告诉你 Windows 上的首选编码是Windows-1252,从而为你省力。如果您将字符串 "This is a test file." 编码为 UTF-16,并将其解码为 Windows-1252,您将看到您发现的异常字符串:

>>> line = "This is a test file."
>>> line_bytes = line.encode('utf-16')
>>> line_bytes.decode('windows-1252')
'ÿþT\x00h\x00i\x00s\x00 \x00i\x00s\x00 \x00a\x00 \x00t\x00e\x00s\x00t\x00 \x00f\x00i\x00l\x00e\x00.\x00'

ÿþ 是 Windows-1252 处理 BOM 的方式。还有一些不太对劲的地方,因为len(line_bytes) 只有 42,而不是 46,所以我不得不假设行尾发生了其他事情;如果将\r\n 添加到原始字符串中,则会得到一个 46 个字符的字符串。

请注意,即使在 Linux 上,Zed 的输出也具有误导性:输入文件是 21 个 Unicode 代码点 长,而不是 21 个字节。它恰好也是 21 个字节,只是因为文件中的所有字符都在 UTF-8 的 ASCII 子集中(这是 Linux 上的首选编码,每个字符可以编码为一个字节)。

【讨论】:

  • 真的很有趣@trentcl。我重复了完整的练习,得到了不同的结果,42 个字节。这一次,在我尝试脚本之前,我用 Windows 记事本打开了 test.txt,然后在点之后按了“Supr”,以确保没有多余的行或我看不到的东西。由于我得到了不同的结果,因此发生了一些变化...顺便说一句,感谢您的回答,我赞成您的评论,但是,我还没有足够的声誉来“公开”赞成。
  • @Naiara 没问题,欢迎来到 Stack Overflow!您可以通过单击旁边的复选标记来接受答案,但我建议您等待一天左右再这样做,因为回答的问题不太可能引起注意,包括点赞、更正和其他答案。
  • @Naiara 顺便说一句,您可以在记事本中编辑文件,当您单击“另存为...”时,您可以使用编码选项以 UTF-8 保存。大多数编辑器以一种或另一种方式支持此功能。我不知道你会如何在 PowerShell 中做到这一点,或者是否有可能。
  • 我将文件保存为 UTF-8。现在的结果是The input file is 23 bytes long,它非常接近原始结果。我想我的问题有点基本或毫无意义,我的意思是,这样一个简单的文件中有 23 个字节或 42 个字节......但感谢你们所有的回答,我学到了一些关于编码格式的新知识,这很棒。
  • @Naiara 这可能意味着它被编码为没有行终止符但带有字节顺序标记的 UTF-8(UTF-8 中的 BOM 为 3 个字节,另请参见 this answer),但你仍在将其解码为 Windows-1252。如果您现在print(repr(indata)),您可能会得到类似'This is a test file.' 的信息,您必须指定正确的编码以正确读取(或写入)文本文件。
猜你喜欢
  • 2016-03-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-01
  • 1970-01-01
相关资源
最近更新 更多