Convertir bytes a una cadena

2310

Estoy usando este código para obtener una salida estándar de un programa externo:

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

El método de comunicación () devuelve una matriz de bytes:

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

Sin embargo, me gustaría trabajar con la salida como una cadena Python normal. Para poder imprimirlo así:

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

Pensé que para eso era el método binascii.b2a_qp () , pero cuando lo probé, obtuve el mismo conjunto de bytes nuevamente:

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

¿Cómo convierto el valor de bytes de nuevo a cadena? Quiero decir, usar las "baterías" en lugar de hacerlo manualmente. Y me gustaría que estuviera bien con Python 3.

Tomás Sedovic
fuente
47
¿Por qué no str(text_bytes)funciona? Esto me parece extraño.
Charlie Parker
13
@CharlieParker Porque str(text_bytes)no se puede especificar la codificación. Dependiendo de lo que hay en text_bytes, text_bytes.decode('cp1250) `podría resultar en una cadena muy diferente a text_bytes.decode('utf-8').
Craig Anderson el
66
entonces la strfunción ya no se convierte en una cadena real. Uno tiene que decir una codificación explícitamente por alguna razón, soy demasiado flojo para leer por qué. Simplemente conviértalo utf-8y vea si su código funciona. por ejemplovar = var.decode('utf-8')
Charlie Parker el
1
@CraigAnderson: unicode_text = str(bytestring, character_encoding)funciona como se esperaba en Python 3. Aunque unicode_text = bytestring.decode(character_encoding)es más preferible evitar la confusión con solo str(bytes_obj)eso produce una representación de texto en bytes_objlugar de decodificarlo en texto: str(b'\xb6', 'cp1252') == b'\xb6'.decode('cp1252') == '¶'ystr(b'\xb6') == "b'\\xb6'" == repr(b'\xb6') != '¶'
jfs

Respuestas:

3677

Necesita decodificar el objeto de bytes para producir una cadena:

>>> 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'
Aaron Maenpaa
fuente
58
El uso "windows-1252"tampoco es confiable (por ejemplo, para versiones de Windows en otros idiomas), ¿no sería mejor usarlo sys.stdout.encoding?
nikow
12
Tal vez esto ayude a alguien más: a veces utiliza una matriz de bytes para la comunicación ex TCP. Si desea convertir la matriz de bytes en cadenas que cortan los caracteres finales '\ x00', la siguiente respuesta no es suficiente. Utilice b'example \ x00 \ x00'.decode ('utf-8'). Strip ('\ x00') entonces.
Wookie88
2
He rellenado un error sobre documentarlo en bugs.python.org/issue17860 ; siéntase libre de proponer un parche. Si es difícil contribuir, los comentarios sobre cómo mejorar son bienvenidos.
anatoly techtonik
44
En Python 2.7.6 no maneja b"\x80\x02\x03".decode("utf-8")-> UnicodeDecodeError: 'utf8' codec can't decode byte 0x80 in position 0: invalid start byte.
Martineau
99
Si el contenido son valores binarios aleatorios, utf-8es probable que la conversión falle. En su lugar, consulte la respuesta @techtonik (a continuación) stackoverflow.com/a/27527728/198536
wallyk
215

Debe decodificar la cadena de bytes y convertirla en una cadena de caracteres (Unicode).

En Python 2

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

o

unicode('hello', encoding)

En Python 3

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

o

str(b'hello', encoding)
dF.
fuente
2
En Python 3, ¿qué pasa si la cadena está en una variable?
Alaa M.
1
@AlaaM .: lo mismo. Si es así variable = b'hello', entoncesunicode_text = variable.decode(character_encoding)
jfs
182

Creo que de esta manera es fácil:

>>> bytes_data = [112, 52, 52]
>>> "".join(map(chr, bytes_data))
'p44'
Sisso
fuente
66
Gracias, su método funcionó para mí cuando nadie más lo hizo. Tenía una matriz de bytes no codificada que necesitaba convertir en una cadena. Estaba tratando de encontrar una manera de volver a codificarlo para poder decodificarlo en una cadena. ¡Este método funciona perfectamente!
leetNightshade
55
@leetNightshade: sin embargo, es terriblemente ineficiente. Si tiene una matriz de bytes, solo necesita decodificar.
Martijn Pieters
12
@Martijn Pieters Acabo de hacer un punto de referencia simple con estas otras respuestas, ejecutando múltiples 10,000 ejecuciones stackoverflow.com/a/3646405/353094 Y la solución anterior fue en realidad mucho más rápida cada vez. Para 10,000 ejecuciones en Python 2.7.7, toma 8 ms, en comparación con las demás a 12 ms y 18 ms. De acuerdo, podría haber alguna variación dependiendo de la entrada, la versión de Python, etc. No me parece demasiado lento.
leetNightshade
55
@Martijn Pieters Sí. Entonces, con ese punto, esta no es la mejor respuesta para el cuerpo de la pregunta que se hizo. Y el título es engañoso, ¿no? Él / ella quiere convertir una cadena de bytes en una cadena normal, no una matriz de bytes en una cadena. Esta respuesta funciona bien para el título de la pregunta que se hizo.
leetNightshade
55
Para python 3, esto debería ser equivalente a bytes([112, 52, 52])- btw bytes es un mal nombre para una variable local exactamente porque es un p3 incorporado
Mr_and_Mrs_D
92

Si no conoce la codificación, para leer la entrada binaria en una cadena en forma compatible con Python 3 y Python 2, use la antigua codificación CP437 de MS-DOS :

PY3K = sys.version_info >= (3, 0)

lines = []
for line in stream:
    if not PY3K:
        lines.append(line)
    else:
        lines.append(line.decode('cp437'))

Como la codificación es desconocida, espere que los símbolos que no están en inglés se traduzcan a caracteres de cp437 (los caracteres en inglés no se traducen, porque coinciden en la mayoría de las codificaciones de byte único y UTF-8).

La decodificación de la entrada binaria arbitraria a UTF-8 no es segura, porque puede obtener esto:

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

Lo mismo se aplica a latin-1, que era popular (¿el predeterminado?) Para Python 2. Vea los puntos que faltan en Diseño de página de códigos : es donde Python se ahoga con infameordinal not in range .

ACTUALIZACIÓN 20150604 : Hay rumores de que Python 3 tiene la surrogateescapeestrategia de error para codificar cosas en datos binarios sin pérdida de datos y fallas, pero necesita pruebas de conversión,[binary] -> [str] -> [binary] para validar tanto el rendimiento como la confiabilidad.

ACTUALIZACIÓN 20170116 : Gracias a los comentarios de Nearoo: también existe la posibilidad de escapar de todos los bytes desconocidos con backslashreplaceun controlador de errores. Eso funciona solo para Python 3, por lo que incluso con esta solución aún obtendrá resultados inconsistentes de diferentes versiones de 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'))

Ver Soporte Unicode de Python para más detalles.

ACTUALIZACIÓN 20170119 : Decidí implementar una decodificación de escape de barra que funciona tanto para Python 2 como para Python 3. Debería ser más lenta que la cp437solución, pero debería producir resultados idénticos en cada versión de 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'))
anatoly techtonik
fuente
66
Realmente siento que Python debería proporcionar un mecanismo para reemplazar los símbolos faltantes y continuar.
anatoly techtonik
@techtonik: Esto no funcionará en una matriz como funcionó en python2.
user2284570
@ user2284570 te refieres a la lista? ¿Y por qué debería funcionar en matrices? Especialmente matrices de carrozas ..
anatoly techtonik
También puede ignorar los errores Unicode con b'\x00\x01\xffsd'.decode('utf-8', 'ignore')Python 3.
Antonis Kalou
3
@anatolytechtonik Existe la posibilidad de dejar la secuencia de escape en la cadena y continuar: b'\x80abc'.decode("utf-8", "backslashreplace")dará como resultado '\\x80abc'. Esta información se tomó de la página de documentación Unicode que parece haberse actualizado desde la redacción de esta respuesta.
Nearoo
86

En Python 3 , la codificación predeterminada es "utf-8", por lo que puede usar directamente:

b'hello'.decode()

que es equivalente a

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

Por otro lado, en Python 2 , la codificación predeterminada es la codificación de cadena predeterminada. Por lo tanto, debe usar:

b'hello'.decode(encoding)

¿Dónde encodingestá la codificación que quieres?

Nota: se agregó soporte para argumentos de palabras clave en Python 2.7.

lmiguelvargasf
fuente
41

Creo que realmente quieres esto:

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

La respuesta de Aaron fue correcta, excepto que necesita saber qué codificación usar. Y creo que Windows usa 'windows-1252'. Solo importará si tiene algunos caracteres inusuales (no ASCII) en su contenido, pero luego marcará la diferencia.

Por cierto, el hecho de que importa es la razón por la que Python se movió para usar dos tipos diferentes de datos binarios y de texto: no puede convertir mágicamente entre ellos, ¡porque no conoce la codificación a menos que usted lo diga! La única forma en que USTED sabría es leer la documentación de Windows (o leerla aquí).

mcherm
fuente
3
open()función para secuencias de texto o Popen()si lo pasa universal_newlines=Truedecida mágicamente la codificación de caracteres por usted ( locale.getpreferredencoding(False)en Python 3.3+).
jfs
2
'latin-1'es una codificación literal con todos los puntos de código establecidos, por lo que puede usarla para leer de manera efectiva una cadena de bytes en cualquier tipo de cadena que su Python admita (de manera literal en Python 2, en Unicode para Python 3).
tripleee
@tripleee: 'latin-1'es una buena manera de obtener mojibake. También hay sustitución mágica en Windows: es sorprendentemente difícil canalizar datos de un proceso a otro sin modificar, por ejemplo dir: \xb6-> \x14(el ejemplo al final de mi respuesta)
jfs
32

Establezca universal_newlines en True, es decir

command_stdout = Popen(['ls', '-l'], stdout=PIPE, universal_newlines=True).communicate()[0]
Cambio de contexto
fuente
55
He estado usando este método y funciona. Sin embargo, solo está adivinando la codificación basada en las preferencias del usuario en su sistema, por lo que no es tan robusta como algunas otras opciones. Esto es lo que está haciendo, haciendo referencia a docs.python.org/3.4/library/subprocess.html: "Si universal_newlines es True, [stdin, stdout y stderr] se abrirán como secuencias de texto en modo universal de nuevas líneas utilizando la codificación devuelta por la configuración regional .getpreferredencoding (Falso) ".
twasbrillig
En 3.7 puedes (y debes) hacer en text=Truelugar de universal_newlines=True.
Boris el
23

Si bien la respuesta de @Aaron Maenpaa simplemente funciona, un usuario recientemente preguntó :

¿Hay alguna manera más simple? 'fhand.read (). decode ("ASCII")' [...] ¡Es tan largo!

Puedes usar:

command_stdout.decode()

decode()tiene un argumento estándar :

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

serv-inc
fuente
.decode()ese uso 'utf-8'puede fallar (la salida del comando puede usar una codificación de caracteres diferente o incluso devolver una secuencia de bytes no codificable). Aunque si la entrada es ascii (un subconjunto de utf-8) entonces .decode()funciona.
jfs
23

Para interpretar una secuencia de bytes como un texto, debe conocer la codificación de caracteres correspondiente:

unicode_text = bytestring.decode(character_encoding)

Ejemplo:

>>> b'\xc2\xb5'.decode('utf-8')
'µ'

lsEl comando puede producir resultados que no se pueden interpretar como texto. Los nombres de archivo en Unix pueden ser cualquier secuencia de bytes, excepto la barra diagonal b'/'y cero b'\0':

>>> open(bytes(range(0x100)).translate(None, b'\0/'), 'w').close()

Intentando decodificar tal sopa de bytes usando los aumentos de codificación utf-8 UnicodeDecodeError.

Puede ser peor La decodificación puede fallar en silencio y producir mojibake si usa una codificación incompatible incorrecta:

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

Los datos están dañados pero su programa no se da cuenta de que se ha producido un error.

En general, qué codificación de caracteres utilizar no está incrustada en la secuencia de bytes en sí. Tienes que comunicar esta información fuera de banda. Algunos resultados son más probables que otros y, por lo tanto chardet, existe un módulo que puede adivinar la codificación de caracteres. Un solo script de Python puede usar codificaciones de caracteres múltiples en diferentes lugares.


lsla salida se puede convertir a una cadena de Python usando la os.fsdecode() función que tiene éxito incluso para nombres de archivo no codificables (usa sys.getfilesystemencoding() y surrogateescapecontrolador de errores en Unix):

import os
import subprocess

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

Para obtener los bytes originales, puede usar os.fsencode().

Si pasa el universal_newlines=Trueparámetro, entonces subprocessusa locale.getpreferredencoding(False) para decodificar bytes, por ejemplo, puede estar cp1252en Windows.

Para decodificar el flujo de bytes sobre la marcha, io.TextIOWrapper() podría usarse: ejemplo .

Los diferentes comandos pueden usar diferentes codificaciones de caracteres para su salida, por ejemplo, direl comando interno ( cmd) puede usar cp437. Para decodificar su salida, puede pasar la codificación explícitamente (Python 3.6+):

output = subprocess.check_output('dir', shell=True, encoding='cp437')

Los nombres de los archivos pueden diferir de os.listdir()(que usa la API de Windows Unicode), por ejemplo, '\xb6'se pueden sustituir con '\x14'los mapas de códec cp437 de Python b'\x14'para controlar el carácter U + 0014 en lugar de U + 00B6 (¶). Para admitir nombres de archivo con caracteres Unicode arbitrarios, consulte Decodificar la salida de PowerShell que posiblemente contenga caracteres Unicode no ASCII en una cadena de Python

jfs
fuente
16

Dado que esta pregunta es realmente sobre la subprocesssalida, tiene un enfoque más directo disponible ya que Popenacepta una palabra clave de codificación (en Python 3.6+):

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

La respuesta general para otros usuarios es decodificar bytes en texto:

>>> b'abcde'.decode()
'abcde'

Sin argumento, sys.getdefaultencoding()será utilizado. Si sus datos no lo son sys.getdefaultencoding(), debe especificar la codificación explícitamente en la decodellamada:

>>> b'caf\xe9'.decode('cp1250')
'café'
wim
fuente
3
O con Python 3.7 puede pasar text=Truea decodificar stdin, stdout y stderr usando la codificación dada (si está configurada) o el sistema predeterminado de lo contrario. Popen(['ls', '-l'], stdout=PIPE, text=True).
Boris
La lssalida de decodificación usando utf-8codificación puede fallar (ver ejemplo en mi respuesta de 2016 ).
jfs
1
@ Boris: si encodingse proporciona el textparámetro , entonces el parámetro se ignora.
jfs
11

Si debe obtener lo siguiente intentando decode():

AttributeError: el objeto 'str' no tiene atributo 'decode'

También puede especificar el tipo de codificación directamente en un molde:

>>> my_byte_str
b'Hello World'

>>> str(my_byte_str, 'utf-8')
'Hello World'
Broper
fuente
6

Cuando trabajo con datos de sistemas Windows (con \r\nterminaciones de línea), mi respuesta es

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

¿Por qué? Pruebe esto con un Input.txt multilínea:

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

Todas las terminaciones de línea se duplicarán (a \r\r\n), lo que dará lugar a líneas vacías adicionales. Las funciones de lectura de texto de Python normalmente normalizan las terminaciones de línea para que las cadenas solo se usen \n. Si recibe datos binarios de un sistema Windows, Python no tiene la oportunidad de hacerlo. Así,

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

replicará su archivo original.

bers
fuente
Estaba buscando .replace("\r\n", "\n")adiciones tanto tiempo. Esta es la respuesta si desea representar HTML correctamente.
mhlavacka
5

Hice una función para limpiar una lista

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
eafloresf
fuente
66
En realidad se puede encadenar todos los .strip, .replace, .encodellamadas, etc en una lista por comprensión y sólo iterar sobre la lista una vez en lugar de la iteración más de cinco veces.
Taylor Edmiston
1
@TaylorEdmiston Tal vez ahorre en la asignación, pero el número de operaciones seguirá siendo el mismo.
JulienD
5

Para Python 3, este es un enfoque mucho más seguro y pitónico para convertir de bytea string:

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

Salida:

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
Inconnu
fuente
55
1) Como dijo @bodangly, la verificación de tipo no es pitónica en absoluto. 2) La función que escribió se llama " byte_to_str", lo que implica que devolverá un str, pero solo imprime el valor convertido e imprime un mensaje de error si falla (pero no genera una excepción). Este enfoque tampoco es propónico y ofusca la bytes.decodesolución que proporcionó.
cosmicFluke
3

Desde sys: parámetros y funciones específicos del sistema :

Para escribir o leer datos binarios desde / hacia las secuencias estándar, use el búfer binario subyacente. Por ejemplo, para escribir bytes en stdout, use sys.stdout.buffer.write(b'abc').

Zhichang Yu
fuente
3
La tubería al subproceso ya es un búfer binario. Su respuesta no aborda cómo obtener un valor de cadena del valor resultante bytes.
Martijn Pieters
1
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))
Leonardo Filipe
fuente
1
Si bien este código puede responder la pregunta, proporciona un contexto adicional sobre cómo y / o por qué resuelve el problema mejoraría el valor a largo plazo de la respuesta. Recuerde que está respondiendo la pregunta para los lectores en el futuro, ¡no solo la persona que pregunta ahora! Por favor, editar su respuesta a añadir una explicación, y dar una indicación de lo que se aplican limitaciones y supuestos. Tampoco hace daño mencionar por qué esta respuesta es más apropiada que otras.
Dev-iL
Una explicación estaría en orden.
Peter Mortensen
1

Para su caso específico de "ejecutar un comando de shell y obtener su salida como texto en lugar de bytes", en Python 3.7, debe usar subprocess.runy pasar text=True(así como capture_output=Truecapturar la salida)

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

textsolía llamarse universal_newlinesy se cambió (bueno, con alias) en Python 3.7. Si desea admitir versiones de Python anteriores a 3.7, pase en universal_newlines=Truelugar detext=True

Boris
fuente
0

Si desea convertir cualquier byte, no solo la cadena convertida a bytes:

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

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

Sin embargo, esto no es muy eficiente. Convierte una imagen de 2 MB en 9 MB.

HCLivess
fuente
-1

prueba esto

bytes.fromhex('c3a9').decode('utf-8') 
Victor Choy
fuente