Converter bytes em uma sequência

2310

Estou usando esse código para obter saída padrão de um programa externo:

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

O método communic () retorna uma 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'

No entanto, eu gostaria de trabalhar com a saída como uma string Python normal. Para que eu pudesse imprimi-lo assim:

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

Eu pensei que é para isso que serve o método binascii.b2a_qp () , mas quando tentei, obtive a mesma matriz de bytes novamente:

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

Como faço para converter o valor de bytes novamente em string? Quero dizer, usando as "baterias" em vez de fazê-lo manualmente. E eu gostaria que tudo estivesse bem com o Python 3.

Tomas Sedovic
fonte
47
por que não str(text_bytes)funciona? Isso me parece bizarro.
Charlie Parker
13
@CharlieParker Porque str(text_bytes)não é possível especificar a codificação. Dependendo do conteúdo de text_bytes, text_bytes.decode('cp1250) `pode resultar em uma string muito diferente de text_bytes.decode('utf-8').
Craig Anderson
6
portanto, a strfunção não se converte mais em uma string real. É preciso dizer uma codificação explicitamente, por algum motivo, que tenho preguiça de ler o porquê. Basta convertê-lo utf-8e ver se seu código funciona. por exemplo,var = var.decode('utf-8')
Charlie Parker
1
@CraigAnderson: unicode_text = str(bytestring, character_encoding)funciona como esperado no Python 3. Embora unicode_text = bytestring.decode(character_encoding)seja mais preferível evitar confusão, apenas str(bytes_obj)isso produz uma representação de texto para, em bytes_objvez de decodificá-la para texto: str(b'\xb6', 'cp1252') == b'\xb6'.decode('cp1252') == '¶'estr(b'\xb6') == "b'\\xb6'" == repr(b'\xb6') != '¶'
jfs

Respostas:

3677

Você precisa decodificar o objeto bytes para produzir uma string:

>>> 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
fonte
58
O uso "windows-1252"também não é confiável (por exemplo, para versões em outros idiomas do Windows), não seria melhor usar sys.stdout.encoding?
Nikow 3/01/12
12
Talvez isso ajude mais alguém: às vezes você usa um array de bytes para a comunicação TCP ex. Se você deseja converter a matriz de bytes em caracteres de corte \ \ x00 à direita, a resposta a seguir não é suficiente. Use b'example \ x00 \ x00'.decode ('utf-8'). Strip ('\ x00') e, em seguida.
precisa saber é o seguinte
2
Eu preenchi um bug sobre a documentação em bugs.python.org/issue17860 - fique à vontade para propor um patch. Se é difícil contribuir - comenta como melhorar isso é bem-vindo.
Anatoly techtonik
44
No Python 2.7.6 não lida com b"\x80\x02\x03".decode("utf-8")-> UnicodeDecodeError: 'utf8' codec can't decode byte 0x80 in position 0: invalid start byte.
18714 martineau
9
Se o conteúdo tiver valores binários aleatórios, utf-8é provável que a conversão falhe. Em vez disso ver a resposta @techtonik (abaixo) stackoverflow.com/a/27527728/198536
wallyk
215

Você precisa decodificar a sequência de bytes e transformá-la em uma sequência de caracteres (Unicode).

No Python 2

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

ou

unicode('hello', encoding)

No Python 3

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

ou

str(b'hello', encoding)
dF.
fonte
2
No Python 3, e se a string estiver em uma variável?
Alaa M.
1
@AlaaM .: o mesmo. Se você tiver variable = b'hello', entãounicode_text = variable.decode(character_encoding)
jfs
182

Eu acho assim fácil:

>>> bytes_data = [112, 52, 52]
>>> "".join(map(chr, bytes_data))
'p44'
Sisso
fonte
6
Obrigado, seu método funcionou para mim quando nenhum outro funcionou. Eu tinha uma matriz de bytes não codificada que precisava ser transformada em uma string. Estava tentando encontrar uma maneira de recodificá-lo para que eu pudesse decodificá-lo em uma string. Este método funciona perfeitamente!
LeetNightshade
5
@leetNightshade: ainda é terrivelmente ineficiente. Se você tiver uma matriz de bytes, precisará decodificar apenas.
Martijn Pieters
12
@Martijn Pieters Acabei de fazer um benchmark simples com essas outras respostas, executando várias 10.000 execuções stackoverflow.com/a/3646405/353094 E a solução acima foi realmente muito mais rápida todas as vezes. Para 10.000 execuções no Python 2.7.7, são necessários 8ms, contra os outros 12ms e 18ms. Concedido que pode haver alguma variação dependendo da entrada, versão do Python, etc. Não me parece muito lento.
LeetNightshade
5
@Martijn Pieters Sim. Portanto, com esse ponto, essa não é a melhor resposta para o corpo da pergunta que foi feita. E o título é enganador, não é? Ele / ela deseja converter uma sequência de bytes em uma sequência regular, não uma matriz de bytes em uma sequência. Esta resposta funciona bem para o título da pergunta que foi feita.
LeetNightshade
5
Para python 3 este deve ser equivalente a bytes([112, 52, 52])- btw bytes é um nome ruim para uma variável local exatamente porque é um P3 builtin
Mr_and_Mrs_D
92

Se você não conhece a codificação, para ler a entrada binária na cadeia de caracteres de maneira compatível com Python 3 e Python 2, use a antiga codificação CP437 do 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 a codificação é desconhecida, espere que símbolos não ingleses sejam traduzidos para caracteres de cp437(caracteres ingleses não são traduzidos, porque correspondem na maioria das codificações de byte único e UTF-8).

A decodificação de entrada binária arbitrária para UTF-8 não é segura, pois você pode obter o seguinte:

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

O mesmo se aplica a latin-1, que era popular (o padrão?) Para o Python 2. Veja os pontos que faltam no Codepage Layout - é onde o Python se engasga com o famoso ordinal not in range.

ATUALIZAÇÃO 20150604 : Há rumores de que o Python 3 tem a surrogateescapeestratégia de erro para codificar coisas em dados binários sem perda de dados e falhas, mas precisa de testes de conversão [binary] -> [str] -> [binary]para validar o desempenho e a confiabilidade.

ATUALIZAÇÃO 20170116 : Graças ao comentário de Nearoo - também há a possibilidade de cortar com escape todos os bytes desconhecidos com o backslashreplacemanipulador de erros. Isso funciona apenas para o Python 3, portanto, mesmo com essa solução alternativa, você ainda terá resultados inconsistentes de diferentes versões do 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'))

Veja Suporte Unicode do Python para obter detalhes.

ATUALIZAÇÃO 20170119 : Decidi implementar a decodificação de escape de barra que funciona tanto para o Python 2 quanto para o Python 3. Deve ser mais lento que a cp437solução, mas deve produzir resultados idênticos em todas as versões do 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
fonte
6
Eu realmente sinto que o Python deve fornecer um mecanismo para substituir os símbolos ausentes e continuar.
Anatoly techtonik
@techtonik: Isso não funcionará em uma matriz como funcionou em python2.
user2284570
@ user2284570 você quer dizer lista? E por que deveria funcionar em matrizes? Especialmente matrizes de carros alegóricos ..
anatoly techtonik
Você também pode simplesmente ignorar erros unicode com b'\x00\x01\xffsd'.decode('utf-8', 'ignore')em python 3.
Antonis Kalou
3
@anatolytechtonik Existe a possibilidade de deixar a sequência de escape na string e seguir em frente: b'\x80abc'.decode("utf-8", "backslashreplace")resultará em '\\x80abc'. Esta informação foi retirada da página de documentação unicode, que parece ter sido atualizada desde a redação desta resposta.
Nearoo
86

No Python 3 , a codificação padrão é "utf-8", então você pode usar diretamente:

b'hello'.decode()

que é equivalente a

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

Por outro lado, no Python 2 , a codificação é padronizada com a codificação de string padrão. Portanto, você deve usar:

b'hello'.decode(encoding)

onde encodingestá a codificação que você deseja.

Nota: o suporte a argumentos de palavras-chave foi adicionado no Python 2.7.

lmiguelvargasf
fonte
41

Eu acho que você realmente quer isso:

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

A resposta de Aaron estava correta, exceto que você precisa saber qual codificação usar. E eu acredito que o Windows usa 'windows-1252'. Só importa se você tiver alguns caracteres incomuns (não ASCII) em seu conteúdo, mas isso fará a diferença.

By the way, o fato de que ele faz questão é a razão que Python se mudou para usando dois tipos diferentes de dados binários e texto: não pode converter magicamente entre eles, porque não sei a codificação, a menos que você diga a ele! A única maneira que você saberia é ler a documentação do Windows (ou leia aqui).

mcherm
fonte
3
open()para fluxos de texto ou, Popen()se você passar, universal_newlines=Truedecide magicamente a codificação de caracteres para você ( locale.getpreferredencoding(False)no Python 3.3 ou superior).
jfs
2
'latin-1'é uma codificação literal com todos os pontos de código definidos, para que você possa efetivamente ler uma sequência de bytes em qualquer tipo de sequência suportada pelo Python (portanto, literalmente no Python 2, no Unicode para Python 3).
Tripleee
Tripleee @: 'latin-1'é uma boa maneira de obter mojibake. Também há substituição mágica no Windows: é surpreendentemente difícil canalizar dados de um processo para outro sem modificações, por exemplo dir: \xb6-> \x14(o exemplo no final da minha resposta)
jfs
32

Defina universal_newlines como True, ou seja,

command_stdout = Popen(['ls', '-l'], stdout=PIPE, universal_newlines=True).communicate()[0]
Mudança de contexto
fonte
5
Eu tenho usado esse método e funciona. Embora apenas adivinhe a codificação com base nas preferências do usuário no seu sistema, não é tão robusta quanto algumas outras opções. É o que está fazendo, referenciando docs.python.org/3.4/library/subprocess.html: "Se universal_newlines for True, [stdin, stdout e stderr] serão abertos como fluxos de texto no modo de novas linhas universais usando a codificação retornada pelo código do idioma .getpreferredencoding (False). "
#
Na versão 3.7, você pode (e deve) fazer em text=Truevez de universal_newlines=True.
Boris
23

Enquanto a resposta de @Aaron Maenpaa simplesmente funciona, um usuário perguntou recentemente :

Existe alguma maneira mais simples? 'fhand.read (). decode ("ASCII")' [...] É tanto tempo!

Você pode usar:

command_stdout.decode()

decode()tem um argumento padrão :

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

serv-inc
fonte
.decode()que usa 'utf-8'pode falhar (a saída do comando pode usar uma codificação de caracteres diferente ou até retornar uma sequência de bytes não codificável). Embora se a entrada for ascii (um subconjunto de utf-8), .decode()funcionará.
jfs 12/04
23

Para interpretar uma sequência de bytes como um texto, é necessário conhecer a codificação de caracteres correspondente:

unicode_text = bytestring.decode(character_encoding)

Exemplo:

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

lsO comando pode produzir uma saída que não pode ser interpretada como texto. Os nomes de arquivo no Unix podem ter qualquer sequência de bytes, exceto barra b'/'e zero b'\0':

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

Tentando decodificar essa sopa de bytes usando a codificação utf-8 aumenta UnicodeDecodeError.

Pode ser pior. A decodificação pode falhar silenciosamente e produzir mojibake se você usar uma codificação incompatível incorreta:

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

Os dados estão corrompidos, mas seu programa permanece inconsciente de que ocorreu uma falha.

Em geral, qual codificação de caracteres usar não é incorporada na própria sequência de bytes. Você precisa comunicar essas informações fora da banda. Alguns resultados são mais prováveis ​​que outros e, portanto chardet, existe um módulo que pode adivinhar a codificação de caracteres. Um único script Python pode usar várias codificações de caracteres em locais diferentes.


lsA saída pode ser convertida em uma string Python usando a os.fsdecode() função que é bem-sucedida mesmo para nomes de arquivos não codificáveis (usa sys.getfilesystemencoding()e surrogateescapemanipulador de erros no Unix):

import os
import subprocess

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

Para obter os bytes originais, você pode usar os.fsencode().

Se você passar universal_newlines=Trueparâmetro, em seguida, subprocessusa locale.getpreferredencoding(False)para decodificar bytes por exemplo, pode ser cp1252no Windows.

Para decodificar o fluxo de bytes em tempo real, io.TextIOWrapper() pode ser usado: exemplo .

Comandos diferentes podem usar codificações de caracteres diferentes para sua saída, por exemplo, diro comando interno ( cmd) pode usar o cp437. Para decodificar sua saída, você pode transmitir a codificação explicitamente (Python 3.6+):

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

Os nomes dos arquivos podem diferir de os.listdir()(que usa a API Unicode do Windows), por exemplo, '\xb6'podem ser substituídos '\x14'pelos mapas de codec cp437 do Python b'\x14'para controlar o caractere U + 0014 em vez de U + 00B6 (¶). Para dar suporte a nomes de arquivos com caracteres Unicode arbitrários, consulte Decodificar a saída do PowerShell que possivelmente contém caracteres Unicode não ASCII em uma sequência Python

jfs
fonte
16

Como essa pergunta está realmente perguntando sobre a subprocesssaída, você tem uma abordagem mais direta disponível, pois Popenaceita uma palavra-chave de codificação (no 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

A resposta geral para outros usuários é decodificar bytes para texto:

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

Sem argumento, sys.getdefaultencoding()será usado. Se seus dados não estiverem sys.getdefaultencoding(), você deve especificar a codificação explicitamente na decodechamada:

>>> b'caf\xe9'.decode('cp1250')
'café'
wim
fonte
3
Ou, com o Python 3.7, você pode text=Truedecodificar stdin, stdout e stderr usando a codificação fornecida (se configurada) ou o padrão do sistema. Popen(['ls', '-l'], stdout=PIPE, text=True).
Boris
A decodificação da lssaída usando a utf-8codificação pode falhar (veja o exemplo na minha resposta de 2016 ).
JFS
1
@ Boris: se o encodingparâmetro for fornecido, o textparâmetro será ignorado.
JFS
11

Se você deve obter o seguinte, tente decode():

AttributeError: o objeto 'str' não possui atributo 'decode'

Você também pode especificar o tipo de codificação diretamente em uma conversão:

>>> my_byte_str
b'Hello World'

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

Ao trabalhar com dados de sistemas Windows (com \r\nterminações de linha), minha resposta é

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

Por quê? Tente isso com uma entrada multilinha:

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

Todas as terminações de sua linha serão dobradas (para \r\r\n), resultando em linhas vazias extras. As funções de leitura de texto do Python normalmente normalizam as terminações de linha, para que as strings sejam usadas apenas \n. Se você receber dados binários de um sistema Windows, o Python não terá chance de fazer isso. Portanto,

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

irá replicar seu arquivo original.

bers
fonte
Eu estava procurando por .replace("\r\n", "\n")adição por tanto tempo. Esta é a resposta se você deseja renderizar HTML corretamente.
Mhlavacka
5

Eu criei uma função para limpar uma 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
fonte
6
Você realmente pode encadear todos os .strip, .replace, .encodechamadas, etc em uma compreensão da lista e apenas iterar sobre a lista uma vez, em vez de iteração mais de cinco vezes.
Taylor Edmiston
1
@TaylorEdmiston Talvez economize na alocação, mas o número de operações permanecerá o mesmo.
JulienD
5

Para Python 3, essa é uma abordagem muito mais segura e Python para converter de bytepara 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')

Resultado:

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
fonte
5
1) Como disse @bodangly, a verificação de tipo não é de forma alguma pitônica. 2) A função que você escreveu é nomeada " byte_to_str", o que implica que ele retornará um str, mas somente imprime o valor convertido e uma mensagem de erro se falhar (mas não gera uma exceção). Essa abordagem também não é sintônica e ofusca a bytes.decodesolução que você forneceu.
CosmicFluke
3

From sys - parâmetros e funções específicos do sistema :

Para gravar ou ler dados binários de / para os fluxos padrão, use o buffer binário subjacente. Por exemplo, para escrever bytes em stdout, use sys.stdout.buffer.write(b'abc').

Zhichang Yu
fonte
3
O canal para o subprocesso é um buffer binário. Sua resposta falha ao abordar como obter um valor de sequência do 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
fonte
1
Embora esse código possa responder à pergunta, fornecer um contexto adicional sobre como e / ou por que resolve o problema melhoraria o valor a longo prazo da resposta. Lembre-se de que você está respondendo à pergunta dos leitores no futuro, não apenas à pessoa que está perguntando agora! Por favor edite sua resposta para adicionar uma explicação, e dar uma indicação do que limitações e premissas se aplicam. Também não custa mencionar por que essa resposta é mais apropriada do que outras.
Dev-iL
Uma explicação estaria em ordem.
Peter Mortensen
1

Para o seu caso específico de "executar um comando shell e obter sua saída como texto em vez de bytes", no Python 3.7, você deve usar subprocess.rune passar text=True(além capture_output=Truede capturar a saída)

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

textcostumava ser chamado universal_newlinese foi alterado (bem, com alias) no Python 3.7. Se você deseja suportar versões do Python anteriores à 3.7, passe em universal_newlines=Truevez detext=True

Boris
fonte
0

Se você deseja converter bytes, não apenas a string convertida em bytes:

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

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

Isso não é muito eficiente, no entanto. Ele transformará uma imagem de 2 MB em 9 MB.

HCLivess
fonte
-1

tente isso

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