Вопрос: Преобразовать байты в строку?


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

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


1174


источник


Ответы:


Вы должны декодировать объект 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'

1924



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

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

113



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

b'hello'.decode(encoding)

или

str(b'hello', encoding)

97



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

57



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

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

Ответ Аарона был правильным, за исключением того, что вам нужно знать кодировку WHICH для использования. И я считаю, что Windows использует «windows-1252». Это будет иметь значение только в том случае, если у вас есть некоторые необычные (не ascii) символы в вашем контенте, но тогда это будет иметь значение.

Кстати, тот факт, что он имеет значение, является причиной того, что Python перешел на использование двух разных типов для двоичных и текстовых данных: он не может преобразовать магически между ними, потому что он не знает кодировки, если вы не скажете об этом! Единственный способ, которым вы должны знать, - это прочитать документацию Windows (или прочитать ее здесь).


32



In Python 3, the default encoding is "utf-8", so you can use directly:

b'hello'.decode()

which is equivalent to

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

On the other hand, in Python 2, encoding defaults to the default string encoding. Thus, you should use:

b'hello'.decode(encoding)

where encoding is the encoding you want.

Note: support for keyword arguments was added in Python 2.7.


28



Set universal_newlines to True, i.e.

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

26



While @Aaron Maenpaa's answer just works, a user recently asked

Is there any more simply way? 'fhand.read().decode("ASCII")' [...] It's so long!

You can use

command_stdout.decode()

decode() has a standard argument

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


14



To interpret a byte sequence as a text, you have to know the corresponding character encoding:

unicode_text = bytestring.decode(character_encoding)

Example:

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

ls command may produce output that can't be interpreted as text. File names on Unix may be any sequence of bytes except slash b'/' and zero b'\0':

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

Trying to decode such byte soup using utf-8 encoding raises UnicodeDecodeError.

It can be worse. The decoding may fail silently and produce mojibake if you use a wrong incompatible encoding:

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

The data is corrupted but your program remains unaware that a failure has occurred.

In general, what character encoding to use is not embedded in the byte sequence itself. You have to communicate this info out-of-band. Some outcomes are more likely than others and therefore chardet module exists that can guess the character encoding. A single Python script may use multiple character encodings in different places.


ls output can be converted to a Python string using os.fsdecode() function that succeeds even for undecodable filenames (it uses sys.getfilesystemencoding() and surrogateescape error handler on Unix):

import os
import subprocess

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

To get the original bytes, you could use os.fsencode().

If you pass universal_newlines=True parameter then subprocess uses locale.getpreferredencoding(False) to decode bytes e.g., it can be cp1252 on Windows.

To decode the byte stream on-the-fly, io.TextIOWrapper() could be used: example.

Different commands may use different character encodings for their output e.g., dir internal command (cmd) may use cp437. To decode its output, you could pass the encoding explicitly (Python 3.6+):

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

The filenames may differ from os.listdir() (which uses Windows Unicode API) e.g., '\xb6' can be substituted with '\x14'—Python's cp437 codec maps b'\x14' to control character U+0014 instead of U+00B6 (¶). To support filenames with arbitrary Unicode characters, see Decode poweshell output possibly containing non-ascii unicode characters into a python string


9



If you should get the following by trying decode():

AttributeError: 'str' object has no attribute 'decode'

You can also specify the encoding type straight in a cast:

>>> my_byte_str
b'Hello World'

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

5