Зачем мне нужно '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

Почему это?


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

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: ñáéíóúÑ

На самом деле это не самый простой способ, но один из самых понятных, когда важно, какое кодирование используется для передачи строки, что является частью «протокола» передачи данных через 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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.