Преобразовать байты в строку? - PullRequest
1709 голосов
/ 03 марта 2009

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

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

Метод connect () возвращает массив байтов:

>>> 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 все было в порядке.

Ответы [ 17 ]

2800 голосов
/ 03 марта 2009

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

>>> 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'
151 голосов
/ 22 августа 2012

Я думаю, что это легко:

bytes_data = [112, 52, 52]
"".join(map(chr, bytes_data))
>> p44
134 голосов
/ 03 марта 2009

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

b'hello'.decode(encoding)

или на Python 3

str(b'hello', encoding)
69 голосов
/ 17 декабря 2014

Если вы не знаете кодировку, то для чтения двоичного ввода в строку в 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'))

Подробнее см. https://docs.python.org/3/howto/unicode.html#python-s-unicode-support.

ОБНОВЛЕНИЕ 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'))
64 голосов
/ 29 июня 2016

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

b'hello'.decode()

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

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

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

b'hello'.decode(encoding)

где encoding - нужная кодировка.

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

36 голосов
/ 18 июля 2011

Я думаю, что вы действительно хотите это:

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

29 голосов
/ 21 января 2014

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

command_stdout = Popen(['ls', '-l'], stdout=PIPE, universal_newlines=True).communicate()[0]
17 голосов
/ 13 ноября 2015

Пока @ ответ Аарона Маенпаа просто работает, пользователь недавно спросил :

Есть ли более простой способ? 'fhand.read (). decode ("ASCII")' [...] Это так долго!

Вы можете использовать:

command_stdout.decode()

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

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

13 голосов
/ 16 ноября 2016

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

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() функция, которая преуспевает даже для uncodable имена файлов (используется Обработчик ошибок 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, см. Декодирование вывода Poweshell, возможно, содержащего символы Unicode, отличные от ascii, в строку Python

13 голосов
/ 31 мая 2018

Поскольку этот вопрос фактически задает вывод 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é'
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...