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

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


当前回答

尝试使用这个;此函数将忽略所有非字符集(如UTF-8)二进制文件,并返回一个干净的字符串。它针对Python 3.6及更高版本进行了测试。

def bin2str(text, encoding = 'utf-8'):
    """Converts a binary to Unicode string by removing all non Unicode char
    text: binary string to work on
    encoding: output encoding *utf-8"""

    return text.decode(encoding, 'ignore')

在这里,函数将获取二进制并对其进行解码(使用Python预定义的字符集将二进制数据转换为字符,忽略参数忽略二进制中的所有非字符集数据,并最终返回所需的字符串值)。

如果您不确定编码,请使用sys.getdefaultencoding()获取设备的默认编码。

其他回答

对于“运行shell命令并将其输出作为文本而不是字节”的特定情况,在Python 3.7上,应该使用subprocess.run并传入text=True(以及capture_output=True来捕获输出)

command_result = subprocess.run(["ls", "-l"], capture_output=True, text=True)
command_result.stdout  # is a `str` containing your program's stdout

文本过去被称为universal_newlines,在Python 3.7中被更改(嗯,别名)。如果希望支持3.7之前的Python版本,请传入universal_newlines=True而不是text=True

如果您不知道编码,那么要以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'))

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

使用Windows系统中的数据(以行结尾)时,我的答案是

String = Bytes.decode("utf-8").replace("\r\n", "\n")

为什么?尝试使用多行Input.txt:

Bytes = open("Input.txt", "rb").read()
String = Bytes.decode("utf-8")
open("Output.txt", "w").write(String)

所有的行尾都将加倍(到\r\n),导致多余的空行。Python的文本读取函数通常规范化行结尾,以便字符串只使用\n。如果您从Windows系统接收二进制数据,Python就没有机会这样做。因此

Bytes = open("Input.txt", "rb").read()
String = Bytes.decode("utf-8").replace("\r\n", "\n")
open("Output.txt", "w").write(String)

将复制原始文件。