题 将字节转换为字符串?


我正在使用此代码从外部程序获取标准输出:

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

communic()方法返回一个字节数组:

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

我以为那是什么的 binascii.b2a_qp() 方法是,但是当我尝试它时,我再次得到相同的字节数组:

>>> binascii.b2a_qp(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 3。


1243
2018-03-03 12:23


起源




答案:


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

>>> b"abcde"
b'abcde'

# utf-8 is used here because it is a very common encoding, but you
# need to use the encoding your data is actually in.
>>> b"abcde".decode("utf-8") 
'abcde'

2050
2018-03-03 12:26



考虑到这是一个如此简单的问题,这个“解决方案”特别难以找到(至少对我而言)...我想在子流程文档的某个地方加上一条线,因为我打赌很多像我这样的新手会使用子进程时遇到这个问题。有人知道有关python文档的贡献吗? - mathtick
运用 "windows-1252" 也不可靠(例如,对于Windows的其他语言版本),不是最好使用 sys.stdout.encoding? - nikow
这是我第二次忘记这一点,它仍然无法在文档中找到,甚至在unicode部分也找不到。多可惜。 - Profpatsch
也许这会对某些人有所帮助:有时你会使用字节数组来表示e.x. TCP通信。如果要将字节数组转换为字符串,请删除尾随的'\ x00'字符,以下答案是不够的。使用b'example \ x00 \ x00'.decode('utf-8')。strip('\ x00')然后。 - Wookie88
在Python 2.7.6中无法处理 b"\x80\x02\x03".decode("utf-8")  - > UnicodeDecodeError: 'utf8' codec can't decode byte 0x80 in position 0: invalid start byte。 - martineau


我觉得这种方式很简单:

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

120
2017-08-22 12:57



谢谢你,你的方法在没有其他人的情况下为我工作。我有一个非编码的字节数组,我需要把它变成一个字符串。试图找到一种方法来重新编码它,以便我可以将其解码为字符串。这种方法效果很好! - leetNightshade
@leetNightshade:但效率非常低。如果你有一个字节数组,你只需要解码。 - Martijn Pieters♦
@Martijn Pieters我刚刚用这些其他答案做了一个简单的基准测试,运行了多次10,000次运行 stackoverflow.com/a/3646405/353094 而且上述解决方案实际上每次都要快得多。对于Python 2.7.7中的10,000次运行,它需要8ms,而其他运行时间为12ms和18ms。当然,根据输入,Python版本等可能存在一些变化。对我来说似乎不太慢。 - leetNightshade
@Martijn Pieters是的。因此,就这一点而言,这不是所提问题的主体的最佳答案。标题是误导,不是吗?他/她想将字节字符串转换为常规字符串,而不是将字节数组转换为字符串。这个答案适用于所提问题的标题。 - leetNightshade
对于python 3,这应该相当于 bytes([112, 52, 52])  - btw bytes对于局部变量来说是一个坏名字,因为它是一个内置的p3 - Mr_and_Mrs_D


您需要解码字节字符串并将其转换为字符(unicode)字符串。

b'hello'.decode(encoding)

要么

str(b'hello', encoding)

99
2018-03-03 12:28



请注意 str Python 2中的函数(至少2.7.5我正在运行)不支持第二个编码参数,所以最好配合 decode 方法,如果您希望您的代码在Python 2和3上工作。 - metakermit
@dF。 :这不适用于python3。 - user2284570
@ user2284570 str(s,'utf-8')在Python3中为我工作 - Kat


如果您不知道编码,那么要以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来说很流行(默认?)。请参阅中的缺失点 代码页布局  - 这是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'))

看到 https://docs.python.org/3/howto/unicode.html#python-s-unicode-support 详情。

更新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'))

57
2017-12-17 14:23



我真的觉得Python应该提供一种机制来替换丢失的符号并继续。 - anatoly techtonik
辉煌!这比@Sisso的256 MB文件的方法要快得多! - wallyk
@techtonik:这对于在python2中工作的数组不起作用。 - user2284570
@ user2284570你的意思是列表?为什么它应该在数组上工作?特别是花车阵列.. - anatoly techtonik
@anatolytechtonik有可能将转义序列保留在字符串中并继续: b'\x80abc'.decode("utf-8", "backslashreplace") 会导致 '\\x80abc'。这些信息来自于 unicode文档页面 自写这个答案以来,似乎已经更新了。 - Nearoo


我想你真正想要的是这个:

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

Aaron的回答是正确的,除了你需要知道要使用的WHICH编码。我相信Windows使用'windows-1252'。只有在你的内容中有一些不寻常的(非ascii)字符才有意义,但它会产生影响。

顺便说一句,事实上它是重要的是Python转向使用两种不同类型的二进制和文本数据:它不能在它们之间神奇地转换,因为除非你告诉它,它不知道编码!您将知道的唯一方法是阅读Windows文档(或在此处阅读)。


33
2017-07-18 19:51



open() 用于文本流或 Popen() 如果你通过它 universal_newlines=True 为你做神奇的角色编码(locale.getpreferredencoding(False) 在Python 3.3+)。 - jfs
'latin-1' 是一个设置了所有代码点的逐字编码,因此您可以使用它来有效地将字节字符串读入您的Python支持的任何类型的字符串(所以在Python 2上逐字逐句地转换为Python 3的Unicode)。 - tripleee


在Python 3中,默认编码是 "utf-8",所以你可以直接使用:

b'hello'.decode()

这相当于

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

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

b'hello'.decode(encoding)

哪里 encoding 是你想要的编码。

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


31
2018-06-29 14:21



@Artyer,在Python 3中默认编码,根据你提供的链接,是 Default encoding is 'utf-8'. 如果我说默认编码是,为什么我的回答错了 utf-8 隐含地,我的意思是它永远 utf-8。 - lmiguelvargasf
@Artyer,我明白你的意思了。我的意思是你可以检查一般的默认编码,而不仅仅是python 3,这就是为什么我没有把你运行时获得的值放在什么原因 sys.getdefaultencoding()。 - lmiguelvargasf
@Artyer,我已经更新了我的回答,感谢您的评论。 - lmiguelvargasf


将universal_newlines设置为True,即

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

26
2018-01-21 15:31



我一直在使用这种方法,它的工作原理。虽然,它只是根据您系统上的用户偏好来猜测编码,因此它不像其他一些选项那样强大。这就是它正在做的,引用docs.python.org/3.4/library/subprocess.html:“如果universal_newlines为True,[stdin,stdout和stderr]将使用locale返回的编码以通用换行模式打开文本流.getpreferredencoding(假)“。 - twasbrillig


@Aaron Maenpaa的回答 只是工作,一个用户 最近问道

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

您可以使用

command_stdout.decode()

decode() 有一个 标准论点

codecs.decode(obj, encoding='utf-8', errors='strict')


15
2017-11-13 10:24