Перетворити байти в рядок


2306

Я використовую цей код, щоб отримати стандартний вихід із зовнішньої програми:

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

Метод communication () повертає масив байтів:

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

Однак я хотів би працювати з висновком як звичайний рядок Python. Щоб я міг роздрукувати його так:

>>> 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 () , але коли я спробував це, я знову отримав той самий байтовий масив:

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

Як перетворити значення байтів назад у рядок? Я маю на увазі використання "батарей" замість того, щоб робити це вручну. І я хотів би, щоб це було добре з Python 3.


47
чому не str(text_bytes)працює? Мені це здається химерним.
Чарлі Паркер

13
@CharlieParker Тому що str(text_bytes)не можна вказати кодування. Залежно від того, що є у text_bytes, text_bytes.decode('cp1250) `може призвести до дуже різного рядка text_bytes.decode('utf-8').
Крейг Андерсон

6
тому strфункція вже не перетворюється на реальну рядок. Я повинен сказати кодування прямо з якихось причин, я лінуся прочитати, чому. Просто перетворіть його utf-8і подивіться, чи працює ур-код. наприкладvar = var.decode('utf-8')
Чарлі Паркер

1
@CraigAnderson: unicode_text = str(bytestring, character_encoding)працює так, як очікувалося на Python 3. Хоча unicode_text = bytestring.decode(character_encoding)більш бажано уникати плутанини лише з тим, str(bytes_obj)що створює подання тексту, bytes_objа не розшифровувати його на текст: str(b'\xb6', 'cp1252') == b'\xb6'.decode('cp1252') == '¶'іstr(b'\xb6') == "b'\\xb6'" == repr(b'\xb6') != '¶'
jfs

Відповіді:


3673

Вам потрібно розшифрувати об'єкт байтів, щоб створити рядок:

>>> 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
Використання "windows-1252"також не є надійним (наприклад, для інших мовних версій Windows), чи не найкраще це використовувати sys.stdout.encoding?
nikow

12
Можливо, це допоможе комусь далі: Іноді ви використовуєте масив байтів для колишнього зв'язку TCP. Якщо ви хочете перетворити масив байтів у відсікання рядків, що знаходяться в кінці "\ x00", наступного відповіді недостатньо. Використовуйте b'example \ x00 \ x00'.decode ('utf-8'). Strip ('\ x00').
Wookie88

2
Я заповнив помилку щодо її документування на bugs.python.org/issue17860 - не соромтеся запропонувати виправлення. Якщо важко зробити свій внесок - коментарі, як покращити, вітаються.
anatoly technonik

44
У Python 2.7.6 не обробляється b"\x80\x02\x03".decode("utf-8")-> UnicodeDecodeError: 'utf8' codec can't decode byte 0x80 in position 0: invalid start byte.
мартіно

9
Якщо вміст є випадковими бінарними значеннями, utf-8конверсія, ймовірно, не вдасться. Натомість див. Відповідь @techtonik (нижче) stackoverflow.com/a/27527728/198536
wallyk

214

Вам потрібно розшифрувати рядок байтів і перетворити його в символьний рядок (Unicode).

На Python 2

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

або

unicode('hello', encoding)

На Python 3

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

або

str(b'hello', encoding)

2
На Python 3, що робити, якщо рядок знаходиться в змінній?
Алаа М.

1
@AlaaM .: те саме. Якщо у вас є variable = b'hello', тоunicode_text = variable.decode(character_encoding)
jfs

182

Я думаю, що цей спосіб простий:

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

6
Дякую, ваш метод працював на мене, коли ніхто інший цього не робив. У мене був некодований байтовий масив, який мені потрібен перетворився на рядок. Намагався знайти спосіб перекодувати його, щоб я міг розшифрувати його в рядок. Цей метод прекрасно працює!
leetNightshade

5
@leetNightshade: але це надзвичайно неефективно. Якщо у вас є байтовий масив, вам потрібно лише розшифрувати.
Martijn Pieters

12
@Martijn Pieters Я просто зробив простий орієнтир з цими іншими відповідями, виконуючи декілька 10000 пробіг stackoverflow.com/a/3646405/353094 І вищезазначене рішення було насправді набагато швидше кожен раз. Для 10 000 пробіжок у Python 2.7.7 це займає 8 мс проти інших у 12 мс та 18 мс. За умови, що може бути певна зміна залежно від введення, версії Python тощо. Мені це здається не дуже повільним.
leetNightshade

5
@Martijn Pieters Так. Отже, з цього моменту, це не найкраща відповідь на питання, яке було задано. І назва вводить в оману, чи не так? Він / вона хоче перетворити рядок байтів у звичайний рядок, а не байтовий масив у рядок. Ця відповідь добре відповідає заголовку запитання.
leetNightshade

5
Для python 3 це має бути еквівалентно bytes([112, 52, 52])- btw bytes - це неправильне ім’я для локальної змінної саме тому, що це вбудований p3
Mr_and_Mrs_D

92

Якщо ви не знаєте кодування, то для читання двійкового введення в рядок в сумісному способі Python 3 та Python 2 використовуйте старовинне кодування 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'))

Оскільки кодування невідоме, очікуйте, що неанглійські символи переведуться на символи cp437(англійські символи не перекладаються, оскільки вони відповідають більшості однобайтових кодувань та UTF-8).

Розшифровка довільного бінарного вводу в UTF-8 небезпечна, тому що ви можете отримати таке:

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

Це ж стосується latin-1, який був популярний (за замовчуванням?) Для Python 2. Дивіться пропущені моменти у макеті кодованої сторінки - саме там Python задихається з ганебним ordinal not in range.

ОНОВЛЕННЯ 20150604 : Ходять чутки, що Python 3 має surrogateescapeстратегію помилок щодо кодування матеріалів у двійкові дані без втрати даних та збоїв, але [binary] -> [str] -> [binary]для перевірки як продуктивності, так і для надійності потрібні тести перетворення .

ОНОВЛЕННЯ 20170116 : Завдяки коментарю Nearoo - також є можливість перерізати всі невідомі байти за допомогою backslashreplaceобробника помилок. Це працює лише для Python 3, тому навіть при цьому вирішенні ви все одно отримаєте невідповідний вихід з різних версій 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'))

Докладніше див. У підтримці Unicode Python .

ОНОВЛЕННЯ 20170119 : Я вирішив застосувати декодируючу косу рису, яка працює як для Python 2, так і для Python 3. Це має бути повільніше, ніж cp437рішення, але воно повинно давати однакові результати для кожної версії 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'))

6
Я справді відчуваю, що Python повинен забезпечити механізм заміни відсутніх символів та продовження.
anatoly techtonik

@techtonik: Це не працюватиме на масиві, як це працювало в python2.
користувач2284570

@ user2284570 ти маєш на увазі список? І навіщо це працювати на масивах? Особливо масиви поплавків ..
anatoly techtonik

Ви також можете просто проігнорувати помилки унікоду b'\x00\x01\xffsd'.decode('utf-8', 'ignore')у python 3.
Антоніс Калу

3
@anatolytechtonik Існує можливість залишити послідовність відходу в рядку і рухатися далі: b'\x80abc'.decode("utf-8", "backslashreplace")призведе до '\\x80abc'. Ця інформація була взята зі сторінки документації унікоду, яка, здається, була оновлена ​​з моменту написання цієї відповіді.
Неару

86

У Python 3 кодування за замовчуванням є "utf-8", так що ви можете безпосередньо використовувати:

b'hello'.decode()

що еквівалентно

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

З іншого боку, в Python 2 кодування за замовчуванням до кодування рядка за замовчуванням. Таким чином, ви повинні використовувати:

b'hello'.decode(encoding)

де encodingви хочете кодування.

Примітка: підтримка аргументів ключових слів була додана в Python 2.7.


41

Я думаю, що ти насправді хочеш цього:

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

Відповідь Аарона була правильною, за винятком того, що вам потрібно знати, яке кодування використовувати. І я вважаю, що Windows використовує "windows-1252". Буде важливо лише, якщо у вашому вмісті є якісь незвичайні (не ASCII) символи, але тоді це змінить значення.

До речі, той факт, що це має значення, є причиною того, що Python перейшов до використання двох різних типів для двійкових і текстових даних: він не може магічно перетворити між ними, тому що не знає кодування, якщо ви цього не скажете! Єдиний спосіб, який ви ВАМ знаєте, - це прочитати документацію Windows (або прочитати її тут).


3
open()функція для текстових потоків або Popen()якщо ви передаєте її universal_newlines=True, магічно вирішуйте кодування символів для вас ( locale.getpreferredencoding(False)у Python 3.3+).
jfs

2
'latin-1'це дослідне кодування з усіма встановленими кодовими точками, тому ви можете використовувати це для ефективного зчитування рядка байтів у той чи інший тип рядка, який підтримує ваш Python (так дослівно на Python 2, в Unicode для Python 3).
трійка

@tripleee: 'latin-1'це хороший спосіб отримати mojibake. Також є чарівна заміна на Windows: це дивно важко дані труб від одного процесу до іншого незміненим , наприклад, dir: \xb6-> \x14(приклад в кінці моєї відповіді)
JFS

32

Встановіть Universal_newlines на True, тобто

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

5
Я використовував цей метод, і він працює. Хоча це просто здогадування про кодування, що базується на вподобаннях користувачів у вашій системі, тому це не так надійно, як деякі інші параметри. Це те, що він робить, посилаючись на docs.python.org/3.4/library/subprocess.html: "Якщо значення true_newlines вірно, [stdin, stdout та stderr] буде відкрито як текстові потоки в режимі універсального рядка з використанням кодування, поверненого локалом" .getpreferredencoding (помилкове). "
twasbrillig

На 3.7 ви можете (і повинні) робити text=Trueзамість universal_newlines=True.
Борис

23

Хоча відповідь @Aaron Maenpaa просто працює, нещодавно користувач запитав :

Чи є простіший спосіб? 'fhand.read (). decode ("ASCII")' [...] Це так довго!

Ви можете використовувати:

command_stdout.decode()

decode()має стандартний аргумент :

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


.decode()що використання 'utf-8'може не вдатися (вихід команди може використовувати інше кодування символів або навіть повертати нерозбірливу послідовність байт). Хоча якщо вхід є ascii (підмножина utf-8), то він .decode()працює.
jfs

22

Щоб інтерпретувати послідовність байтів як текст, ви повинні знати відповідне кодування символів:

unicode_text = bytestring.decode(character_encoding)

Приклад:

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

lsкоманда може отримати результат, який не можна інтерпретувати як текст. Імена файлів на Unix можуть бути будь-якою послідовністю байтів, окрім косої риси b'/'та нуля b'\0':

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

Спроба розшифрувати такий байт-суп за допомогою кодування utf-8 підвищує UnicodeDecodeError.

Це може бути і гірше. Розшифровка може вийти з ладу і створити mojibake, якщо ви використовуєте неправильне несумісне кодування:

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

Дані пошкоджені, але програма не знає, що сталася помилка.

Взагалі те, що кодування символів використовувати, не вбудовується в саму послідовність байтів. Ви повинні передавати цю інформацію поза межами діапазону. Деякі результати є більш імовірними, ніж інші, тому chardetіснує модуль, який може вгадати кодування символів. Один сценарій Python може використовувати різні кодування символів у різних місцях.


lsвихід може бути перетворений на рядок Python за допомогою os.fsdecode() функції, яка досягає успіху навіть для нерозбірливих імен файлів (він використовує sys.getfilesystemencoding()і surrogateescapeобробник помилок на Unix):

import os
import subprocess

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

Щоб отримати оригінальні байти, ви можете використовувати os.fsencode().

Якщо ви передаєте universal_newlines=Trueпараметр, який subprocessвикористовується locale.getpreferredencoding(False)для декодування байтів, наприклад, це може бути cp1252в Windows.

Для декодування потоку байтів на ходу, io.TextIOWrapper() може бути використаний: приклад .

Різні команди можуть використовувати різні кодування символів для їх виведення, наприклад, dirвнутрішня команда ( cmd) може використовувати cp437. Щоб розшифрувати його вихід, ви можете передати кодування чітко (Python 3.6+):

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

Імена файлів можуть відрізнятися від os.listdir()(для яких використовується Windows Unicode API), наприклад, '\xb6'можуть бути замінені '\x14'—Python's cp437 кодек-картами b'\x14'для управління символом U + 0014 замість U + 00B6 (¶). Щоб підтримати назви файлів з довільними символами Unicode, див. Висновок Decode PowerShell, можливо, що містить символи Unicode, що не містять ASCII, у рядок Python


16

Оскільки це питання на самому справі просять про subprocessвихід, у вас є більш прямий підхід , оскільки наявний Popenприймає кодування ключове слово (в 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

Загальна відповідь для інших користувачів - це декодування байтів до тексту:

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

Без аргументу sys.getdefaultencoding()буде використано. Якщо ваших даних немає sys.getdefaultencoding(), то в decodeвиклику потрібно чітко вказати кодування :

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

3
Або з Python 3.7 ви можете перейти text=Trueдо декодування stdin, stdout та stderr, використовуючи задане кодування (якщо встановлено) або за замовчуванням системи в іншому випадку. Popen(['ls', '-l'], stdout=PIPE, text=True).
Борис

Декодування lsвиводу за допомогою utf-8кодування може не вдатися (див. Приклад у моїй відповіді від 2016 року ).
jfs

1
@Boris: якщо encodingпараметр заданий, то textпараметр ігнорується.
jfs

11

Якщо вам потрібно зробити наступне, спробуйте decode():

AttributeError: об’єкт "str" ​​не має атрибута "декодування"

Ви також можете вказати тип кодування прямо в складі:

>>> my_byte_str
b'Hello World'

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

6

Під час роботи з даними із систем Windows (із \r\nзакінченнями рядків) моя відповідь така

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

Чому? Спробуйте це з багаторядковим Input.txt:

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

Усі ваші закінчення рядків будуть подвоєні (до \r\r\n), що призведе до зайвих порожніх рядків. Функції читання тексту Python зазвичай нормалізують закінчення рядків так, що використовуються лише рядки \n. Якщо ви отримуєте двійкові дані з системи Windows, Python не має шансів це зробити. Таким чином,

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

буде копіювати ваш оригінальний файл.


Я .replace("\r\n", "\n")так довго шукав доповнення. Це відповідь, якщо ви хочете правильно візуалізувати HTML.
mhlavacka

5

Я зробив функцію очищення списку

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
На насправді ви можете прикувати все .strip, .replace, .encode, і т.д. виклики в одному списку розуміння і тільки ітерація за списком раз замість Перебір нього п'ять разів.
Тейлор Едмістон

1
@TaylorEdmiston Можливо, це економить на розподілі, але кількість операцій залишиться такою ж.
JulienD

5

Для Python 3, це набагато безпечніше і Pythonic підхід для перетворення 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')

Вихід:

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, перевірка типу зовсім не є пітонічною. 2) Функція, яку ви написали, має ім'я " byte_to_str", що означає, що вона поверне str, але вона друкує лише перетворене значення, і вона виводить повідомлення про помилку, якщо вона не працює (але не викликає виняток). Цей підхід є також непітонічним і обтяжує bytes.decodeрішення, яке ви запропонували.
cosmicFluke

3

Від sys - Специфічні для системи параметри та функції :

Для запису чи читання двійкових даних із / до стандартних потоків використовуйте базовий двійковий буфер. Наприклад, для написання байтів у stdout використовуйте sys.stdout.buffer.write(b'abc').


3
Труба до підпроцесу - це вже двійковий буфер. Ваша відповідь не відповідає на те, як отримати значення рядка з отриманого 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))

1
Хоча цей код може відповісти на питання, надаючи додатковий контекст щодо того, як та / або чому він вирішує проблему, покращить довгострокове значення відповіді. Пам’ятайте, що ви відповідаєте на запитання читачів у майбутньому, а не лише про людину, яка зараз запитує! Будь ласка , змініть свій відповідь , щоб додати пояснення, і дати вказівку про те , що застосовувати обмеження і допущення. Також не завадить згадати, чому ця відповідь доречніша за інші.
Dev-iL

Пояснення було б в порядку.
Пітер Мортенсен

1

У вашому конкретному випадку "запустіть команду оболонки та отримайте її вихід у вигляді тексту замість байтів", на Python 3.7 слід використовувати subprocess.runта переходити text=True(а також capture_output=Trueдля отримання виводу)

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

textколись називались universal_newlinesі був змінений (ну, псевдонім) у Python 3.7. Якщо ви хочете підтримувати версії Python до 3.7, перейдіть universal_newlines=Trueзамістьtext=True


0

Якщо ви хочете перетворити будь-які байти, а не лише рядки, перетворені в байти:

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

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

Однак це не дуже ефективно. Це перетворить зображення в 2 Мб в 9 Мб.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.