【问题标题】:Convert bytes to a string将字节转换为字符串
【发布时间】:2022-01-18 18:35:31
【问题描述】:

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

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

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

>>> 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 也可以。

【问题讨论】:

  • 为什么str(text_bytes) 不起作用?这对我来说似乎很奇怪。
  • @CharlieParker 因为str(text_bytes) 无法指定编码。根据 text_bytes 中的内容,text_bytes.decode('cp1250)` 可能会导致与 text_bytes.decode('utf-8') 完全不同的字符串。
  • 所以str 函数不再转换为真正的字符串。由于某种原因,我不得不明确地说出一种编码,我懒得通读原因。只需将其转换为utf-8 并查看您的代码是否有效。例如var = var.decode('utf-8')
  • @CraigAnderson: unicode_text = str(bytestring, character_encoding) 在 Python 3 上按预期工作。尽管 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') != '¶'

标签: python string python-3.x


【解决方案1】:

我们可以使用 bytes.decode(encoding='utf-8', errors='strict') 解码字节对象以生成字符串 对于文档。点击here

Python3 示例:

byte_value = b"abcde"
print("Initial value = {}".format(byte_value))
print("Initial value type = {}".format(type(byte_value)))
string_value = byte_value.decode("utf-8")
# utf-8 is used here because it is a very common encoding, but you need to use the encoding your data is actually in.
print("------------")
print("Converted value = {}".format(string_value))
print("Converted value type = {}".format(type(string_value)))

输出:

Initial value = b'abcde'
Initial value type = <class 'bytes'>
------------
Converted value = abcde
Converted value type = <class 'str'>

注意:在 Python3 中,默认编码类型为 utf-8。所以,&lt;byte_string&gt;.decode("utf-8")也可以写成&lt;byte_string&gt;.decode()

【讨论】:

    【解决方案2】:

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

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

    见:https://docs.python.org/3/library/stdtypes.html#bytes.decode

    【讨论】:

    • 使用"windows-1252"也不可靠(例如,对于其他语言版本的Windows),使用sys.stdout.encoding不是最好吗?
    • 也许这会进一步帮助某人:有时您使用字节数组作为 e.x. TCP 通信。如果要将字节数组转换为字符串以截断尾随 '\x00' 字符,则以下答案是不够的。然后使用 b'example\x00\x00'.decode('utf-8').strip('\x00')。
    • 我在bugs.python.org/issue17860 填写了一个关于记录它的错误 - 请随时提出补丁。如果很难做出贡献,欢迎 cmets 如何改进。
    • 在 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
    • 如果内容是随机的二进制值,utf-8转换很可能会失败。相反,请参阅@techtonik 答案(下)stackoverflow.com/a/27527728/198536
    【解决方案3】:

    如果您遇到此错误:

    'utf-8 编解码器无法解码字节 0x8a'

    ,那么最好使用以下代码将字节转换为字符串:

    bytes = b"abcdefg"
    string = bytes.decode("utf-8", "ignore") 
    

    享受吧!

    【讨论】:

      【解决方案4】:

      使用.decode() 解码。这将解码字符串。传入'utf-8') 作为里面的值。

      【讨论】:

        【解决方案5】:

        试试这个;此函数将忽略所有非字符集(如utf-8)二进制文件并返回一个干净的字符串。已针对python3.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 预定义字符集将二进制数据转换为字符,ignore 参数忽略二进制文件中的所有非字符集数据,最后返回您想要的 string 值.

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

        【讨论】:

          【解决方案6】:

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

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

          【讨论】:

          • 使用utf-8 编码解码ls 输出可能会失败(参见my answer from 2016 中的示例)。
          • @Boris:如果给定了encoding参数,则忽略text参数。
          • 这是subprocess 的正确答案。如果您只想等待子流程并获得结果,也许仍然强调Popen 几乎总是错误的工具;如文档所述,使用subprocess.runcheck_callcheck_output 的旧功能之一。
          【解决方案7】:

          我认为这种方式很简单:

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

          【讨论】:

          • 谢谢你,你的方法对我有用,而其他方法都没有。我有一个未编码的字节数组,需要将其转换为字符串。试图找到一种重新编码它的方法,以便我可以将它解码成一个字符串。这种方法效果很好!
          • @leetNightshade:但它的效率非常低。如果你有一个字节数组,你只需要解码。
          • @Martijn Pieters 我只是对这些其他答案做了一个简单的基准测试,运行了多次 10,000 次运行stackoverflow.com/a/3646405/353094 而上述解决方案实际上每次都快得多。在 Python 2.7.7 中运行 10,000 次需要 8 毫秒,而其他运行需要 12 毫秒和 18 毫秒。当然,根据输入、Python 版本等可能会有一些变化。对我来说似乎并不太慢。
          • @Sasszem:这种方法是一种变态的表达方式:a.decode('latin-1') where a = bytearray([112, 52, 52]) ("There Ain't No Such Thing as Plain Text"。如果你已经设法将字节转换为文本字符串,那么你使用了一些编码—— latin-1 在这种情况下)
          • 对于 python 3,这应该等同于 bytes([112, 52, 52]) - btw bytes 对局部变量来说是一个坏名字,因为它是 p3 内置
          【解决方案8】:

          试试这个

          bytes.fromhex('c3a9').decode('utf-8') 
          

          【讨论】:

            【解决方案9】:

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

            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

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

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

            数据已损坏,但您的程序仍不知道发生了故障 已经发生了。

            一般来说,使用什么字符编码并不嵌入字节序列本身。您必须在带外传达此信息。有些结果比其他结果更有可能,因此存在可以猜测字符编码的chardet 模块。一个 Python 脚本可能在不同的地方使用多种字符编码。


            ls 输出可以使用os.fsdecode() 转换为 Python 字符串 即使对于undecodable filenames 也能成功的函数(它使用 sys.getfilesystemencoding()surrogateescape 错误处理程序开启 Unix):

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

            要获取原始字节,您可以使用os.fsencode()

            如果您传递 universal_newlines=True 参数,则 subprocess 使用 locale.getpreferredencoding(False) 解码字节,例如,它可以是 cp1252 在 Windows 上。

            要即时解码字节流, io.TextIOWrapper() 可以使用:example

            不同的命令可能使用不同的字符编码 输出例如,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 字符的文件名,请参阅 Decode PowerShell output possibly containing non-ASCII Unicode characters into a Python string

            【讨论】:

              【解决方案10】:

              如果你想转换任何字节,而不仅仅是转换为字节的字符串:

              with open("bytesfile", "rb") as infile:
                  str = base64.b85encode(imageFile.read())
              
              with open("bytesfile", "rb") as infile:
                  str2 = json.dumps(list(infile.read()))
              

              但是,这不是很有效。它将把 2 MB 的图片变成 9 MB。

              【讨论】:

                【解决方案11】:

                对于 Python 3,这是一种从 byte 转换为 string 的更安全且 Pythonic 的方法:

                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
                

                【讨论】:

                • 1) 正如@bodangly 所说,类型检查根本不是pythonic。 2)您编写的函数名为“byte_to_str”,这意味着它将返回一个 str,但它只打印转换后的值,并且如果失败(但不引发异常)。这种方法也是 unpythonic 并且混淆了您提供的 bytes.decode 解决方案。
                【解决方案12】:

                In Python 3,默认编码为"utf-8",所以可以直接使用:

                b'hello'.decode()
                

                相当于

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

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

                b'hello'.decode(encoding)
                

                encoding 是你想要的编码。

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

                【讨论】:

                  【解决方案13】:

                  如果你不知道编码,那么以 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] -&gt; [str] -&gt; [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’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'))
                  

                  【讨论】:

                  • 我真的觉得 Python 应该提供一种机制来替换缺失的符号并继续。
                  • @techtonik : 这不会像在 python2 中那样在数组上工作。
                  • @user2284570 你的意思是列表吗?为什么它应该在数组上工作?尤其是浮点数组..
                  • 您也可以在 python 3 中使用 b'\x00\x01\xffsd'.decode('utf-8', 'ignore') 忽略 unicode 错误。
                  • @anatolytechtonik 有可能将转义序列留在字符串中并继续前进:b'\x80abc'.decode("utf-8", "backslashreplace") 将导致 '\\x80abc'。此信息取自unicode documentation page,自撰写此答案以来似乎已更新。
                  【解决方案14】:

                  来自sys — System-specific parameters and functions

                  要从/向标准流写入或读取二进制数据,请使用底层二进制缓冲区。例如,要将字节写入标准输出,请使用sys.stdout.buffer.write(b'abc')

                  【讨论】:

                  • 子进程的管道已经是一个二进制缓冲区。您的答案未能解决如何从生成的 bytes 值中获取字符串值。
                  【解决方案15】:

                  我想你真的想要这个:

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

                  【讨论】:

                  • open() 用于文本流的函数或Popen() 如果你传递它universal_newlines=True 会神奇地为你决定字符编码(Python 3.3+ 中的locale.getpreferredencoding(False))。
                  • 'latin-1' 是设置了所有代码点的逐字编码,因此您可以使用它来有效地将字节字符串读入您的 Python 支持的任何类型的字符串(因此在 Python 2 上逐字读取到 Unicode Python 3)。
                  • @tripleee: 'latin-1' 是获得 mojibake 的好方法。在 Windows 上也有神奇的替换:将数据从一个进程传输到另一个未修改的进程非常困难,例如 dir: \xb6 -> \x14 (the example at the end of my answer)
                  【解决方案16】:

                  您需要将字节串解码,然后转成字符(Unicode)字符串。

                  在 Python 2 上

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

                  unicode('hello', encoding)
                  

                  在 Python 3 上

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

                  str(b'hello', encoding)
                  

                  【讨论】:

                  • 在 Python 3 上,如果字符串在变量中怎么办?
                  • @AlaaM.: 一样。如果你有variable = b'hello',那么unicode_text = variable.decode(character_encoding)
                  • 对我来说,variable = variable.decode() 自动将其转换为我想要的字符串格式。
                  • @AlexHall> fwiw,您可能想知道 automagic 使用 utf8,如果您不提供它,这是 encoding arg 的默认值。见bytes.decode
                  【解决方案17】:

                  对于您的 特定“运行 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
                  

                  text 曾经被称为universal_newlines,并在 Python 3.7 中被更改(好吧,别名)。如果要支持 Python 3.7 之前的版本,请传入universal_newlines=True 而不是text=True

                  【讨论】:

                    【解决方案18】:

                    虽然@Aaron Maenpaa's answer 正常工作,但用户recently asked

                    还有更简单的方法吗? 'fhand.read().decode("ASCII")' [...] 好长啊!

                    你可以使用:

                    command_stdout.decode()
                    

                    decode() 有一个standard argument

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

                    【讨论】:

                    • .decode() 使用 'utf-8' 可能会失败(命令的输出可能使用不同的字符编码,甚至返回不可解码的字节序列)。虽然如果输入是 ascii(utf-8 的一个子集),那么 .decode() 可以工作。
                    【解决方案19】:

                    如果您通过尝试decode() 得到以下信息:

                    AttributeError: 'str' 对象没有属性 'decode'

                    您也可以直接在强制转换中指定编码类型:

                    >>> my_byte_str
                    b'Hello World'
                    
                    >>> str(my_byte_str, 'utf-8')
                    'Hello World'
                    

                    【讨论】:

                      【解决方案20】:
                      def toString(string):    
                          try:
                              return v.decode("utf-8")
                          except ValueError:
                              return string
                      
                      b = b'97.080.500'
                      s = '97.080.500'
                      print(toString(b))
                      print(toString(s))
                      

                      【讨论】:

                      • 虽然此代码可能会回答问题,但提供有关 如何 和/或 为什么 解决问题的附加 context 将改善答案的长度长期价值。请记住,您正在为将来的读者回答问题,而不仅仅是现在提问的人!请edit您的答案添加解释,并说明适用的限制和假设。提及为什么这个答案比其他答案更合适也没有什么坏处。
                      • 解释一下。
                      【解决方案21】:

                      当处理来自 Windows 系统的数据时(带有\r\n 行尾),我的答案是

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

                      将复制您的原始文件。

                      【讨论】:

                      • 我一直在寻找 .replace("\r\n", "\n") 添加这么久。如果您想正确呈现 HTML,这就是答案。
                      【解决方案22】:

                      我做了一个清理列表的函数

                      def cleanLists(self, lista):
                          lista = [x.strip() for x in lista]
                          lista = [x.replace('\n', '') for x in lista]
                          lista = [x.replace('\b', '') for x in lista]
                          lista = [x.encode('utf8') for x in lista]
                          lista = [x.decode('utf8') for x in lista]
                      
                          return lista
                      

                      【讨论】:

                      • 您实际上可以将所有.strip.replace.encode 等调用链接到一个列表推导中,并且只迭代列表一次而不是迭代五次。跨度>
                      • @TaylorEdmiston 也许它节省了分配,但操作的数量将保持不变。
                      【解决方案23】:

                      将universal_newlines设置为True,即

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

                      【讨论】:

                      • 我一直在使用这种方法,它很有效。虽然,它只是根据系统上的用户偏好来猜测编码,所以它不像其他一些选项那么健壮。这就是它正在做的事情,引用 docs.python.org/3.4/library/subprocess.html:“如果 universal_newlines 为 True,则 [stdin、stdout 和 stderr] 将使用语言环境返回的编码以通用换行符模式作为文本流打开.getpreferredencoding(False)。”
                      • On 3.7 你可以(并且应该)使用text=True 而不是universal_newlines=True
                      猜你喜欢
                      • 2019-10-10
                      • 1970-01-01
                      • 2014-03-09
                      • 1970-01-01
                      相关资源
                      最近更新 更多