Konversi byte ke string

2310

Saya menggunakan kode ini untuk mendapatkan output standar dari program eksternal:

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

Metode berkomunikasi () mengembalikan array byte:

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

Namun, saya ingin bekerja dengan output sebagai string Python normal. Sehingga saya bisa mencetaknya seperti ini:

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

Saya pikir itulah gunanya metode binascii.b2a_qp () , tetapi ketika saya mencobanya, saya mendapatkan array byte yang sama lagi:

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

Bagaimana cara mengubah nilai byte kembali ke string? Maksud saya, menggunakan "baterai" alih-alih melakukannya secara manual. Dan saya ingin itu baik-baik saja dengan Python 3.

Tomas Sedovic
sumber
47
kenapa tidak str(text_bytes)bekerja Ini sepertinya aneh bagi saya.
Charlie Parker
13
@CharlieParker Karena str(text_bytes)tidak dapat menentukan pengkodean. Bergantung pada apa yang ada di text_bytes, text_bytes.decode('cp1250) `mungkin menghasilkan string yang sangat berbeda text_bytes.decode('utf-8').
Craig Anderson
6
jadi strfungsi tidak dikonversi menjadi string nyata lagi. Seseorang HARUS mengatakan penyandian secara eksplisit untuk beberapa alasan saya malas membaca alasannya. Konversikan ke utf-8dan lihat apakah kode ur Anda berfungsi. mis.var = var.decode('utf-8')
Charlie Parker
1
@CraigAnderson: unicode_text = str(bytestring, character_encoding)berfungsi seperti yang diharapkan pada Python 3. Meskipun unicode_text = bytestring.decode(character_encoding)lebih disukai untuk menghindari kebingungan dengan hanya str(bytes_obj)yang menghasilkan representasi teks untuk bytes_objdaripada mendekodekan ke teks: str(b'\xb6', 'cp1252') == b'\xb6'.decode('cp1252') == '¶'danstr(b'\xb6') == "b'\\xb6'" == repr(b'\xb6') != '¶'
jfs

Jawaban:

3677

Anda perlu mendekode objek byte untuk menghasilkan 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
sumber
58
Menggunakan "windows-1252"juga tidak dapat diandalkan (misalnya, untuk Windows versi bahasa lainnya), bukankah lebih baik digunakan sys.stdout.encoding?
nikow
12
Mungkin ini akan membantu seseorang lebih jauh: Kadang-kadang Anda menggunakan array byte untuk komunikasi TCP ex. Jika Anda ingin mengonversi byte array ke string yang memotong karakter trailing '\ x00', jawaban berikut tidak cukup. Gunakan b'example \ x00 \ x00'.decode ('utf-8'). Strip ('\ x00') lalu.
Wookie88
2
Saya telah mengisi bug tentang mendokumentasikannya di bugs.python.org/issue17860 - jangan ragu untuk mengajukan tambalan. Jika sulit untuk berkontribusi - berkomentar bagaimana meningkatkan yang diterima.
anatoly techtonik
44
Dalam Python 2.7.6 tidak menangani b"\x80\x02\x03".decode("utf-8")-> UnicodeDecodeError: 'utf8' codec can't decode byte 0x80 in position 0: invalid start byte.
martineau
9
Jika konten adalah nilai biner acak, utf-8konversi cenderung gagal. Alih-alih lihat jawaban @techtonik (di bawah) stackoverflow.com/a/27527728/198536
wallyk
215

Anda perlu men-decode string byte dan mengubahnya menjadi string karakter (Unicode).

Di Python 2

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

atau

unicode('hello', encoding)

Di Python 3

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

atau

str(b'hello', encoding)
dF.
sumber
2
Pada Python 3, bagaimana jika string dalam sebuah variabel?
Alaa M.
1
@AlaaM .: sama. Jika sudah variable = b'hello', makaunicode_text = variable.decode(character_encoding)
jfs
182

Saya pikir cara ini mudah:

>>> bytes_data = [112, 52, 52]
>>> "".join(map(chr, bytes_data))
'p44'
Sisso
sumber
6
Terima kasih, metodemu bekerja untukku ketika tidak ada yang melakukannya. Saya memiliki byte array non-encoded yang saya butuhkan berubah menjadi string. Sedang berusaha menemukan cara untuk menyandikan ulang sehingga saya bisa mendekodekannya menjadi string. Metode ini bekerja dengan sempurna!
leetNightshade
5
@leetNightshade: namun sangat tidak efisien. Jika Anda memiliki array byte, Anda hanya perlu mendekode.
Martijn Pieters
12
@ Martijn Pieters Saya baru saja melakukan benchmark sederhana dengan jawaban-jawaban lain ini, menjalankan 10.000 run stackoverflow.com/a/3646405/353094 Dan solusi di atas sebenarnya jauh lebih cepat setiap kali. Untuk 10.000 berjalan di Python 2.7.7 dibutuhkan 8ms, dibandingkan yang lain pada 12ms dan 18ms. Memang mungkin ada beberapa variasi tergantung pada input, versi Python, dll. Sepertinya tidak terlalu lambat bagi saya.
leetNightshade
5
@ Martijn Pieters Ya. Jadi dengan poin itu, ini bukan jawaban terbaik untuk pertanyaan yang diajukan. Dan judulnya menyesatkan, bukan? Ia ingin mengonversi string byte ke string biasa, bukan array byte ke string. Jawaban ini berfungsi baik untuk judul pertanyaan yang diajukan.
leetNightshade
5
Untuk python 3 ini harus sama dengan bytes([112, 52, 52])- byte btw adalah nama yang buruk untuk variabel lokal persis karena itu adalah p3 builtin
Mr_and_Mrs_D
92

Jika Anda tidak tahu pengkodeannya, maka untuk membaca input biner ke dalam string dengan cara yang kompatibel dengan Python 3 dan Python 2, gunakan pengkodean MS-DOS CP437 kuno :

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

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

Karena pengodean tidak diketahui, harap simbol non-Inggris untuk menerjemahkan ke karakter cp437(karakter bahasa Inggris tidak diterjemahkan, karena mereka cocok di sebagian besar pengkodean byte tunggal dan UTF-8).

Mendekode input biner acak ke UTF-8 tidak aman, karena Anda mungkin mendapatkan ini:

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

Hal yang sama berlaku untuk latin-1, yang populer (default?) Untuk Python 2. Lihat poin yang hilang dalam Tata Letak Codepage - ini adalah tempat Python tersedak dengan terkenal ordinal not in range.

UPDATE 20150604 : Ada desas-desus bahwa Python 3 memiliki surrogateescapestrategi kesalahan untuk pengkodean hal-hal menjadi data biner tanpa kehilangan data dan crash, tetapi perlu tes konversi [binary] -> [str] -> [binary],, untuk memvalidasi kinerja dan keandalan.

UPDATE 20170116 : Berkat komentar dari Nearoo - ada juga kemungkinan untuk memangkas semua byte yang tidak diketahui dengan backslashreplacepenangan kesalahan. Itu hanya berfungsi untuk Python 3, jadi meskipun dengan solusi ini Anda masih akan mendapatkan output yang tidak konsisten dari versi Python yang berbeda:

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

Lihat Dukungan Unicode Python untuk detailnya.

UPDATE 20170119 : Saya memutuskan untuk mengimplementasikan slash escaping decode yang berfungsi baik untuk Python 2 dan Python 3. Seharusnya lebih lambat daripada cp437solusinya, tetapi harus menghasilkan hasil yang identik pada setiap versi 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'))
techtonik anatoly
sumber
6
Saya benar-benar merasa seperti Python harus menyediakan mekanisme untuk mengganti simbol yang hilang dan melanjutkan.
anatoly techtonik
@ techtonik: Ini tidak akan berfungsi pada array seperti itu bekerja di python2.
user2284570
@ user2284570 maksud Anda daftar? Dan mengapa itu harus bekerja pada array? Terutama array mengapung ..
anatoly techtonik
Anda juga dapat mengabaikan kesalahan unicode dengan b'\x00\x01\xffsd'.decode('utf-8', 'ignore')python 3.
Antonis Kalou
3
@anatolytechtonik Ada kemungkinan untuk meninggalkan urutan pelarian dalam string dan melanjutkan: b'\x80abc'.decode("utf-8", "backslashreplace")akan menghasilkan '\\x80abc'. Informasi ini diambil dari halaman dokumentasi unicode yang tampaknya telah diperbarui sejak penulisan jawaban ini.
Nearoo
86

Di Python 3 , penyandian default adalah "utf-8", sehingga Anda dapat langsung menggunakan:

b'hello'.decode()

yang setara dengan

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

Di sisi lain, dalam Python 2 , pengkodean default ke pengkodean string default. Jadi, Anda harus menggunakan:

b'hello'.decode(encoding)

di mana encodingpengkodean yang Anda inginkan.

Catatan: dukungan untuk argumen kata kunci ditambahkan dalam Python 2.7.

lmiguelvargasf
sumber
41

Saya pikir Anda benar-benar menginginkan ini:

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

Jawaban Harun benar, kecuali bahwa Anda perlu tahu pengkodean mana yang harus digunakan. Dan saya percaya bahwa Windows menggunakan 'windows-1252'. Itu hanya masalah jika Anda memiliki beberapa karakter yang tidak biasa (non-ASCII) di konten Anda, tetapi kemudian akan membuat perbedaan.

By the way, fakta bahwa itu tidak peduli adalah alasan bahwa Python pindah ke menggunakan dua jenis yang berbeda untuk data biner dan teks: tidak dapat mengkonversi ajaib di antara mereka, karena tidak tahu pengkodean kecuali Anda kirim! Satu-satunya cara ANDA akan tahu adalah membaca dokumentasi Windows (atau membacanya di sini).

mcherm
sumber
3
open()berfungsi untuk stream teks atau Popen()jika Anda lulus universal_newlines=Truesecara ajaib memutuskan pengkodean karakter untuk Anda ( locale.getpreferredencoding(False)dalam Python 3.3+)
jfs
2
'latin-1'adalah pengkodean kata demi kata dengan semua titik kode yang diatur, sehingga Anda dapat menggunakannya untuk secara efektif membaca string byte ke jenis string mana pun yang didukung oleh Python Anda (jadi kata demi kata di Python 2, ke dalam Unicode untuk Python 3).
tripleee
@ tripleee: 'latin-1'adalah cara yang baik untuk mendapatkan mojibake. Juga ada substitusi magis pada Windows: itu mengejutkan sulit data pipa dari satu proses ke yang lain misalnya dimodifikasi, dir: \xb6-> \x14(contoh pada akhir jawaban saya)
jfs
32

Set universal_newlines ke True, yaitu

command_stdout = Popen(['ls', '-l'], stdout=PIPE, universal_newlines=True).communicate()[0]
ContextSwitch
sumber
5
Saya telah menggunakan metode ini dan berhasil. Meskipun, itu hanya menebak pengodean berdasarkan preferensi pengguna pada sistem Anda, jadi tidak sekuat beberapa opsi lain. Inilah yang dilakukannya, dengan merujuk docs.python.org/3.4/library/subprocess.html: "Jika universal_newlines Benar, [stdin, stdout dan stderr] akan dibuka sebagai aliran teks dalam mode baris baru universal menggunakan pengodean yang dikembalikan oleh lokal .getpreferredencoding (Salah). "
twasbrillig
Pada 3,7 Anda dapat (dan harus) melakukan text=Truebukan universal_newlines=True.
Boris
23

Sementara jawaban @Aaron Maenpaa hanya berfungsi, pengguna baru-baru ini bertanya :

Apakah ada cara lain yang lebih sederhana? 'fhand.read (). decode ("ASCII")' [...] Ini sangat panjang!

Kamu bisa menggunakan:

command_stdout.decode()

decode()memiliki argumen standar :

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

serv-inc
sumber
.decode()yang menggunakan 'utf-8'mungkin gagal (output perintah dapat menggunakan pengkodean karakter yang berbeda atau bahkan mengembalikan urutan byte yang tidak dapat didekodekan). Padahal jika inputnya adalah ascii (subset dari utf-8) maka .decode()berfungsi.
jfs
23

Untuk menafsirkan urutan byte sebagai teks, Anda harus mengetahui pengkodean karakter yang sesuai:

unicode_text = bytestring.decode(character_encoding)

Contoh:

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

lsperintah dapat menghasilkan output yang tidak dapat diartikan sebagai teks. Nama file di Unix dapat berupa urutan byte apa pun kecuali garis miring b'/'dan nol b'\0':

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

Mencoba untuk men-decode sup byte seperti itu menggunakan kenaikan gaji utf-8 UnicodeDecodeError.

Itu bisa lebih buruk. Dekoding mungkin gagal secara diam-diam dan menghasilkan mojibake jika Anda menggunakan penyandian yang tidak kompatibel yang salah:

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

Data rusak tetapi program Anda tetap tidak menyadari bahwa telah terjadi kegagalan.

Secara umum, pengkodean karakter apa yang digunakan tidak tertanam dalam urutan byte itu sendiri. Anda harus mengkomunikasikan info ini keluar-dari-band. Beberapa hasil lebih mungkin daripada yang lain dan karena itu chardetada modul yang dapat menebak pengkodean karakter. Sebuah skrip Python tunggal dapat menggunakan beberapa pengkodean karakter di tempat yang berbeda.


lsoutput dapat dikonversi ke string Python menggunakan os.fsdecode() fungsi yang berhasil bahkan untuk nama file yang tidak dapat didekodekan (menggunakan sys.getfilesystemencoding()dan surrogateescapepenangan kesalahan di Unix):

import os
import subprocess

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

Untuk mendapatkan byte asli, Anda bisa menggunakan os.fsencode().

Jika Anda melewatkan universal_newlines=Trueparameter kemudian subprocessgunakan locale.getpreferredencoding(False)untuk mendekode byte misalnya, itu bisa cp1252di Windows.

Untuk mendekode byte stream on-the-fly, io.TextIOWrapper() dapat digunakan: contoh .

Perintah yang berbeda dapat menggunakan pengkodean karakter yang berbeda untuk keluarannya misalnya, dirperintah internal ( cmd) dapat menggunakan cp437. Untuk mendekode outputnya, Anda bisa meneruskan penyandian secara eksplisit (Python 3.6+):

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

Nama file mungkin berbeda dari os.listdir()(yang menggunakan Windows Unicode API) misalnya, '\xb6'dapat diganti dengan '\x14'peta codec cp437 -Python b'\x14'untuk mengontrol karakter U + 0014 alih-alih U + 00B6 (¶). Untuk mendukung nama file dengan karakter Unicode yang arbitrer, lihat Decode PowerShell output yang mungkin mengandung karakter Unicode non-ASCII menjadi string Python

jfs
sumber
16

Karena pertanyaan ini sebenarnya menanyakan tentang subprocesskeluaran, Anda memiliki pendekatan yang lebih langsung karena Popenmenerima kata kunci pengodean (dengan 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

Jawaban umum untuk pengguna lain adalah mendekode byte ke teks:

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

Tanpa argumen, sys.getdefaultencoding()akan digunakan. Jika data Anda tidak sys.getdefaultencoding(), maka Anda harus menentukan pengkodean secara eksplisit dalam decodepanggilan:

>>> b'caf\xe9'.decode('cp1250')
'café'
wim
sumber
3
Atau dengan Python 3.7 Anda dapat meneruskan text=Trueuntuk mendekode stdin, stdout dan stderr menggunakan pengkodean yang diberikan (jika diatur) atau sistem default sebaliknya. Popen(['ls', '-l'], stdout=PIPE, text=True).
Boris
lsOutput decoding menggunakan utf-8encoding mungkin gagal (lihat contoh dalam jawaban saya dari 2016 ).
jfs
1
@Boris: jika encodingparameter diberikan, maka textparameter diabaikan.
jfs
11

Jika Anda harus mendapatkan yang berikut dengan mencoba decode():

AttributeError: objek 'str' tidak memiliki atribut 'decode'

Anda juga dapat menentukan jenis penyandian lurus dalam gips:

>>> my_byte_str
b'Hello World'

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

Ketika bekerja dengan data dari sistem Windows (dengan \r\nujung garis), jawaban saya adalah

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

Mengapa? Coba ini dengan Input.txt multiline:

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

Semua ujung garis Anda akan digandakan (ke \r\r\n), mengarah ke garis kosong ekstra. Fungsi membaca teks Python biasanya menormalkan akhir baris sehingga string hanya digunakan \n. Jika Anda menerima data biner dari sistem Windows, Python tidak memiliki kesempatan untuk melakukan itu. Jadi,

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

akan mereplikasi file asli Anda.

bers
sumber
Saya mencari .replace("\r\n", "\n")tambahan begitu lama. Ini adalah jawabannya jika Anda ingin merender HTML dengan benar.
mhlavacka
5

Saya membuat fungsi untuk membersihkan daftar

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
sumber
6
Anda benar-benar dapat rantai semua .strip, .replace, .encodepanggilan, dll dalam satu daftar pemahaman dan hanya iterate atas daftar sekali bukan iterasi itu lima kali.
Taylor Edmiston
1
@TaylorEdmiston Mungkin menghemat alokasi tetapi jumlah operasi akan tetap sama.
JulienD
5

Untuk Python 3, ini adalah pendekatan yang jauh lebih aman dan Pythonic untuk dikonversi dari byteke 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')

Keluaran:

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
sumber
5
1) Seperti yang dikatakan @bodangly, pengecekan tipe bukan pythonic sama sekali. 2) Fungsi yang Anda tulis bernama " byte_to_str" yang menyiratkan akan mengembalikan str, tetapi hanya mencetak nilai yang dikonversi, dan mencetak pesan kesalahan jika gagal (tetapi tidak menimbulkan pengecualian). Pendekatan ini juga unpythonic dan mengaburkan bytes.decodesolusi yang Anda berikan.
cosmicFluke
3

Dari sys - Parameter dan fungsi khusus sistem :

Untuk menulis atau membaca data biner dari / ke stream standar, gunakan buffer biner yang mendasarinya. Misalnya, untuk menulis byte ke stdout, gunakan sys.stdout.buffer.write(b'abc').

Zhichang Yu
sumber
3
Pipa ke subproses sudah menjadi buffer biner. Jawaban Anda gagal membahas cara mendapatkan nilai string dari nilai yang dihasilkan 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
sumber
1
Sementara kode ini dapat menjawab pertanyaan, memberikan konteks tambahan tentang bagaimana dan / atau mengapa memecahkan masalah akan meningkatkan nilai jangka panjang jawaban. Ingatlah bahwa Anda menjawab pertanyaan untuk pembaca di masa depan, bukan hanya orang yang bertanya sekarang! Harap edit jawaban Anda untuk menambahkan penjelasan, dan berikan indikasi batasan dan asumsi apa yang berlaku. Tidak ada salahnya untuk menyebutkan mengapa jawaban ini lebih tepat daripada yang lain.
Dev-iL
Penjelasan akan diurutkan.
Peter Mortensen
1

Untuk kasus spesifik Anda "jalankan perintah shell dan dapatkan output sebagai teks alih-alih byte", pada Python 3.7, Anda harus menggunakan subprocess.rundan meneruskan text=True(dan juga capture_output=Trueuntuk menangkap output)

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

textdulu dipanggil universal_newlines, dan diubah (well, alias) dalam Python 3.7. Jika Anda ingin mendukung versi Python sebelum 3.7, berikan universal_newlines=Truealih-alihtext=True

Boris
sumber
0

Jika Anda ingin mengonversi byte apa pun, bukan hanya string yang dikonversi ke byte:

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

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

Namun, ini tidak terlalu efisien. Ini akan mengubah gambar 2 MB menjadi 9 MB.

HCLivess
sumber
-1

coba ini

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