Зачем мне нужно 'b' для кодирования строки с Base64?

259

Следуя этому примеру с Python , я кодирую строку как Base64 с помощью:

>>> import base64
>>> encoded = base64.b64encode(b'data to be encoded')
>>> encoded
b'ZGF0YSB0byBiZSBlbmNvZGVk'

Но если я пропущу ведущие b:

>>> encoded = base64.b64encode('data to be encoded')

Я получаю следующую ошибку:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python32\lib\base64.py", line 56, in b64encode
   raise TypeError("expected bytes, not %s" % s.__class__.__name__)
   TypeError: expected bytes, not str

Почему это?

dublintech
источник
38
На самом деле все вопросы, которые возвращают "TypeError: ожидаемые байты, а не str", имеют одинаковый ответ.
Леннарт Регебро

Ответы:

275

кодирование base64 занимает 8-битовые двоичные данные байт и кодируют его использует только символы A-Z, a-z, 0-9, +, /* так что он может быть передан по каналам , которые не сохраняют все 8 бит данных, такие как электронная почта.

Следовательно, он хочет строку из 8-битных байтов. Вы создаете те в Python 3 с b''синтаксисом.

Если вы удалите b, он станет строкой. Строка - это последовательность символов Юникода. Base64 понятия не имеет, что делать с данными Unicode, это не 8-битный код. На самом деле это не какие-то биты. :-)

В вашем втором примере:

>>> encoded = base64.b64encode('data to be encoded')

Все символы аккуратно вписываются в набор символов ASCII, и поэтому кодирование base64 на самом деле немного бессмысленно. Вы можете преобразовать его в ASCII вместо

>>> encoded = 'data to be encoded'.encode('ascii')

Или проще:

>>> encoded = b'data to be encoded'

Что было бы то же самое в этом случае.


* Большинство base64-ароматов могут также включать =в конце как отступ. Кроме того, некоторые варианты base64 могут использовать символы, отличные от +и /. Смотрите сводную таблицу вариантов в Википедии для обзора.

Леннарт Регебро
источник
174

Короткий ответ

Вам нужно вставить bytes-likeобъект ( bytes, bytearrayи т. Д.) В base64.b64encode()метод. Вот два способа:

>>> data = base64.b64encode(b'data to be encoded')
>>> print(data)
b'ZGF0YSB0byBiZSBlbmNvZGVk'

Или с переменной:

>>> string = 'data to be encoded'
>>> data = base64.b64encode(string.encode())
>>> print(data)
b'ZGF0YSB0byBiZSBlbmNvZGVk'

Зачем?

В Python 3 strобъекты не являются символьными массивами в стиле C (поэтому они не являются байтовыми массивами), а скорее являются структурами данных, которые не имеют встроенной кодировки. Вы можете закодировать эту строку (или интерпретировать ее) различными способами. Наиболее распространенным (и по умолчанию в Python 3) является utf-8, тем более что он обратно совместим с ASCII (хотя, как и наиболее широко используемые кодировки). Вот что происходит, когда вы берете a stringи вызываете .encode()метод для него: Python интерпретирует строку в utf-8 (кодировка по умолчанию) и предоставляет вам массив байтов, которому она соответствует.

Кодировка Base-64 в Python 3

Первоначально заголовок вопроса задавался о кодировке Base-64. Продолжайте читать для Base-64 вещи.

base64кодирование берет 6-битные двоичные фрагменты и кодирует их, используя символы AZ, az, 0-9, '+', '/' и '=' (некоторые кодировки используют разные символы вместо '+' и '/') , Это кодировка символов, основанная на математической конструкции системы счисления radix-64 или base-64, но они очень разные. Base-64 в математике - это система счисления, такая как двоичная или десятичная, и вы делаете это изменение основ на всем числе, или (если основание, из которого вы производите преобразование, является степенью 2 меньше 64) в кусках справа налево осталось.

В base64кодировке перевод выполняется слева направо; эти первые 64 символа - вот почему это называется base64 кодированием . 65-й ​​символ «=» используется для заполнения, поскольку кодирование извлекает 6-битные порции, но данные, которые обычно предназначены для кодирования, представляют собой 8-битные байты, поэтому иногда в последнем порции есть только два или 4 бита.

Пример:

>>> data = b'test'
>>> for byte in data:
...     print(format(byte, '08b'), end=" ")
...
01110100 01100101 01110011 01110100
>>>

Если вы интерпретируете эти двоичные данные как одно целое число, то вы должны преобразовать их в base-10 и base-64 ( таблица для base-64 ):

base-2:  01 110100 011001 010111 001101 110100 (base-64 grouping shown)
base-10:                            1952805748
base-64:  B      0      Z      X      N      0

base64 кодирование , однако, перегруппирует эти данные таким образом:

base-2:  011101  000110  010101 110011 011101 00(0000) <- pad w/zeros to make a clean 6-bit chunk
base-10:     29       6      21     51     29      0
base-64:      d       G       V      z      d      A

Итак, «B0ZXN0» - это версия нашего бинарного кода с точки зрения математики, основанная на 64. Однако base64 кодирование должно выполнять кодирование в противоположном направлении (поэтому необработанные данные преобразуются в «dGVzdA»), а также имеет правило, чтобы сообщать другим приложениям, сколько места осталось в конце. Это делается путем заполнения конца символами '='. Таким образом, base64кодировка этих данных - «dGVzdA ==», с двумя символами «=» для обозначения двух пар битов необходимо будет удалить с конца, когда эти данные будут декодированы, чтобы они соответствовали исходным данным.

Давайте проверим это, чтобы убедиться, что я нечестен:

>>> encoded = base64.b64encode(data)
>>> print(encoded)
b'dGVzdA=='

Зачем использовать base64кодировку?

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

>>> data = b'\x04\x6d\x73\x67\x08\x08\x08\x20\x20\x20'
>>> print(data.decode())

>>> print(data)
b'\x04msg\x08\x08\x08   '
>>>

Я поставил две проблемы:

  1. Если я попытаюсь отправить это электронное письмо в Unix, оно будет отправлено, как только \x04будет прочитан символ, потому что это ASCII для END-OF-TRANSMISSION(Ctrl-D), поэтому оставшиеся данные будут исключены из передачи.
  2. Кроме того, хотя Python достаточно умен, чтобы избежать всех моих злых управляющих символов, когда я печатаю данные напрямую, когда эта строка декодируется как ASCII, вы можете видеть, что «msg» не существует. Это потому, что я использовал три BACKSPACEсимвола и три SPACEсимвола, чтобы стереть «MSG». Таким образом, даже если бы у меня не было EOFперсонажа, конечный пользователь не смог бы перевести текст с экрана на реальные необработанные данные.

Это просто демонстрация, чтобы показать вам, как трудно просто отправлять необработанные данные. Кодирование данных в формате base64 дает вам точно такие же данные, но в формате, который обеспечивает их безопасную отправку через электронные носители, такие как электронная почта.

Грег Шмит
источник
6
base64.b64encode(s.encode()).decode()не очень питоничен, когда все, что вам нужно, это преобразование строки в строку. base64.encode(s)должно быть достаточно хотя бы в python3. Спасибо за очень хорошее объяснение о строках и байтах в python
MortenB
2
@MortenB Да, это странно, но с другой стороны, очень ясно, что происходит, пока инженер осознает разницу между массивами байтов и строк, поскольку между ними нет ни одного отображения (кодирования), как в других языках. предполагать.
Грег Шмит
3
@MortenB Кстати, base64.encode(s)не будет работать в Python3; Вы говорите, что нечто подобное должно быть доступно? Я думаю, причина, по которой это может сбить с толку, заключается в том, что в зависимости от кодировки и содержимого строки sможет не иметь 1 уникальное представление в виде массива байтов.
Грег Шмит
Шмитт: это был просто пример того, как все должно быть просто. самые распространенные случаи использования должны быть такими.
MortenB
1
@MortenB, но b64 предназначен не только для текста, любой двоичный контент может быть закодирован b64 (аудио, изображения и т. Д.). На мой взгляд, заставить его работать так, как вы предлагаете, еще больше скрывает разницу между текстом и байтовым массивом, что затрудняет отладку. Это просто перемещает трудности в другое место.
Майкл Экока
32

Если данные для кодирования содержат «экзотические» символы, я думаю, что вы должны кодировать в «UTF-8»

encoded = base64.b64encode (bytes('data to be encoded', "utf-8"))
Alecz
источник
24

Если строка является Unicode, самый простой способ:

import base64                                                        

a = base64.b64encode(bytes(u'complex string: ñáéíóúÑ', "utf-8"))

# a: b'Y29tcGxleCBzdHJpbmc6IMOxw6HDqcOtw7PDusOR'

b = base64.b64decode(a).decode("utf-8", "ignore")                    

print(b)
# b :complex string: ñáéíóúÑ
alfredocambera
источник
На самом деле это не самый простой способ, но один из самых понятных, когда важно, какое кодирование используется для передачи строки, что является частью «протокола» передачи данных через base64.
xuiqzy
12

Есть все, что вам нужно:

expected bytes, not str

Ведущий bделает вашу строку двоичной.

Какую версию Python вы используете? 2.x или 3.x?

Изменить: см. Http://docs.python.org/release/3.0.1/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit для подробного описания строк в Python 3.x


источник
Спасибо, я использую, 3.x. Почему Python хочет преобразовать его явно в двоичный файл. То же самое в Ruby будет ... требует> "base64" и затем> Base64.encode64 ("данные для кодирования")
dublintech
2
@dublintech Потому что (Unicode) текст отличается от необработанных данных. Если вы хотите кодировать текстовую строку в Base64, сначала вам нужно определить кодировку символов (например, UTF-8), а затем у вас есть байты, а не символы, которые вы можете кодировать в текстовой безопасной для ascii форме.
Фортран
2
Это не отвечает на вопрос. Он знает, что это работает с байтовым объектом, но не строковым объектом. Вопрос в том, почему .
Леннарт Регебро
@fortran По умолчанию строковое кодирование Python3 - UTF, не знаю, почему оно должно быть задано явно.
xmedeko
0

Это просто означает, что вы воспринимаете ввод как байты или байты, а не как строки.

Atul6.Singh
источник