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

>>> 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?


当前回答

解码字节字符串并将其转换为字符(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)

其他回答

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

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

您可以使用:

command_stdout.decode()

decode()有一个标准参数:

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

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

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

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

>>> 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é'

这将字节列表合并为字符串:

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

对于Python 3,这是一种从字节转换为字符串的更安全和Python方法:

def byte_to_str(bytes_or_str):
    if isinstance(bytes_or_str, bytes): # Check if it's in bytes
        print(bytes_or_str.decode('utf-8'))
    else:
        print("Object not of byte type")

byte_to_str(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')

输出:

total 0
-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和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'))