Чому мені потрібно "b", щоб кодувати рядок з Base64?


258

Слідуючи цьому прикладу 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

Чому це?


37
Насправді всі питання, що повертають "TypeError: очікувані байти, а не str", мають однакову відповідь.
Леннарт Регебро

Відповіді:


273

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

Отже, він бажає рядок з 8-бітових байтів. Ви створюєте ті в Python 3 за допомогою b''синтаксису.

Якщо ви вилучите значок b, він стає рядком. Рядок - це послідовність символів Unicode. 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

Це b просто означає, що ви приймаєте дані як масив байтів або байтів, а не як рядок.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.