Dlaczego potrzebuję „b”, aby zakodować ciąg za pomocą Base64?

258

Zgodnie z tym przykładem w języku Python koduję ciąg znaków jako Base64 za pomocą:

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

Ale jeśli pominę wiodące b:

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

Otrzymuję następujący błąd:

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

Dlaczego to?

dublintech
źródło
37
Właściwie wszystkie pytania zwracające „TypeError: oczekiwane bajty, a nie str” mają tę samą odpowiedź.
Lennart Regebro

Odpowiedzi:

273

base64 kodowania trwa 8-bitowy binarny bajt danych i koduje używa tylko znaków A-Z, a-z, 0-9, +, /* więc może być transmitowany przez kanały, które nie zachowują wszystkie 8 bitów danych, takich jak e-mail.

Dlatego chce ciąg 8-bitowych bajtów. Tworzysz je w Pythonie 3 za pomocą b''składni.

Jeśli usuniesz b, staje się ciąg. Ciąg jest sekwencją znaków Unicode. base64 nie ma pojęcia, co zrobić z danymi Unicode, nie jest to 8-bit. Tak naprawdę to nie są żadne bity. :-)

W twoim drugim przykładzie:

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

Wszystkie znaki dobrze pasują do zestawu znaków ASCII, dlatego kodowanie base64 jest w rzeczywistości trochę bezcelowe. Zamiast tego możesz przekonwertować go na ascii za pomocą

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

Lub prościej:

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

Co byłoby identyczne w tym przypadku.


* Większość smaków base64 może również zawierać =na końcu jako dopełnienie. Ponadto niektóre warianty base64 mogą używać znaków innych niż +i /. Przegląd znajduje się w tabeli podsumowującej warianty na Wikipedii.

Lennart Regebro
źródło
174

Krótka odpowiedź

Trzeba wcisnąć bytes-likeobiekt ( bytes, bytearray, etc) do base64.b64encode()metody. Oto dwa sposoby:

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

Lub ze zmienną:

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

Czemu?

W Pythonie 3, strobiekty nie są tablice znaków w stylu C (więc są one nie bajt tablic), ale raczej są struktury danych, które nie mają żadnego kodowania wrodzoną. Możesz zakodować ten ciąg (lub zinterpretować go) na różne sposoby. Najczęstszym (i domyślnym w Pythonie 3) jest utf-8, zwłaszcza, że ​​jest wstecznie kompatybilny z ASCII (chociaż, jak to są najczęściej stosowane kodowania). Tak dzieje się, gdy weźmiesz stringi wywołasz na nim .encode()metodę: Python interpretuje ciąg znaków w utf-8 (kodowanie domyślne) i zapewnia tablicę bajtów, z którą odpowiada.

Kodowanie Base-64 w Pythonie 3

Pierwotnie pytanie brzmiało na temat kodowania Base-64. Czytaj dalej na temat Base-64.

base64kodowanie zajmuje 6-bitowe binarne fragmenty i koduje je przy użyciu znaków AZ, az, 0-9, „+”, „/” i „=” (niektóre kodowania używają różnych znaków zamiast „+” i „/”) . Jest to kodowanie znaków oparte na matematycznej konstrukcji systemu liczbowego Radix-64 lub base-64, ale są one bardzo różne. Base-64 w matematyce to system liczbowy, taki jak binarny lub dziesiętny, i dokonuje się tej zmiany podstawki na całej liczbie lub (jeśli podstawa, z której przeliczamy, jest potęgą 2 mniejszą niż 64) w częściach od prawej do lewo.

W base64kodowaniu tłumaczenie odbywa się od lewej do prawej; te pierwsze 64 znaki nazywają to base64 kodowaniem . Symbol 65. „=” jest używany do wypełniania, ponieważ kodowanie ściąga 6-bitowe porcje, ale dane, które zwykle mają być kodowane, to 8-bitowe bajty, więc czasami w ostatnim fragmencie są tylko dwa lub 4 bity.

Przykład:

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

Jeśli interpretujesz te dane binarne jako pojedynczą liczbę całkowitą, to w ten sposób przekonwertujesz je na base-10 i base-64 ( tabela dla 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 kodowanie spowoduje jednak ponowne grupowanie tych danych:

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

Tak więc „B0ZXN0” jest matematyczną wersją naszej bazy binarnej w wersji 64. Jednak base64 kodowanie musi zrobić kodowanie w kierunku przeciwnym (tak surowe dane przekształca się w „dGVzdA”), a także ma reguły do powiedzenia inne aplikacje ile miejsca zostało przerwane na końcu. Odbywa się to poprzez wypełnienie końca symbolami „=”. Tak więc base64kodowanie tych danych to „dGVzdA ==”, a dwa symbole „=” oznaczające dwie pary bitów będą musiały zostać usunięte od końca, gdy dane te zostaną zdekodowane, aby dopasować je do pierwotnych danych.

Przetestujmy to, aby sprawdzić, czy jestem nieuczciwy:

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

Dlaczego warto korzystać z base64kodowania?

Powiedzmy, że muszę przesłać komuś jakieś dane pocztą e-mail, takie jak te dane:

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

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

Zasadziłem dwa problemy:

  1. Gdybym próbował wysłać tę wiadomość e-mail w systemie Unix, wiadomość e-mail zostałaby wysłana natychmiast po \x04odczytaniu znaku, ponieważ jest to ASCII dla END-OF-TRANSMISSION(Ctrl-D), więc pozostałe dane zostałyby pominięte w transmisji.
  2. Ponadto, podczas gdy Python jest wystarczająco inteligentny, aby uciec przed wszystkimi moimi złymi znakami kontroli, gdy drukuję dane bezpośrednio, gdy ten ciąg jest dekodowany jako ASCII, widać, że nie ma „msg”. Jest tak, ponieważ użyłem trzech BACKSPACEznaków i trzech SPACEznaków, aby usunąć „msg”. Dlatego nawet gdybym nie miał EOFtam znaku, użytkownik końcowy nie byłby w stanie przetłumaczyć tekstu z ekranu na prawdziwe, surowe dane.

To tylko wersja demonstracyjna pokazująca, jak trudno może być po prostu wysłać surowe dane. Kodowanie danych w formacie base64 daje dokładnie te same dane, ale w formacie zapewniającym bezpieczeństwo przesyłania za pośrednictwem mediów elektronicznych, takich jak poczta elektroniczna.

Greg Schmit
źródło
6
base64.b64encode(s.encode()).decode()nie jest bardzo pytoniczny, kiedy wszystko, czego potrzebujesz, to konwersja łańcucha na łańcuch. base64.encode(s)powinno wystarczyć przynajmniej w python3. Dzięki za bardzo dobre wyjaśnienie ciągów i bajtów w pythonie
MortenB
2
@MortenB Tak, to dziwne, ale z drugiej strony jest bardzo jasne, co się dzieje, o ile inżynier jest świadomy różnicy między tablicami bajtów i ciągów, ponieważ nie ma jednego mapowania (kodowania) między nimi, jak w innych językach założyć.
Greg Schmit
3
@MortenB Nawiasem mówiąc, base64.encode(s)nie działałby w Python3; czy mówisz, że coś takiego powinno być dostępne? Myślę, że powodem tego może być zamieszanie, ponieważ w zależności od kodowania i zawartości łańcucha smoże nie mieć 1 unikatowej reprezentacji jako tablicy bajtów.
Greg Schmit
Schmitt: był to tylko przykład tego, jak proste powinno być. takie powinny być najczęstsze przypadki użycia.
MortenB
1
@MortenB, ale b64 nie jest przeznaczony tylko do tekstu, każda treść binarna może być zakodowana w formacie b64 (audio, obrazy itp.). Moim zdaniem sprawienie, by działało tak, jak proponujesz, jeszcze bardziej ukrywa różnicę między tekstem a tablicą bajtów, co utrudnia debugowanie. Po prostu przenosi trudność gdzie indziej.
Michael Ekoka
32

Jeśli dane, które mają być zakodowane, zawierają znaki „egzotyczne”, myślę, że musisz zakodować w „UTF-8”

encoded = base64.b64encode (bytes('data to be encoded', "utf-8"))
Alecz
źródło
24

Jeśli ciąg jest Unicode, najprostszym sposobem jest:

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
źródło
Naprawdę nie jest to najłatwiejszy sposób, ale jeden z najbardziej przejrzystych sposobów, kiedy ważne jest, które kodowanie jest używane do przesyłania ciągu znaków, który jest częścią „protokołu” transmisji danych przez base64.
xuiqzy
12

Jest wszystko, czego potrzebujesz:

expected bytes, not str

Prowadzenie bsprawia, że ​​Twój ciąg binarny.

Jakiej wersji Pythona używasz? 2.x czy 3.x?

Edycja: Zobacz http://docs.python.org/release/3.0.1/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit, aby uzyskać szczegółowe informacje na temat ciągów znaków w Pythonie 3.x


źródło
Dzięki korzystam, 3.x. Dlaczego Python chce przekonwertować go binarnie. To samo w Ruby będzie ... wymaga> „base64”, a następnie> Base64.encode64 („dane do zakodowania”)
dublintech
2
@dublintech Ponieważ tekst (Unicode) różni się od surowych danych. Jeśli chcesz zakodować ciąg tekstowy w Base64, najpierw musisz określić kodowanie znaków (jak UTF-8), a następnie masz bajty zamiast znaków, które możesz zakodować w postaci tekstowej bezpiecznej dla ascii.
fortran
2
To nie odpowiada na pytanie. Wie, że działa z obiektem bajtowym, ale nie obiektem ciągowym. Pytanie brzmi dlaczego .
Lennart Regebro
@fortran Domyślne kodowanie napisów w języku Python3 to UTF, nie wiem, dlaczego należy to jawnie ustawić.
xmedeko
0

To b oznacza po prostu, że pobierasz dane jako bajty lub tablicę bajtów, a nie jako ciąg.

Atul6.Singh
źródło