将字节转换为字符串在Python 3中。

3717

我将外部程序的标准输出捕获到一个 bytes 对象中:

>>> from subprocess import *
>>> stdout = Popen(['ls', '-l'], stdout=PIPE).communicate()[0]
>>> stdout
b'total 0\n-rw-rw-r-- 1 thomas thomas 0 Mar  3 07:03 file1\n-rw-rw-r-- 1 thomas thomas 0 Mar  3 07:03 file2\n'

我想将其转换为普通的Python字符串,以便可以像这样打印:

>>> print(stdout)
-rw-rw-r-- 1 thomas thomas 0 Mar  3 07:03 file1
-rw-rw-r-- 1 thomas thomas 0 Mar  3 07:03 file2

如何将Python 3中的bytes对象转换为str

查看在Python 3中将字符串转换为字节的最佳方法以了解另一种方式。


156
为什么 str(text_bytes) 不起作用?这对我来说似乎很奇怪。 - Charlie Parker
71
因为str(text_bytes)无法指定编码方式。根据text_bytes中包含的内容,使用text_bytes.decode('cp1250')可能会得到与text_bytes.decode('utf-8')截然不同的字符串。 - Craig Anderson
17
现在的 str 函数不再自动将其它类型转换为字符串。由于某些原因,必须显式指定编码方式。只需要将编码方式设置为 utf-8 并检查你的代码是否正常工作即可。例如:var = var.decode('utf-8') - Charlie Parker
16
在Python 3中,unicode_text = str(bytestring, character_encoding) 可以正常工作。虽然 unicode_text = bytestring.decode(character_encoding) 更可取,以避免与仅为 bytes_obj 生成文本表示的 str(bytes_obj) 混淆,而不是将其解码为文本: str(b'\xb6', 'cp1252') == b'\xb6'.decode('cp1252') == '¶',并且 str(b'\xb6') == "b'\\xb6'" == repr(b'\xb6') != '¶' - jfs
3
此外,您可以将 text=True 传递给 subprocess.run().Popen(),然后您将获得一个字符串,无需转换字节。或者在任一函数中指定 encoding="utf-8" - David Gilbertson
@CharlieParker - 我从未遇到过使用str(<bytes>)时得到的字符串的用例,但也许只是为了与其他str调用保持一致。我本以为它们可以默认为UTF-8编码,但也许是因为Windows有太多奇怪的编码方式,所以它不会默认为UTF-8;但我同意你的观点。 - NeilG
24个回答

5640

解码 bytes 对象以生成字符串:

>>> b"abcde".decode("utf-8") 
'abcde'

上面的例子假设bytes对象是UTF-8编码,因为它是常用的编码方式。但是,你应该使用实际数据所采用的编码。


1
是的,但考虑到这是来自Windows命令的输出,它应该使用“.decode('windows-1252')”吗? - mcherm
98
使用 "windows-1252" 也不是很可靠(例如对于其他语言版本的 Windows),最好使用 sys.stdout.encoding 吗? - nikow
23
也许这会对某些人有所帮助:有时您会使用字节数组进行例如TCP通信。如果您想将字节数组转换为字符串,并剪切掉末尾的'\x00'字符,那么以下答案可能不足够。请使用b'example\x00\x00'.decode('utf-8').strip('\x00')。 - Wookie88
1
这是官方文档:有关于所有bytesbytearray操作(可以在这些对象上调用的方法),请参见此处:https://docs.python.org/3/library/stdtypes.html#bytes-methods。特别是对于`bytes.decode()`,请参见此处:https://docs.python.org/3/library/stdtypes.html#bytes.decode。 - Gabriel Staples

414

将字节字符串解码并转换为字符(Unicode)字符串。


Python 3:

encoding = 'utf-8'
b'hello'.decode(encoding)
或者
str(b'hello', encoding)
抱歉,我只能使用英文进行回答。

Python 2:

encoding = 'utf-8'
'hello'.decode(encoding)
或者
unicode('hello', encoding)

263

这将一系列字节连接成字符串:

>>> bytes_data = [112, 52, 52]
>>> "".join(map(chr, bytes_data))
'p44'

8
它的效率非常低。如果你有一个字节数组,你只需要解码即可。 - Martijn Pieters
11
@Sasszem说:这种方法是一种变态的表达方式:a.decode('latin-1'),其中 a = bytearray([112, 52, 52])"There Ain't No Such Thing as Plain Text")。如果你已经成功将字节转换为文本字符串,那么你使用了某种编码——在这种情况下是 latin-1 - jfs
6
为了完整起见:在Python 3.6上,bytes(list_of_integers).decode('ascii')''.join(map(chr, list_of_integers))快约三分之一。 - Martijn Pieters

129
如果您不知道编码方式,那么在Python 3和Python 2兼容的方式中,将二进制输入读入字符串时,请使用古老的MS-DOS CP437 编码。
PY3K = sys.version_info >= (3, 0)

lines = []
for line in stream:
    if not PY3K:
        lines.append(line)
    else:
        lines.append(line.decode('cp437'))

由于编码未知,预期非英文符号将被翻译为cp437字符(英文字符不会被翻译,因为它们与大多数单字节编码和UTF-8匹配)。
对任意二进制输入进行UTF-8解码是不安全的,因为可能出现这种情况:
>>> b'\x00\x01\xffsd'.decode('utf-8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 2: invalid
start byte

同样适用于latin-1,这在Python 2中很受欢迎(默认?)。请参阅Codepage Layout中的缺失点 - 这是Python出现臭名昭著的ordinal not in range错误的地方。

更新20150604:有传言称Python 3具有surrogateescape错误策略,可将数据编码为二进制数据而不会丢失数据和崩溃,但需要进行转换测试,[binary] -> [str] -> [binary],以验证性能和可靠性。

更新20170116:感谢Nearoo的评论 - 还有一种可能性是使用backslashreplace错误处理程序来斜杠转义所有未知字节。这仅适用于Python 3,因此即使使用此解决方法,您仍将从不同版本的Python中获得不一致的输出:

PY3K = sys.version_info >= (3, 0)

lines = []
for line in stream:
    if not PY3K:
        lines.append(line)
    else:
        lines.append(line.decode('utf-8', 'backslashreplace'))

详情请见Python的Unicode支持

更新 20170119: 我决定实现针对 Python 2 和 Python 3 都有效的斜杠转义解码。它可能比 cp437 的解决方案慢,但它应该在每个 Python 版本上产生相同的结果

# --- preparation

import codecs

def slashescape(err):
    """ codecs error handler. err is UnicodeDecode instance. return
    a tuple with a replacement for the unencodable part of the input
    and a position where encoding should continue"""
    #print err, dir(err), err.start, err.end, err.object[:err.start]
    thebyte = err.object[err.start:err.end]
    repl = u'\\x'+hex(ord(thebyte))[2:]
    return (repl, err.end)

codecs.register_error('slashescape', slashescape)

# --- processing

stream = [b'\x80abc']

lines = []
for line in stream:
    lines.append(line.decode('utf-8', 'slashescape'))

2
这个答案是不正确的。Latin-1,即ISO-8859-1编码完全能够处理任意二进制数据 - bytes(range(256)).decode('latin-1') 在现代Python版本上可以正常运行,我想不出它为什么会失败。Latin-1的整个重点在于它将每个字节映射到Unicode中的前256个代码点 - 或者更确切地说,自1991年首次发布以来,Unicode的排序方式被选择为使前256个代码点与Latin-1匹配。你可能会在打印字符串时遇到问题,但那完全是无关的。 - Karl Knechtel

125

在Python 3中, 默认编码为"utf-8",因此您可以直接使用:

b'hello'.decode()

相当于

b'hello'.decode(encoding="utf-8")

另一方面,在Python 2中,编码默认为默认字符串编码。因此,您应该使用:

链接1

b'hello'.decode(encoding)

其中encoding是您想要的编码方式。

注意:关键字参数的支持是在Python 2.7中添加的。


49

我认为你实际上想要的是这个:

>>> from subprocess import *
>>> command_stdout = Popen(['ls', '-l'], stdout=PIPE).communicate()[0]
>>> command_text = command_stdout.decode(encoding='windows-1252')

除了你需要知道使用哪种编码,Aaron的回答是正确的。我认为Windows使用'windows-1252'编码。只有当您的内容中包含一些不寻常的(非ASCII)字符时,这才会有影响。

顺便说一下,它很重要的原因是Python已经采用两种不同类型的二进制和文本数据:它无法自动地在它们之间进行转换,因为它不知道编码,除非您告诉它!唯一的方法是阅读Windows文档(或在此处阅读)。


45

由于这个问题实际上是询问subprocess输出,您有更直接的方法可用。最先进的方法是使用subprocess.check_output并传递text=True(Python 3.7+),以自动使用系统默认编码解码stdout:

text = subprocess.check_output(["ls", "-l"], text=True)

对于 Python 3.6,Popen 接受一个 encoding 关键字:

>>> from subprocess import Popen, PIPE
>>> text = Popen(['ls', '-l'], stdout=PIPE, encoding='utf-8').communicate()[0]
>>> type(text)
str
>>> print(text)
total 0
-rw-r--r-- 1 wim badger 0 May 31 12:45 some_file.txt

如果您不处理子进程输出,那么对于标题中的问题,通常的答案是将字节解码为文本:

>>> b'abcde'.decode()
'abcde'

如果没有参数,将使用sys.getdefaultencoding()。如果您的数据不是sys.getdefaultencoding(),则必须在decode调用中明确指定编码:

>>> b'caf\xe9'.decode('cp1250')
'café'

39

将universal_newlines设置为True,即

command_stdout = Popen(['ls', '-l'], stdout=PIPE, universal_newlines=True).communicate()[0]

3
从 Python 3.7 开始,你可以(而且应该)使用 text=True 而不是 universal_newlines=True。这将使得子进程 stdout 和 stderr 的输出以字符串形式返回,更加易于处理。 - user3064538

37

要将字节序列解释为文本,您需要知道相应的字符编码:

unicode_text = bytestring.decode(character_encoding)

例子:

>>> b'\xc2\xb5'.decode('utf-8')
'µ'

ls 命令可能会产生无法解释为文本的输出。在 Unix 上,文件名可以是除斜杠 b'/' 和零 b'\0' 之外的任何字节序列:

>>> open(bytes(range(0x100)).translate(None, b'\0/'), 'w').close()

我正在尝试使用utf-8编码解码这些字节,但是会出现UnicodeDecodeError错误。

情况可能更糟。如果您使用不兼容的错误编码,则解码可能会默默失败并产生乱码

>>> '—'.encode('utf-8').decode('cp1252')
'—'

数据已损坏,但您的程序仍然不知道故障已发生。
通常,要使用哪种字符编码并未嵌入在字节序列本身中。您必须通过外带信息来传达此信息。一些结果比其他结果更有可能,因此存在可以“猜测”字符编码的chardet模块。单个Python脚本可能在不同的位置使用多个字符编码。

ls输出可以使用os.fsdecode()函数转换为Python字符串,即使对于无法解码的文件名也是如此(它在Unix上使用sys.getfilesystemencoding()surrogateescape错误处理程序):

import os
import subprocess

output = os.fsdecode(subprocess.check_output('ls'))

要获取原始字节,您可以使用os.fsencode()
如果传递universal_newlines=True参数,则subprocess使用locale.getpreferredencoding(False)解码字节,例如,在Windows上可以是cp1252
要实时解码字节流,可以使用io.TextIOWrapper()示例
不同的命令可能使用不同的字符编码进行其输出,例如,dir内部命令(cmd)可能使用cp437。要解码其输出,可以显式传递编码(Python 3.6+):
output = subprocess.check_output('dir', shell=True, encoding='cp437')

文件名可能与os.listdir()不同(它使用Windows Unicode API),例如,'\xb6'可以被替换为'\x14'——Python的cp437编解码器将b'\x14'映射到控制字符U+0014而不是U+00B6(¶)。要支持带有任意Unicode字符的文件名,请参见将可能包含非ASCII Unicode字符的PowerShell输出解码为Python字符串


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接