Baytları bir dizeye dönüştürme


2307

Harici bir programdan standart çıktı almak için bu kodu kullanıyorum:

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

Communications () yöntemi bir bayt dizisi döndürür:

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

Ancak, çıktı ile normal bir Python dizesi olarak çalışmak istiyorum. Böylece şöyle yazdırabilirim:

>>> 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 () yöntemi bunun için olduğunu düşündüm , ama denediğimde, yine aynı bayt dizisini aldım:

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

Bayt değerini dizeye nasıl geri dönüştürebilirim? Yani, manuel olarak yapmak yerine "pilleri" kullanmak. Ve Python 3 ile iyi olmasını istiyorum.


47
neden çalışmıyor str(text_bytes)? Bu bana tuhaf geliyor.
Charlie Parker

13
@CharlieParker Çünkü str(text_bytes)kodlamayı belirleyemiyoruz. Text_bytes içinde ne olduğuna bağlı olarak, text_bytes.decode('cp1250) `için çok farklı bir dizeye neden olabilir text_bytes.decode('utf-8').
Craig Anderson

6
böylece strişlev artık gerçek bir dizgeye dönüşmez. Bir nedenle nedenini okumak için tembel olmak için nedense açıkça bir kodlama söylemeliyim. Sadece dönüştürün utf-8ve ur kodunun çalışıp çalışmadığını görün. örneğinvar = var.decode('utf-8')
Charlie Parker

1
@CraigAnderson: unicode_text = str(bytestring, character_encoding)Python 3'te beklendiği gibi çalışır. Ancak unicode_text = bytestring.decode(character_encoding), str(bytes_obj)metne bytes_objkodunu çözmek yerine bir metin temsili üreten karışıklıktan kaçınmak daha çok tercih edilir : str(b'\xb6', 'cp1252') == b'\xb6'.decode('cp1252') == '¶'vestr(b'\xb6') == "b'\\xb6'" == repr(b'\xb6') != '¶'
jfs

Yanıtlar:


3674

Bir dize oluşturmak için bayt nesnesinin kodunu çözmeniz gerekir:

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

58
Kullanmak "windows-1252"da güvenilir değildir (örneğin, Windows'un diğer dil sürümleri için), kullanmak en iyisi olmaz sys.stdout.encodingmı?
nikow

12
Belki bu daha fazla yardımcı olacaktır: Bazen eski TCP iletişimi için bayt dizisi kullanırsınız. Bayt dizisini sondaki '\ x00' karakterlerini kesen dizeye dönüştürmek istiyorsanız, aşağıdaki cevap yeterli değildir. Daha sonra b'example \ x00 \ x00'.decode ('utf-8'). Şerit ('\ x00') kullanın.
Wookie88

2
Bugs.python.org/issue17860 adresinde belgelemekle ilgili bir hata doldurdum - bir yama önermekten çekinmeyin. Katkıda bulunmak zorsa - bunu nasıl geliştirebileceğinizi açıklar.
anatoly techtonik

44
Python 2.7.6'da işlem görmez b"\x80\x02\x03".decode("utf-8")-> UnicodeDecodeError: 'utf8' codec can't decode byte 0x80 in position 0: invalid start byte.
martineau

9
İçerik rastgele ikili değerler ise, utf-8dönüşümün başarısız olması muhtemeldir. Bunun yerine bkz. @Techtonik cevap (aşağıda) stackoverflow.com/a/27527728/198536
wallyk

215

Bayt dizgisinin kodunu çözmeniz ve bir karakter (Unicode) dizesine dönüştürmeniz gerekir.

Python 2'de

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

veya

unicode('hello', encoding)

Python 3'te

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

veya

str(b'hello', encoding)

2
Python 3'te, dize bir değişkendeyse ne olur?
Alaa M.

1
@AlaaM .: aynı. Eğer varsa variable = b'hello', o zamanunicode_text = variable.decode(character_encoding)
jfs

182

Bu şekilde kolay olduğunu düşünüyorum:

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

6
Teşekkür ederim, başka hiçbiri yapmadığında yönteminiz benim için çalıştı. Ben bir dize dönüştü gereken kodlanmamış bir bayt dizisi vardı. Ben bir dize içine çözebilir böylece yeniden kodlamak için bir yol bulmaya çalışıyordu. Bu yöntem mükemmel çalışıyor!
leetNightshade

5
@ leetNightshade: yine de çok verimsiz. Bir bayt diziniz varsa, sadece kodunu çözmeniz gerekir.
Martijn Pieters

12
@Martijn Pieters Bu diğer cevaplarla basit bir kıyaslama yaptım, birden fazla 10.000 koşu çalıştırdı stackoverflow.com/a/3646405/353094 Ve yukarıdaki çözüm aslında her seferinde çok daha hızlıydı. Python 2.7.7'de 10.000 koşu için 8ms, 12ms ve 18ms'de diğerlerine kıyasla. Girdi, Python sürümü, vb bağlı olarak bazı değişiklikler olabilir. Bana çok yavaş görünmüyor.
leetNightshade

5
@Martijn Pieters Evet. Yani bu noktada, sorulan sorunun bedeni için en iyi cevap bu değil. Ve başlık yanıltıcı, değil mi? Bayt dizesini bir diziye değil, normal bir dizeye dönüştürmek ister. Bu cevap, sorulan sorunun başlığı için uygundur.
17

5
Python 3 için bu eşdeğer olmalıdır bytes([112, 52, 52])- btw bytes yerel bir değişken için kötü bir isim çünkü tam olarak bir p3 yerleşik
Mr_and_Mrs_D

92

Kodlamayı bilmiyorsanız, Python 3 ve Python 2 uyumlu şekilde dizeye ikili girişi okumak için eski MS-DOS CP437 kodlamasını kullanın:

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

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

Kodlama bilinmediğinden, İngilizce olmayan sembollerin karakterlerine çevrilmesini bekleyin cp437(İngilizce karakterler çevrilmez, çünkü çoğu tek bayt kodlama ve UTF-8 ile eşleşirler).

UTF-8'e keyfi ikili giriş kodunu çözmek güvenli değildir, çünkü bunu alabilirsiniz:

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

Aynı için de geçerlidir latin-1(? Varsayılan) eksik noktaları Python 2. See için popüler olan, Codepage Düzeni o rezil ile nerede Python jiklleler olduğunu - ordinal not in range.

GÜNCELLEME 20150604 : Python 3'ün surrogateescapeveri kaybı ve çökmesi olmadan verileri ikili verilere kodlamak için hata stratejisine sahip olduğu söylentileri var , ancak [binary] -> [str] -> [binary]hem performansı hem de güvenilirliği doğrulamak için dönüşüm testlerine ihtiyacı var .

GÜNCELLEME 20170116 : Nearoo'nun yorumu sayesinde - bilinmeyen tüm baytların backslashreplacehata işleyicisiyle kaçışını kesme olasılığı da var . Bu sadece Python 3 için geçerlidir, bu nedenle bu geçici çözümde bile farklı Python sürümlerinden tutarsız çıktı elde edersiniz:

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

Ayrıntılar için Python'un Unicode Desteğine bakın.

GÜNCELLEME 20170119 : Hem Python 2 hem de Python 3 için çalışan eğik çizgi kod çözme uygulamaya karar verdim. Çözümden daha yavaş olmalı cp437, ancak her Python sürümünde aynı sonuçları üretmelidir .

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

6
Gerçekten Python'un eksik sembolleri değiştirmek ve devam etmek için bir mekanizma sağlaması gerektiğini hissediyorum.
anatoly techtonik

@techtonik: Bu, python2'de çalıştığı gibi bir dizide çalışmaz.
user2284570

@ user2284570 Bunu mu demek istediniz? Ve neden dizilerde çalışmalı? Özellikle yüzen diziler ..
anatoly techtonik

Ayrıca b'\x00\x01\xffsd'.decode('utf-8', 'ignore')python 3'teki unicode hatalarını göz ardı edebilirsiniz .
Antonis Kalou

3
@anatolytechtonik Kaçış dizisini dizede bırakma ve devam etme olasılığı vardır: b'\x80abc'.decode("utf-8", "backslashreplace")sonuçlanır '\\x80abc'. Bu bilgi, bu cevabın yazılmasından bu yana güncellenmiş görünen unicode dokümantasyon sayfasından alınmıştır .
Nearoo

86

Python 3'te varsayılan kodlama "utf-8", böylece doğrudan şunları kullanabilirsiniz:

b'hello'.decode()

eşdeğer

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

Öte yandan, Python 2'de kodlama varsayılan olarak varsayılan dize kodlamasıdır. Bu nedenle, şunları kullanmalısınız:

b'hello'.decode(encoding)

encodingistediğiniz kodlama nerede .

Not: Python 2.7'de anahtar kelime bağımsız değişkenleri için destek eklendi.


41

Bence bunu gerçekten istiyorsun:

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

Aaron'un cevabı doğruydu, ancak hangi kodlamayı kullanacağınızı bilmeniz gerekiyor . Ve Windows'un 'windows-1252' kullandığına inanıyorum. Yalnızca içeriğinizde alışılmadık (ASCII olmayan) karakterler olması önemli olacaktır, ancak daha sonra bir fark yaratacaktır.

Bu arada, gerçeği yapar olsun sebebi Python ikili ve metin veri için iki farklı türleri kullanarak taşındı olmasıdır: Bunu söylemek sürece kodlamasını bilmez, çünkü aralarında sihirli dönüştürmek olamaz! SİZİN bilmenin tek yolu Windows belgelerini okumak (veya burada okumak).


3
open()metin akışları için işlev veya Popen()geçerseniz universal_newlines=Truesihirli bir şekilde sizin için karakter kodlaması karar ( locale.getpreferredencoding(False)Python 3.3+).
jfs

2
'latin-1'tüm kod noktaları ayarlanmış bir kelimeyle kodlamadır, bu nedenle bunu bir bayt dizesini Python'unuzun desteklediği dize türüne etkili bir şekilde okumak için kullanabilirsiniz (böylece Python 2'de, Python 3 için Unicode'a sözcük olarak).
tripleee

@tripleee: mojibake 'latin-1'almak için iyi bir yoldur. Ayrıca Windows'da sihirli bir ikame var: bir işlemden değiştirilmemiş bir veriyi borulamak şaşırtıcı derecede zor örneğin dir: \xb6-> \x14(cevabımın sonunda örnek)
jfs

32

Universal_newlines öğesini True olarak ayarlayın;

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

5
Bu yöntemi kullanıyorum ve işe yarıyor. Her ne kadar, sadece sisteminizdeki kullanıcı tercihlerine göre kodlamayı tahmin ediyor, bu yüzden diğer bazı seçenekler kadar sağlam değil. Yaptığı budur, docs.python.org/3.4/library/subprocess.html referansını alıyorum: "Universal_newlines True ise, [stdin, stdout ve stderr], yerel ayar tarafından döndürülen kodlamayı kullanarak evrensel yeni satırlar modunda metin akışları olarak açılacaktır (Yanlış) .getpreferredencoding."
twasbrillig

3.7text=True yerine bunu yapabilirsiniz (ve yapmalısınız) universal_newlines=True.
Boris

23

@Aaron Maenpaa'nın yanıtı çalışırken , bir kullanıcı yakın zamanda sordu :

Daha basit bir yolu var mı? 'fhand.read (). decode ("ASCII")' [...] Çok uzun!

Kullanabilirsiniz:

command_stdout.decode()

decode()Bir sahiptir standart bir tartışma :

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


.decode()kullanımları 'utf-8'başarısız olabilir (komutun çıktısı farklı bir karakter kodlaması kullanabilir veya hatta kodu çözülemeyen bir bayt dizisi döndürebilir). Giriş ascii olsa da (utf-8'in bir alt kümesi) .decode()çalışır.
jfs

22

Bayt dizisini metin olarak yorumlamak için, karşılık gelen karakter kodlamasını bilmeniz gerekir:

unicode_text = bytestring.decode(character_encoding)

Misal:

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

lskomutu metin olarak yorumlanamayan çıktılar üretebilir. Unix'teki dosya adları, eğik çizgi b'/'ve sıfır dışında herhangi bir bayt dizisi olabilir b'\0':

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

Utf-8 kodlama artışlarını kullanarak bu bayt çorbasının kodunu çözmeye çalışın UnicodeDecodeError.

Daha kötü olabilir. Yanlış uyumsuz kodlama kullanırsanız kod çözme sessizce başarısız olabilir ve mojibake üretebilir :

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

Veriler bozuk, ancak programınız bir hata oluştuğunun farkında değil.

Genel olarak, hangi karakter kodlamasının kullanılacağı bayt dizisinin kendisine gömülü değildir. Bu bilgiyi bant dışı olarak iletmeniz gerekir. Bazı sonuçlar diğerlerinden daha olasıdır ve bu nedenle karakter kodlamasını tahminchardet edebilen modül mevcuttur . Tek bir Python betiği farklı yerlerde birden çok karakter kodlaması kullanabilir.


lsçıktı, kod çözülemeyen dosya adlarıos.fsdecode() için bile başarılı olan bir işlev kullanılarak Python dizesine dönüştürülebilir ( Unix'te hata işleyicisi kullanır ve kullanır ):sys.getfilesystemencoding()surrogateescape

import os
import subprocess

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

Orijinal baytları almak için kullanabilirsiniz os.fsencode().

universal_newlines=TrueParametreyi geçerseniz , bayt kodunu çözmek için subprocesskullanır locale.getpreferredencoding(False), örneğin cp1252Windows'ta olabilir.

Bayt akışının anında kodunu çözmek için io.TextIOWrapper() kullanılabilir: örnek .

Farklı komutlar çıktıları için farklı karakter kodlamaları kullanabilirler, örneğin dirdahili komut ( cmd) cp437 kullanabilir. Çıktının kodunu çözmek için kodlamayı açıkça geçebilirsiniz (Python 3.6+):

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

Dosya farklı olabilir os.listdir()(Windows Unicode API kullanır), örneğin, '\xb6'ile ikame edilmiş olabilir '\x14'-Python en cp437 kodek haritalar b'\x14'kontrol karakteri U + 0014 yerine U +, 00B6 (¶) için. İsteğe bağlı Unicode karakterleri olan dosya adlarını desteklemek için bkz. Muhtemelen ASCII Unicode olmayan karakterler içeren PowerShell çıktısını Python dizesine kodlama


16

Bu soru aslında subprocessçıktıyı sorduğundan , Popenbir kodlama anahtar kelimesini kabul ettiğinden (Python 3.6+) daha doğrudan bir yaklaşımınız var :

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

Diğer kullanıcılar için genel cevap, baytların metne kodunu çözmektir:

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

Hiçbir argüman olmadan, sys.getdefaultencoding()kullanılacak. Verileriniz değilse sys.getdefaultencoding(), decodeçağrıda kodlamayı açıkça belirtmeniz gerekir :

>>> b'caf\xe9'.decode('cp1250')
'café'

3
Veya Python 3.7 ile text=Trueverilen kodlamayı (ayarlanmışsa) veya sistem varsayılanını kullanarak stdin, stdout ve stderr kodunu çözebilirsiniz. Popen(['ls', '-l'], stdout=PIPE, text=True).
Boris

Kodlama lskullanılarak çıktı kodunun çözülmesi utf-8başarısız olabilir ( 2016'daki yanıtımdaki örneğe bakın ).
jfs

1
@Boris: encodingparametre verilirse textparametre yoksayılır.
jfs

11

Aşağıdakileri deneyerek almanız gerekiyorsa decode():

AttributeError: 'str' nesnesinin 'decode' özelliği yok

Kodlama türünü doğrudan bir yayında da belirleyebilirsiniz:

>>> my_byte_str
b'Hello World'

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

6

Windows sistemlerinden gelen verilerle çalışırken ( \r\nsatır sonlarıyla), cevabım

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

Neden? Bunu çok satırlı Input.txt ile deneyin:

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

Tüm satır sonlarınız iki katına çıkacak \r\r\nve bu da ekstra boş satırlara yol açacaktır . Python'un metin okuma işlevleri genellikle satır sonlarını normalleştirir, böylece dizeler yalnızca kullanır \n. Bir Windows sisteminden ikili veri alırsanız, Python'un bunu yapma şansı yoktur. Böylece,

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

orijinal dosyanızı çoğaltır.


.replace("\r\n", "\n")Uzun zamandır toplama arayışındaydım . HTML'yi düzgün bir şekilde oluşturmak istiyorsanız bu cevaptır.
mhlavacka

5

Bir listeyi temizlemek için bir işlev yaptım

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

6
Sen tüm zincirle gerçekten yapabilen .strip, .replace, .encodetek liste anlama vb aramalar ve sadece yinelerler liste üzerinde bir kez yerine iterating Onun üzerine beş kez.
Taylor Edmiston

1
@TaylorEdmiston Belki dağıtımdan tasarruf sağlar, ancak işlem sayısı aynı kalır.
JulienD

5

Python 3 için, bu çok daha güvenli ve olduğu Pythonic dönüştürmek için yaklaşım byteiçin 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')

Çıktı:

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

5
1) @bodangly'ın dediği gibi, tip kontrolü hiç pitonik değildir. 2) Yazdığınız işlevin " byte_to_str" bir str döndüreceğini gösterir, ancak yalnızca dönüştürülen değeri yazdırır ve başarısız olursa (ancak bir istisna oluşturmazsa) bir hata iletisi yazdırır. Bu yaklaşım aynı zamanda ses kayıtsızdır ve sağladığınız bytes.decodeçözümü gizler .
cosmicFluke

3

Gönderen sys - Sistem özgü parametreleri ve fonksiyonları :

Standart akışlardan / standart akışlara ikili veri yazmak veya okumak için temel ikili arabelleği kullanın. Örneğin, stdout'a bayt yazmak için kullanın sys.stdout.buffer.write(b'abc').


3
Alt işleme boru zaten ikili bir arabellektir. Yanıtınız, elde edilen bytesdeğerden bir dize değerinin nasıl alınacağını ele almıyor .
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))

1
Bu kod soruyu cevaplayabilirken, sorunun nasıl ve / veya neden çözüldüğüne dair ek bağlam sağlamak yanıtlayıcının uzun vadeli değerini artıracaktır. Sadece şu anda soran kişi için değil, gelecekte okuyucular için soruyu cevapladığınızı unutmayın! Lütfen bir açıklama eklemek için cevabınızı düzenleyin ve hangi sınırlamaların ve varsayımların geçerli olduğunu belirtin. Bu cevabın neden diğerlerinden daha uygun olduğunu da belirtmek zordur.
Dev-iL

Bir açıklama yapılabilir.
Peter Mortensen

1

Senin için belirli durumunda "bir kabuk komutunu çalıştırın ve yerine bayt metin olarak üretimini olsun", Python 3.7 üzerinde, kullanmak gerekir subprocess.runve geçmek text=True(yanı sıra capture_output=Trueçıkış yakalamak)

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

textuniversal_newlinesPython 3.7'de çağrıldı ve değiştirildi (iyi, takma). 3.7'den önceki Python sürümlerini desteklemek istiyorsanız, universal_newlines=Trueyerine geçmektext=True


0

Herhangi bir baytı dönüştürmek istiyorsanız, sadece bayta dönüştürülen dize değil:

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

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

Ancak bu çok verimli değildir. 2 MB'lık bir resmi 9 MB'ye dönüştürür.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.