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

>>> from subprocess import *
>>> command_stdout = Popen(['ls', '-l'], stdout=PIPE).communicate()[0]
>>>
>>> command_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(command_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将字节对象转换为str?


当前回答

如果您不知道编码,那么要以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就是在这里用不在范围内的臭名昭著的序数词窒息的。

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

更新20170116:感谢Nearoo的评论-也有可能使用反斜杠替换错误处理程序对所有未知字节进行反斜杠转义。这只适用于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'))

其他回答

我想你真的想要这样:

>>> 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文档(或在此处阅读)。

如果您不知道编码,那么要以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就是在这里用不在范围内的臭名昭著的序数词窒息的。

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

更新20170116:感谢Nearoo的评论-也有可能使用反斜杠替换错误处理程序对所有未知字节进行反斜杠转义。这只适用于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'))

虽然@Aaron Maenpaa的回答很有效,但一位用户最近问道:

还有什么更简单的方法吗?”fhand.read().decode(“ASCII”)'[…]太长了!

您可以使用:

command_stdout.decode()

decode()有一个标准参数:

codec.decode(obj,编码='utf-8',错误='strict')

将universal_newlines设置为True,即。

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

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


Python 3:

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

or

str(b'hello', encoding)

Python 2:

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

or

unicode('hello', encoding)