Mengapa saya perlu 'b' untuk menyandikan string dengan Base64?

258

Mengikuti contoh python ini , saya menyandikan string sebagai Base64 dengan:

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

Tapi, jika saya tinggalkan pemimpin b:

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

Saya mendapatkan kesalahan berikut:

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

Kenapa ini?

dublintech
sumber
38
Sebenarnya semua pertanyaan yang mengembalikan "TypeError: byte yang diharapkan, bukan str" memiliki jawaban yang sama.
Lennart Regebro

Jawaban:

274

base64 encoding mengambil 8-bit byte data dan mengkodekan itu hanya menggunakan karakter A-Z, a-z, 0-9, +, /* sehingga dapat ditransmisikan melalui saluran yang tidak melestarikan semua 8-bit data, seperti email.

Oleh karena itu, ia ingin string byte 8-bit. Anda membuatnya di Python 3 dengan b''sintaks.

Jika Anda menghapus b, itu menjadi string. String adalah urutan karakter Unicode. base64 tidak tahu apa yang harus dilakukan dengan data Unicode, ini bukan 8-bit. Sebenarnya tidak sedikit pun. :-)

Dalam contoh kedua Anda:

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

Semua karakter cocok dengan rapi ke set karakter ASCII, dan oleh karena itu pengkodean base64 agak tidak ada gunanya. Anda dapat mengubahnya menjadi ascii sebagai gantinya, dengan

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

Atau lebih sederhana:

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

Yang akan menjadi hal yang sama dalam hal ini.


* Sebagian besar rasa base64 juga bisa termasuk a =di akhir sebagai padding. Selain itu, beberapa varian base64 dapat menggunakan karakter selain +dan /. Lihat tabel ringkasan Varian di Wikipedia untuk ikhtisar.

Lennart Regebro
sumber
174

Jawaban singkat

Anda perlu untuk mendorong bytes-likeobjek ( bytes, bytearray, dll) ke base64.b64encode()metode. Berikut ini dua cara:

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

Atau dengan variabel:

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

Mengapa?

Dalam Python 3, strobjek bukan array karakter C-style (jadi mereka bukan byte array), melainkan, mereka adalah struktur data yang tidak memiliki pengkodean yang melekat. Anda dapat menyandikan string itu (atau menafsirkannya) dengan berbagai cara. Yang paling umum (dan default dalam Python 3) adalah utf-8, terutama karena itu kompatibel dengan ASCII (meskipun, seperti pengkodean yang paling banyak digunakan). Itulah yang terjadi ketika Anda mengambil stringdan memanggil .encode()metode di atasnya: Python menafsirkan string dalam utf-8 (pengodean default) dan memberikan Anda array byte yang sesuai dengan itu.

Pengodean Basis-64 dengan Python 3

Awalnya judul pertanyaan bertanya tentang pengkodean Base-64. Baca terus untuk hal-hal Base-64.

base64Pengkodean mengambil potongan biner 6-bit dan mengkodekannya menggunakan karakter AZ, az, 0-9, '+', '/', dan '=' (beberapa pengkodean menggunakan karakter yang berbeda di tempat '+' dan '/') . Ini adalah pengkodean karakter yang didasarkan pada konstruksi matematika dari sistem nomor radix-64 atau base-64, tetapi mereka sangat berbeda. Basis-64 dalam matematika adalah sistem bilangan seperti biner atau desimal, dan Anda melakukan perubahan radix ini pada seluruh bilangan, atau (jika radix yang Anda konversi adalah kekuatan 2 kurang dari 64) dalam potongan dari kanan ke kiri.

Dalam base64encoding, terjemahan dilakukan dari kiri ke kanan; 64 karakter pertama itulah mengapa disebut base64 encoding . Simbol '=' ke-65 digunakan untuk melapisi, karena penyandian menarik potongan 6-bit tetapi data yang biasanya dimaksudkan untuk disandikan adalah byte 8-bit, jadi kadang-kadang hanya ada dua atau 4 bit di chunk terakhir.

Contoh:

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

Jika Anda menginterpretasikan data biner itu sebagai bilangan bulat tunggal, maka ini adalah bagaimana Anda akan mengubahnya menjadi basis-10 dan basis-64 ( tabel untuk basis-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 pengkodean , bagaimanapun, akan mengelompokkan kembali data ini sebagai berikut:

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

Jadi, 'B0ZXN0' adalah versi basis-64 dari biner kami, secara matematis. Namun, base64 pengkodean harus melakukan pengkodean dalam arah yang berlawanan (sehingga data mentah dikonversi ke 'dGVzdA') dan juga memiliki aturan untuk memberi tahu aplikasi lain berapa banyak ruang yang tersisa pada akhirnya. Ini dilakukan dengan melapisi bagian akhir dengan simbol '='. Jadi, base64pengkodean data ini adalah 'dGVzdA ==', dengan dua simbol '=' untuk menandakan dua pasang bit harus dihapus dari bagian akhir ketika data ini di-decode untuk membuatnya cocok dengan data asli.

Mari kita uji ini untuk melihat apakah saya tidak jujur:

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

Mengapa menggunakan base64encoding?

Katakanlah saya harus mengirim beberapa data ke seseorang melalui email, seperti data ini:

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

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

Ada dua masalah yang saya tanam:

  1. Jika saya mencoba mengirim email itu di Unix, email itu akan dikirimkan segera setelah \x04karakter dibaca, karena itu adalah ASCII untuk END-OF-TRANSMISSION(Ctrl-D), sehingga data yang tersisa akan ditinggalkan dari transmisi.
  2. Juga, sementara Python cukup pintar untuk keluar dari semua karakter kontrol jahat saya ketika saya mencetak data secara langsung, ketika string itu diterjemahkan sebagai ASCII, Anda dapat melihat bahwa 'msg' tidak ada di sana. Itu karena saya menggunakan tiga BACKSPACEkarakter dan tiga SPACEkarakter untuk menghapus 'msg'. Jadi, bahkan jika saya tidak memiliki EOFkarakter di sana, pengguna akhir tidak akan dapat menerjemahkan dari teks di layar ke data mentah yang asli.

Ini hanya demo untuk menunjukkan betapa sulitnya mengirim data mentah. Pengkodean data ke dalam format base64 memberi Anda data yang sama persis tetapi dalam format yang memastikan aman untuk mengirim melalui media elektronik seperti email.

Greg Schmit
sumber
6
base64.b64encode(s.encode()).decode()tidak terlalu pythonic ketika semua yang Anda inginkan adalah string ke string konversi. base64.encode(s)harus cukup setidaknya di python3. Terima kasih atas penjelasan yang sangat baik tentang string dan byte dalam python
MortenB
2
@MortenB Ya, ini aneh, tetapi pada sisi atas sangat jelas apa yang terjadi selama insinyur menyadari perbedaan antara array byte dan string, karena tidak ada pemetaan tunggal (pengkodean) di antara mereka, seperti bahasa lainnya menganggap.
Greg Schmit
3
@MortenB By the way, base64.encode(s)tidak akan bekerja di Python3; apakah Anda mengatakan bahwa sesuatu seperti itu harus tersedia? Saya pikir alasannya mungkin membingungkan adalah bahwa, tergantung pada pengkodean dan isi string, smungkin tidak memiliki 1 representasi unik sebagai array byte.
Greg Schmit
Schmitt: itu hanya contoh betapa sederhananya seharusnya. usecases yang paling umum harus seperti itu.
MortenB
1
@MortenB tetapi b64 tidak hanya dimaksudkan untuk teks, konten biner apa pun dapat disandikan b64 (audio, gambar, dll). Membuatnya berfungsi saat Anda usulkan menurut saya menyembunyikan perbedaan antara teks dan array byte lebih, membuat debugging lebih sulit. Ini hanya memindahkan kesulitan di tempat lain.
Michael Ekoka
32

Jika data yang akan dikodekan berisi karakter "eksotis", saya pikir Anda harus menyandikan dalam "UTF-8"

encoded = base64.b64encode (bytes('data to be encoded', "utf-8"))
Alecz
sumber
24

Jika string adalah Unicode, cara termudah adalah:

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
sumber
Benar-benar bukan cara termudah, tetapi salah satu cara yang paling jelas, ketika penting pengkodean mana yang digunakan untuk mentransmisikan string, yang merupakan bagian dari "protokol" transmisi data melalui base64.
xuiqzy
12

Yang Anda butuhkan:

expected bytes, not str

Yang terdepan bmembuat string biner Anda.

Versi Python apa yang Anda gunakan? 2.x atau 3.x?

Sunting: Lihat http://docs.python.org/release/3.0.1/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit untuk detail berdarah string di Python 3.x


sumber
Terima kasih, saya menggunakan, 3.x. Mengapa Python ingin mengubahnya secara eksplisit menjadi biner. Hal yang sama di Ruby adalah ... membutuhkan> "base64" dan kemudian> Base64.encode64 ('data yang akan dikodekan')
dublintech
2
@dublintech Karena (unicode) teks berbeda dari data mentah. Jika Anda ingin menyandikan string teks di Base64, pertama-tama Anda perlu menentukan pengkodean karakter (seperti UTF-8) dan kemudian Anda memiliki byte daripada karakter, yang dapat Anda encode dalam bentuk teks ascii-safe.
fortran
2
Ini tidak menjawab pertanyaan. Dia tahu itu bekerja dengan objek byte, tetapi bukan objek string. Pertanyaannya adalah mengapa .
Lennart Regebro
@fortran Default Python3 string encoding adalah UTF, tidak tahu, mengapa harus diatur secara eksplisit.
xmedeko
0

Itu berarti Anda mengambil input sebagai byte atau byte array bukan sebagai string.

Atul6.Singh
sumber