Convertir des octets en chaîne

2311

J'utilise ce code pour obtenir la sortie standard d'un programme externe:

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

La méthode communic () renvoie un tableau d'octets:

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

Cependant, je voudrais travailler avec la sortie comme une chaîne Python normale. Pour que je puisse l'imprimer comme ceci:

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

Je pensais que c'est à cela que sert la méthode binascii.b2a_qp () , mais quand je l'ai essayée, j'ai de nouveau obtenu le même tableau d'octets:

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

Comment puis-je reconvertir la valeur des octets en chaîne? Je veux dire, en utilisant les "batteries" au lieu de le faire manuellement. Et je voudrais que ce soit OK avec Python 3.

Tomas Sedovic
la source
47
pourquoi ça ne str(text_bytes)marche pas ? Cela me semble bizarre.
Charlie Parker
13
@CharlieParker Car str(text_bytes)ne peut pas spécifier l'encodage. Selon ce qui se trouve dans text_bytes, text_bytes.decode('cp1250) `peut entraîner une chaîne très différente de text_bytes.decode('utf-8').
Craig Anderson
6
donc la strfonction ne se convertit plus en une vraie chaîne. On DOIT dire explicitement un encodage pour une raison quelconque, je suis trop paresseux pour lire pourquoi. Il suffit de le convertir utf-8et de voir si votre code fonctionne. par exemplevar = var.decode('utf-8')
Charlie Parker
1
@CraigAnderson: unicode_text = str(bytestring, character_encoding)fonctionne comme prévu sur Python 3. Bien qu'il unicode_text = bytestring.decode(character_encoding)soit plus préférable d'éviter la confusion avec juste ce str(bytes_obj)qui produit une représentation textuelle au bytes_objlieu de la décoder en texte: str(b'\xb6', 'cp1252') == b'\xb6'.decode('cp1252') == '¶'etstr(b'\xb6') == "b'\\xb6'" == repr(b'\xb6') != '¶'
jfs

Réponses:

3677

Vous devez décoder l'objet bytes pour produire une chaîne:

>>> 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
la source
58
L'utilisation "windows-1252"n'est pas non plus fiable (par exemple, pour d'autres versions linguistiques de Windows), ne serait-il pas préférable de l'utiliser sys.stdout.encoding?
nikow
12
Peut-être que cela aidera quelqu'un plus loin: Parfois, vous utilisez un tableau d'octets pour les communications TCP. Si vous souhaitez convertir un tableau d'octets en chaîne supprimant les caractères '\ x00' finaux, la réponse suivante n'est pas suffisante. Utilisez ensuite b'example \ x00 \ x00'.decode ('utf-8'). Strip ('\ x00').
Wookie88
2
J'ai corrigé un bogue concernant sa documentation sur bugs.python.org/issue17860 - n'hésitez pas à proposer un correctif. S'il est difficile de contribuer - les commentaires sur la façon de l'améliorer sont les bienvenus.
anatoly techtonik
44
En Python 2.7.6 ne gère pas b"\x80\x02\x03".decode("utf-8")-> UnicodeDecodeError: 'utf8' codec can't decode byte 0x80 in position 0: invalid start byte.
martineau
9
Si le contenu est constitué de valeurs binaires aléatoires, la utf-8conversion est susceptible d'échouer. Voir plutôt la réponse @techtonik (ci-dessous) stackoverflow.com/a/27527728/198536
wallyk
215

Vous devez décoder la chaîne d'octets et la transformer en chaîne de caractères (Unicode).

Sur Python 2

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

ou

unicode('hello', encoding)

Sur Python 3

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

ou

str(b'hello', encoding)
dF.
la source
2
Sur Python 3, que faire si la chaîne est dans une variable?
Alaa M.
1
@AlaaM .: la même chose. Si vous avez variable = b'hello', alorsunicode_text = variable.decode(character_encoding)
jfs
182

Je pense que cette façon est facile:

>>> bytes_data = [112, 52, 52]
>>> "".join(map(chr, bytes_data))
'p44'
Sisso
la source
6
Merci, votre méthode a fonctionné pour moi alors qu'aucune autre ne l'a fait. J'avais un tableau d'octets non codé dont j'avais besoin transformé en chaîne. J'essayais de trouver un moyen de le ré-encoder pour que je puisse le décoder en chaîne. Cette méthode fonctionne parfaitement!
leetNightshade
5
@leetNightshade: pourtant, il est terriblement inefficace. Si vous avez un tableau d'octets, vous n'avez qu'à décoder.
Martijn Pieters
12
@Martijn Pieters Je viens de faire un simple benchmark avec ces autres réponses, en exécutant plusieurs 10 000 courses stackoverflow.com/a/3646405/353094 Et la solution ci-dessus était en fait beaucoup plus rapide à chaque fois. Pour 10 000 exécutions en Python 2.7.7, cela prend 8 ms, contre les autres à 12 ms et 18 ms. Certes, il pourrait y avoir des variations en fonction de l'entrée, de la version Python, etc. Cela ne me semble pas trop lent.
leetNightshade
5
@Martijn Pieters Oui. Donc, avec ce point, ce n'est pas la meilleure réponse pour le corps de la question qui a été posée. Et le titre est trompeur, n'est-ce pas? Il / elle veut convertir une chaîne d'octets en une chaîne régulière, pas un tableau d'octets en une chaîne. Cette réponse fonctionne bien pour le titre de la question qui a été posée.
leetNightshade
5
Pour python 3, cela devrait être équivalent à bytes([112, 52, 52])- btw bytes est un mauvais nom pour une variable locale exactement parce que c'est un module intégré p3
Mr_and_Mrs_D
92

Si vous ne connaissez pas l'encodage, alors pour lire l'entrée binaire en chaîne de manière compatible Python 3 et Python 2, utilisez l'ancien encodage 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'))

Parce que le codage est inconnu, attendez-vous à ce que les symboles non anglais se traduisent en caractères de cp437(les caractères anglais ne sont pas traduits, car ils correspondent dans la plupart des codages à un octet et UTF-8).

Le décodage d'une entrée binaire arbitraire en UTF-8 n'est pas sûr, car vous pouvez obtenir ceci:

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

La même chose s'applique à latin-1, qui était populaire (par défaut?) Pour Python 2. Voir les points manquants dans la mise en page de la page de code - c'est là que Python s'étrangle avec l'infâme ordinal not in range.

MISE À JOUR 20150604 : Il y a des rumeurs selon lesquelles Python 3 a la surrogateescapestratégie d'erreur pour encoder des trucs en données binaires sans perte de données et plantages, mais il a besoin de tests de conversion [binary] -> [str] -> [binary], pour valider les performances et la fiabilité.

MISE À JOUR 20170116 : Merci au commentaire de Nearoo - il y a aussi une possibilité de slash échapper tous les octets inconnus avec le backslashreplacegestionnaire d'erreurs. Cela ne fonctionne que pour Python 3, donc même avec cette solution de contournement, vous obtiendrez toujours une sortie incohérente de différentes versions 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'))

Voir le support Unicode de Python pour plus de détails.

MISE À JOUR 20170119 : J'ai décidé d'implémenter le décodage d'échappement slash qui fonctionne à la fois pour Python 2 et Python 3. Il devrait être plus lent que la cp437solution, mais il devrait produire des résultats identiques sur chaque version 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
la source
6
Je pense vraiment que Python devrait fournir un mécanisme pour remplacer les symboles manquants et continuer.
anatoly techtonik
@techtonik: Cela ne fonctionnera pas sur un tableau comme cela fonctionnait en python2.
user2284570
@ user2284570 voulez-vous dire la liste? Et pourquoi cela devrait fonctionner sur des tableaux? Surtout des tableaux de flotteurs ..
anatoly techtonik
Vous pouvez également ignorer les erreurs unicode avec b'\x00\x01\xffsd'.decode('utf-8', 'ignore')en python 3.
Antonis Kalou
3
@anatolytechtonik Il est possible de laisser la séquence d'échappement dans la chaîne et de continuer: cela b'\x80abc'.decode("utf-8", "backslashreplace")se traduira par '\\x80abc'. Ces informations proviennent de la page de documentation unicode qui semble avoir été mise à jour depuis la rédaction de cette réponse.
Nearoo
86

Dans Python 3 , l'encodage par défaut est "utf-8", vous pouvez donc utiliser directement:

b'hello'.decode()

ce qui équivaut à

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

D'un autre côté, en Python 2 , l'encodage par défaut est l'encodage de chaîne par défaut. Ainsi, vous devez utiliser:

b'hello'.decode(encoding)

encodingest l'encodage que vous souhaitez.

Remarque: la prise en charge des arguments de mots clés a été ajoutée dans Python 2.7.

lmiguelvargasf
la source
41

Je pense que vous voulez vraiment ceci:

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

La réponse d'Aaron était correcte, sauf que vous devez savoir quel encodage utiliser. Et je crois que Windows utilise 'windows-1252'. Cela n'aura d'importance que si vous avez des caractères inhabituels (non ASCII) dans votre contenu, mais cela fera une différence.

D'ailleurs, le fait qu'il ne importe est la raison pour laquelle Python déplacé à l' aide de deux types différents pour les données binaires et texte: il ne peut pas convertir par magie entre eux, car il ne sait pas l'encodage à moins que vous le dites! La seule façon que VOUS sauriez est de lire la documentation Windows (ou de la lire ici).

mcherm
la source
3
open()fonction pour les flux de texte ou Popen()si vous le transmettez, universal_newlines=Truedécidez par magie de l'encodage des caractères pour vous ( locale.getpreferredencoding(False)dans Python 3.3+).
jfs
2
'latin-1'est un encodage textuel avec tous les points de code définis, vous pouvez donc l'utiliser pour lire efficacement une chaîne d'octets dans le type de chaîne pris en charge par Python (donc textuellement sur Python 2, dans Unicode pour Python 3).
tripleee
@tripleee: 'latin-1'est un bon moyen d'obtenir du mojibake. Il existe également une substitution magique sur Windows: il est étonnamment difficile de transférer des données d'un processus à un autre, par exemple dir: \xb6-> \x14(l'exemple à la fin de ma réponse)
jfs
32

Définissez universal_newlines sur True, c'est-à-dire

command_stdout = Popen(['ls', '-l'], stdout=PIPE, universal_newlines=True).communicate()[0]
Changement de contexte
la source
5
J'utilise cette méthode et cela fonctionne. Cependant, il s'agit simplement de deviner l'encodage en fonction des préférences de l'utilisateur sur votre système, il n'est donc pas aussi robuste que d'autres options. C'est ce qu'il fait, référençant docs.python.org/3.4/library/subprocess.html: "Si universal_newlines vaut True, [stdin, stdout et stderr] seront ouverts en tant que flux de texte en mode newlines universel en utilisant l'encodage renvoyé par les paramètres régionaux .getpreferredencoding (False). "
twasbrillig
Sur 3.7, vous pouvez (et devriez) faire à la text=Trueplace de universal_newlines=True.
Boris
23

Alors que la réponse de @Aaron Maenpaa fonctionne, un utilisateur a récemment demandé :

Existe-t-il un moyen plus simple? 'fhand.read (). decode ("ASCII")' [...] C'est si long!

Vous pouvez utiliser:

command_stdout.decode()

decode()a un argument standard :

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

serv-inc
la source
.decode()ces utilisations 'utf-8'peuvent échouer (la sortie de la commande peut utiliser un codage de caractères différent ou même retourner une séquence d'octets indécodable). Bien que si l'entrée est ascii (un sous-ensemble de utf-8), cela .decode()fonctionne.
jfs
23

Pour interpréter une séquence d'octets comme un texte, vous devez connaître le codage de caractères correspondant:

unicode_text = bytestring.decode(character_encoding)

Exemple:

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

lsLa commande peut produire une sortie qui ne peut pas être interprétée comme du texte. Les noms de fichiers sous Unix peuvent être n'importe quelle séquence d'octets, sauf la barre oblique b'/'et zéro b'\0':

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

Essayer de décoder une telle soupe d'octets en utilisant des augmentations de codage utf-8 UnicodeDecodeError.

Ça peut être pire. Le décodage peut échouer en silence et produire du mojibake si vous utilisez un mauvais encodage incompatible:

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

Les données sont corrompues mais votre programme ne sait pas qu'une erreur s'est produite.

En général, le codage de caractères à utiliser n'est pas intégré dans la séquence d'octets elle-même. Vous devez communiquer ces informations hors bande. Certains résultats sont plus probables que d'autres et il chardetexiste donc un module qui peut deviner l'encodage des caractères. Un seul script Python peut utiliser plusieurs encodages de caractères à différents endroits.


lssortie peut être convertie en une chaîne de caractères en utilisant python os.fsdecode() fonction qui réussit même pour les noms de fichiers indécodables (il utilise sys.getfilesystemencoding()et surrogateescapegestionnaire d'erreurs sur Unix):

import os
import subprocess

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

Pour obtenir les octets d'origine, vous pouvez utiliser os.fsencode().

Si vous passez un universal_newlines=Trueparamètre, puis subprocessutilise locale.getpreferredencoding(False)pour décoder les octets, par exemple, il peut être cp1252sous Windows.

Pour décoder le flux d'octets à la volée, io.TextIOWrapper() pourrait être utilisé: exemple .

Différentes commandes peuvent utiliser différents codages de caractères pour leur sortie, par exemple, dirla commande interne ( cmd) peut utiliser cp437. Pour décoder sa sortie, vous pouvez passer l'encodage explicitement (Python 3.6+):

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

Les noms de fichiers peuvent différer de os.listdir()(qui utilise l'API Windows Unicode), par exemple, '\xb6'peuvent être remplacés par '\x14'les mappages de codec cp437 de Python b'\x14'pour contrôler le caractère U + 0014 au lieu de U + 00B6 (¶). Pour prendre en charge les noms de fichiers avec des caractères Unicode arbitraires, voir Décoder la sortie PowerShell contenant éventuellement des caractères Unicode non ASCII dans une chaîne Python

jfs
la source
16

Puisque cette question pose réellement sur la subprocesssortie, vous avez une approche plus directe disponible car Popenaccepte un mot-clé d' encodage (dans 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 réponse générale pour les autres utilisateurs est de décoder les octets en texte:

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

Sans argument, sys.getdefaultencoding()sera utilisé. Si vos données ne le sont pas sys.getdefaultencoding(), vous devez spécifier le codage explicitement dans l' decodeappel:

>>> b'caf\xe9'.decode('cp1250')
'café'
wim
la source
3
Ou avec Python 3.7, vous pouvez passer text=Truepour décoder stdin, stdout et stderr en utilisant l'encodage donné (s'il est défini) ou le système par défaut sinon. Popen(['ls', '-l'], stdout=PIPE, text=True).
Boris
Le décodage de la lssortie à l'aide de l' utf-8encodage peut échouer (voir l'exemple dans ma réponse de 2016 ).
jfs
1
@Boris: si le encodingparamètre est donné, alors le textparamètre est ignoré.
jfs
11

Si vous devez obtenir ce qui suit en essayant decode():

AttributeError: l'objet 'str' n'a pas d'attribut 'decode'

Vous pouvez également spécifier le type d'encodage directement dans une distribution:

>>> my_byte_str
b'Hello World'

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

Lorsque \r\nje travaille avec des données de systèmes Windows (avec des fins de ligne), ma réponse est

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

Pourquoi? Essayez ceci avec un Input.txt multiligne:

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

Toutes vos fins de ligne seront doublées (à \r\r\n), ce qui entraînera des lignes vides supplémentaires. Les fonctions de lecture de texte de Python normalisent généralement les fins de ligne afin que les chaînes n'utilisent que \n. Si vous recevez des données binaires d'un système Windows, Python n'a pas la possibilité de le faire. Donc,

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

va répliquer votre fichier d'origine.

bers
la source
Je cherchais depuis .replace("\r\n", "\n")plus longtemps. C'est la réponse si vous voulez rendre correctement le HTML.
mhlavacka
5

J'ai fait une fonction pour nettoyer une liste

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
la source
6
Vous pouvez réellement enchaîner tous les .strip, .replace, .encode, etc appels dans une compréhension de liste et seulement sur la liste itérer une fois au lieu de Enumérer les cinq fois.
Taylor Edmiston
1
@TaylorEdmiston Peut-être que cela économise sur l'allocation mais le nombre d'opérations resterait le même.
JulienD
5

Pour Python 3, c'est une approche beaucoup plus sûre et Pythonique pour convertir de byteà 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')

Production:

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
la source
5
1) Comme l'a dit @bodangly, la vérification de type n'est pas du tout pythonique. 2) La fonction que vous avez écrite est nommée " byte_to_str", ce qui implique qu'elle renverra une chaîne, mais elle imprime uniquement la valeur convertie et affiche un message d'erreur en cas d'échec (mais ne déclenche pas d'exception). Cette approche est également impythonique et obscurcit la bytes.decodesolution que vous avez fournie.
cosmicFluke
3

From sys - Paramètres et fonctions spécifiques au système :

Pour écrire ou lire des données binaires depuis / vers les flux standard, utilisez le tampon binaire sous-jacent. Par exemple, pour écrire des octets dans stdout, utilisez sys.stdout.buffer.write(b'abc').

Zhichang Yu
la source
3
Le canal vers le sous-processus est déjà un tampon binaire. Votre réponse ne parvient pas à déterminer comment obtenir une valeur de chaîne à partir de la bytesvaleur résultante .
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
la source
1
Bien que ce code puisse répondre à la question, fournir un contexte supplémentaire concernant la manière et / ou la raison pour laquelle il résout le problème améliorerait la valeur à long terme de la réponse. N'oubliez pas que vous répondrez à la question des lecteurs à l'avenir, pas seulement à la personne qui pose la question maintenant! Veuillez modifier votre réponse pour ajouter une explication et donner une indication des limitations et hypothèses applicables. Cela ne fait pas de mal non plus de mentionner pourquoi cette réponse est plus appropriée que d'autres.
Dev-iL
Une explication serait de mise.
Peter Mortensen
1

Pour votre cas spécifique de «exécuter une commande shell et obtenir sa sortie sous forme de texte au lieu d'octets», sur Python 3.7, vous devez utiliser subprocess.runet passer text=True(ainsi que capture_output=Truepour capturer la sortie)

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

textutilisé pour être appelé universal_newlines, et a été modifié (enfin, aliasé) dans Python 3.7. Si vous souhaitez prendre en charge les versions Python avant 3.7, passez au universal_newlines=Truelieu detext=True

Boris
la source
0

Si vous souhaitez convertir des octets, pas seulement une chaîne convertie en octets:

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

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

Ce n'est cependant pas très efficace. Cela transformera une image de 2 Mo en 9 Mo.

HCLivess
la source
-1

essaye ça

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