Конвертировать байты в строку

2311

Я использую этот код для получения стандартного вывода из внешней программы:

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

Ответы:

3678

Вам нужно декодировать объект bytes, чтобы получить строку:

>>> 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?
'15
12
Может быть, это поможет кому-то еще: иногда вы используете байтовый массив для связи по TCP. Если вы хотите преобразовать байтовый массив в строку, обрезая завершающие символы '\ x00', следующего ответа недостаточно. Затем используйте b'example \ x00 \ x00'.decode ('utf-8'). Strip ('\ x00').
Wookie88
2
Я заполнил сообщение об ошибке при его документировании на bugs.python.org/issue17860 - не стесняйтесь предлагать патч. Если трудно внести свой вклад - комментарии, как улучшить, приветствуются.
анатолий техтоник
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
215

Вам необходимо декодировать строку байтов и превратить ее в символьную строку (Unicode).

На питоне 2

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

или

unicode('hello', encoding)

На питоне 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'
Sisso
источник
6
Спасибо, ваш метод сработал для меня, когда никто другой не сработал. У меня был некодированный байтовый массив, который мне нужен был превращен в строку. Пытался найти способ перекодировать его, чтобы я мог декодировать его в строку. Этот метод работает отлично!
leetNightshade
5
@leetNightshade: все же это ужасно неэффективно. Если у вас есть байтовый массив, вам нужно только декодировать.
Мартин Питерс
12
@Martijn Pieters Я только что провел простой тест с этими другими ответами, запустив несколько десятков запусков stackoverflow.com/a/3646405/353094 И вышеупомянутое решение было на самом деле намного быстрее каждый раз. Для 10000 запусков в Python 2.7.7 это занимает 8 мс, в то время как остальные в 12 мс и 18 мс. Конечно, могут быть некоторые различия в зависимости от ввода, версии Python и т. Д. Мне кажется, это не слишком медленно.
leetNightshade
5
@Martijn Pieters Да. Таким образом, с этой точки зрения, это не лучший ответ для основной части вопроса, который был задан. И название вводит в заблуждение, не так ли? Он / она хочет преобразовать байтовую строку в обычную строку, а не байтовый массив в строку. Этот ответ работает хорошо для названия вопроса, который был задан.
leetNightshade
5
Для Python 3 это должно быть эквивалентно bytes([112, 52, 52])- кстати, байты - это плохое имя для локальной переменной именно потому, что она
встроена
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. Смотрите недостающие точки в Layout Codepage - именно там 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 должен предоставить механизм для замены отсутствующих символов и продолжения.
анатолий техтоник
@techtonik: Это не будет работать с массивом, как это работает в python2.
user2284570 20.10.15
@ user2284570 ты имеешь в виду список? И почему это должно работать на массивах? Особенно массивы поплавков ..
анатолий техтоник
Вы также можете просто игнорировать ошибки Unicode b'\x00\x01\xffsd'.decode('utf-8', 'ignore')в Python 3.
Антонис Калу
3
@anatolytechtonik Существует возможность оставить escape-последовательность в строке и двигаться дальше: b'\x80abc'.decode("utf-8", "backslashreplace")приведет к '\\x80abc'. Эта информация была взята со страницы документации Unicode, которая, кажется, была обновлена ​​с момента написания этого ответа.
Nearoo
86

В Python 3 кодировкой по умолчанию является "utf-8", так что вы можете напрямую использовать:

b'hello'.decode()

что эквивалентно

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

С другой стороны, в Python 2 кодировка по умолчанию соответствует строковому кодированию по умолчанию. Таким образом, вы должны использовать:

b'hello'.decode(encoding)

где encodingкодировка, которую вы хотите.

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

lmiguelvargasf
источник
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 (или прочитать ее здесь).

mcherm
источник
3
open()функция для текстовых потоков или, Popen()если вы ее передадите universal_newlines=True, волшебным образом решите для вас кодировку символов ( locale.getpreferredencoding(False)в Python 3.3+).
JFS
2
'latin-1'это дословное кодирование со всеми установленными кодовыми точками, так что вы можете использовать его для эффективного чтения байтовой строки в любой тип строки, поддерживаемый вашим Python (так дословно на Python 2, в Unicode для Python 3).
tripleee
@tripleee: 'latin-1'хороший способ получить моджибаке. Также в Windows есть волшебная замена: на удивление трудно передавать данные из одного процесса в другой без изменений, например dir: \xb6-> \x14(пример в конце моего ответа)
jfs
32

Установите для universal_newlines значение True, т.е.

command_stdout = Popen(['ls', '-l'], stdout=PIPE, universal_newlines=True).communicate()[0]
ContextSwitch
источник
5
Я использовал этот метод, и он работает. Хотя, это всего лишь догадка о кодировке, основанной на пользовательских настройках вашей системы, поэтому она не так надежна, как некоторые другие параметры. Это то, что он делает, ссылаясь на docs.python.org/3.4/library/subprocess.html: «Если universal_newlines равен True, [stdin, stdout и stderr] будут открываться как текстовые потоки в режиме универсальных строк, используя кодировку, возвращаемую локалью .getpreferredencoding (False) «.
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')

Serv-вкл
источник
.decode()это 'utf-8'может привести к сбою (выходные данные команды могут использовать другую кодировку символов или даже возвращать некодируемую последовательность байтов). Хотя, если ввод ascii (подмножество utf-8), то .decode()работает.
JFS
23

Чтобы интерпретировать последовательность байтов как текст, вы должны знать соответствующую кодировку символов:

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() функции, которая успешно выполняется даже для не кодируемых имен файлов ( в Unix используется sys.getfilesystemencoding()и surrogateescapeобработчик ошибок):

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 cp437 b'\x14'для управления символом U + 0014 вместо U + 00B6 (¶). Чтобы поддержать имена файлов с произвольными символами Unicode, см. Декодирование вывода PowerShell, возможно, содержащее символы не-ASCII Unicode в строку Python

JFS
источник
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é'
Wim
источник
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' нет атрибута 'decode'

Вы также можете указать тип кодировки прямо в приведении:

>>> my_byte_str
b'Hello World'

>>> str(my_byte_str, 'utf-8')
'Hello World'
Broper
источник
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.
Мхлавачка
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
eafloresf
источник
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
Inconnu
источник
5
1) Как сказал @bodangly, проверка типов совсем не питонна. 2) Функция, которую вы написали, называется " byte_to_str", что означает, что она возвратит str, но она только печатает преобразованное значение и выводит сообщение об ошибке, если она не срабатывает (но не вызывает исключение). Этот подход также неуместный и запутывает bytes.decodeрешение, которое вы предоставили.
cosmicFluke
3

От sys - Системные параметры и функции :

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

Чжичан ю
источник
3
Канал к подпроцессу уже является двоичным буфером. Ваш ответ не описывает, как получить строковое значение из полученного bytesзначения.
Мартин Питерс
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 МБ.

HCLivess
источник
-1

попробуй это

bytes.fromhex('c3a9').decode('utf-8') 
Виктор Чой
источник