Jenis Python str vs unicode


101

Bekerja dengan Python 2.7, saya bertanya-tanya apa keuntungan nyata yang ada dalam menggunakan tipe unicodedaripada str, karena keduanya tampaknya dapat menahan string Unicode. Apakah ada alasan khusus selain mampu mengatur kode Unicode di unicodestring menggunakan char melarikan diri \?:

Menjalankan modul dengan:

# -*- coding: utf-8 -*-

a = 'á'
ua = u'á'
print a, ua

Hasil dalam: á, á

EDIT:

Lebih banyak pengujian menggunakan shell Python:

>>> a = 'á'
>>> a
'\xc3\xa1'
>>> ua = u'á'
>>> ua
u'\xe1'
>>> ua.encode('utf8')
'\xc3\xa1'
>>> ua.encode('latin1')
'\xe1'
>>> ua
u'\xe1'

Jadi, unicodestring tersebut tampaknya dikodekan menggunakan latin1alih-alih utf-8dan string mentah dikodekan menggunakan utf-8? Saya bahkan lebih bingung sekarang! : S


Tidak ada pengkodean untuk unicode, ini hanya abstraksi dari karakter unicode; unicodedapat diubah menjadi strdengan beberapa pengkodean (misalnya utf-8).
Bin

Jawaban:


178

unicodedimaksudkan untuk menangani teks . Teks adalah urutan poin kode yang mungkin lebih besar dari satu byte . Teks dapat dikodekan dalam pengkodean tertentu untuk mewakili teks sebagai byte mentah (misalnya utf-8, latin-1...).

Perhatikan bahwa unicode tidak dikodekan ! Representasi internal yang digunakan oleh python adalah detail implementasi, dan Anda tidak boleh mempedulikannya selama representasi tersebut dapat mewakili poin kode yang Anda inginkan.

Sebaliknya strdi Python 2 adalah urutan byte biasa . Itu tidak mewakili teks!

Anda dapat menganggapnya unicodesebagai representasi umum dari beberapa teks, yang dapat dikodekan dengan berbagai cara menjadi urutan data biner yang direpresentasikan melalui str.

Catatan: Dalam Python 3, unicodediubah namanya menjadi strdan ada bytestipe baru untuk urutan byte biasa.

Beberapa perbedaan yang bisa Anda lihat:

>>> len(u'à')  # a single code point
1
>>> len('à')   # by default utf-8 -> takes two bytes
2
>>> len(u'à'.encode('utf-8'))
2
>>> len(u'à'.encode('latin1'))  # in latin1 it takes one byte
1
>>> print u'à'.encode('utf-8')  # terminal encoding is utf-8
à
>>> print u'à'.encode('latin1') # it cannot understand the latin1 byte

Perhatikan bahwa menggunakan strAnda memiliki kontrol tingkat yang lebih rendah pada byte tunggal dari representasi pengkodean tertentu, sementara menggunakan unicodeAnda hanya dapat mengontrol pada tingkat titik kode. Misalnya, Anda dapat melakukan:

>>> 'àèìòù'
'\xc3\xa0\xc3\xa8\xc3\xac\xc3\xb2\xc3\xb9'
>>> print 'àèìòù'.replace('\xa8', '')
à�ìòù

Apa yang sebelumnya UTF-8 valid, sekarang tidak lagi. Menggunakan string unicode Anda tidak dapat beroperasi sedemikian rupa sehingga string yang dihasilkan bukan teks unicode yang valid. Anda dapat menghapus titik kode, mengganti titik kode dengan titik kode yang berbeda, dll. Tetapi Anda tidak dapat mengacaukan representasi internal.


4
Terima kasih banyak atas jawaban Anda, itu sangat membantu! Bagian yang paling memperjelas bagi saya adalah: "unicode tidak dikodekan! Representasi internal yang digunakan oleh python adalah detail implementasi, dan Anda tidak perlu mempedulikannya [...]". Jadi, ketika membuat serialisasi unicodeobjek, saya rasa pertama-tama kita harus secara eksplisit encode()memasukkannya ke format pengkodean yang tepat, karena kita tidak tahu mana yang digunakan secara internal untuk mewakili unicodenilainya.
Caumons

10
Iya. Ketika Anda ingin menyimpan beberapa teks (misalnya ke file) Anda harus mewakilinya dengan byte, yaitu Anda harus menyandikannya . Saat mengambil konten, Anda harus mengetahui pengkodean yang digunakan, agar dapat mendekode byte menjadi unicodeobjek.
Bakuriu

Maaf, tetapi pernyataan yang unicodetidak dikodekan salah. UTF-16 / UCS-2 dan UTF-32 / UCS-4 juga merupakan penyandiaksaraan ... dan di masa mendatang lebih banyak lagi yang mungkin akan dibuat. Intinya adalah, hanya karena Anda tidak perlu peduli tentang detail implementasi (dan, memang, Anda tidak boleh!), Tetap tidak berarti itu unicodetidak dikodekan. Tentu saja. Apakah itu bisa .decode()'d adalah cerita yang sama sekali berbeda.
0xC0000022L

1
@ 0xC0000022L Mungkin kalimat seperti itu tidak jelas. Seharusnya dikatakan: unicoderepresentasi internal objek dapat berupa apa pun yang diinginkannya, termasuk yang non-standar. Secara khusus di python3 + unicode tidak menggunakan representasi internal non-standar yang juga berubah tergantung pada data yang ada. Karena itu, ini bukan pengkodean standar . Unicode sebagai standar teks hanya mendefinisikan titik kode yang merupakan representasi abstrak dari teks, ada banyak cara untuk menyandikan unicode dalam memori termasuk standar utf-X dll. Python menggunakan caranya sendiri untuk efisiensi.
Bakuriu

1
@ 0xC0000022L Juga fakta bahwa UTF-16 adalah pengkodean tidak ada hubungannya dengan objek CPython unicode, karena tidak menggunakan UTF-16, atau UTF-32. Ini menggunakan representasi ad hoc, dan jika Anda ingin menyandikan data ke dalam byte aktual, Anda harus menggunakan encode. Juga: bahasa tidak mengamanatkan bagaimana unicodediimplementasikan, sehingga versi atau implementasi yang berbeda dari python dapat (dan memang memiliki ) representasi internal yang berbeda.
Bakuriu

38

Unicode dan pengkodean sama sekali berbeda, hal-hal yang tidak terkait.

Unicode

Menetapkan ID numerik untuk setiap karakter:

  • 0x41 → A
  • 0xE1 → á
  • 0x414 → Д

Jadi, Unicode memberikan angka 0x41 ke A, 0xE1 ke á, dan 0x414 ke Д.

Bahkan panah kecil → yang saya gunakan memiliki nomor Unicode-nya, 0x2192. Dan bahkan emoji memiliki nomor Unicode, 😂 adalah 0x1F602.

Anda dapat mencari nomor Unicode dari semua karakter di tabel ini . Secara khusus, Anda dapat menemukan tiga karakter pertama di atas di sini , panah di sini , dan emoji di sini .

Nomor-nomor ini ditetapkan ke semua karakter oleh Unicode disebut poin kode .

Tujuan dari semua ini adalah untuk menyediakan sarana untuk secara jelas mengacu pada setiap karakter. Misalnya kalau saya ngomongin 😂, daripada bilang "kamu tahu, emoji tertawa ini berlinang air mata" , saya cukup bilang, titik kode Unicode 0x1F602 . Lebih mudah, bukan?

Perhatikan bahwa titik kode Unicode biasanya diformat dengan awalan U+, kemudian nilai numerik heksadesimal ditambahkan ke setidaknya 4 digit. Jadi, contoh di atas adalah U + 0041, U + 00E1, U + 0414, U + 2192, U + 1F602.

Poin kode unicode berkisar dari U + 0000 hingga U + 10FFFF. Itu adalah 1.114.112 angka. 2048 dari angka-angka ini digunakan sebagai pengganti , jadi masih ada 1.112.064. Ini berarti, Unicode dapat menetapkan ID unik (titik kode) ke 1.112.064 karakter berbeda. Belum semua poin kode ini ditetapkan ke sebuah karakter, dan Unicode diperpanjang terus menerus (misalnya, saat emoji baru diperkenalkan).

Hal penting yang harus diingat adalah bahwa semua yang dilakukan Unicode adalah menetapkan ID numerik, yang disebut titik kode, ke setiap karakter untuk referensi yang mudah dan tidak ambigu.

Pengodean

Memetakan karakter ke pola bit.

Pola bit ini digunakan untuk mewakili karakter dalam memori komputer atau pada disk.

Ada banyak pengkodean berbeda yang mencakup himpunan bagian karakter yang berbeda. Di dunia berbahasa Inggris, penyandiaksaraan yang paling umum adalah sebagai berikut:

ASCII

Peta karakter 128 (poin kode U + 0000 untuk U + 007F) ke pola bit panjang 7.

Contoh:

  • a → 1100001 (0x61)

Anda dapat melihat semua pemetaan di tabel ini .

ISO 8859-1 (alias Latin-1)

Peta karakter 191 (kode poin U + 0020 untuk U + 007E dan U + 00A0 untuk U + 00FF) ke pola bit panjang 8.

Contoh:

  • a → 00.00001 (0x61)
  • á → 11100001 (0xE1)

Anda dapat melihat semua pemetaan di tabel ini .

UTF-8

Maps 1.112.064 karakter (semua yang ada Unicode kode poin) ke pola bit baik panjang 8, 16, 24, atau 32 bit (yaitu, 1, 2, 3, atau 4 byte).

Contoh:

  • a → 00.00001 (0x61)
  • á → 11000011 10100001 (0xC3 0xA1)
  • ≠ → 11100010 10001001 10100000 (0xE2 0x89 0xA0)
  • 😂 → 11110000 10011111 10011000 10000010 (0xF0 0x9F 0x98 0x82)

Cara UTF-8 mengkodekan karakter menjadi string bit dijelaskan dengan sangat baik di sini .

Unicode dan Encoding

Melihat contoh di atas, menjadi jelas bagaimana Unicode berguna.

Misalnya, jika saya Latin-1 dan saya ingin menjelaskan pengkodean saya untuk á, saya tidak perlu mengatakan:

"Saya menyandikannya dengan aigu (atau bagaimanapun Anda menyebutnya bilah naik) sebagai 11100001"

Tapi saya hanya bisa mengatakan:

"Saya menyandikan U + 00E1 sebagai 11100001"

Dan jika saya UTF-8 , saya dapat mengatakan:

"Saya, pada gilirannya, saya menyandikan U + 00E1 sebagai 11000011 10100001"

Dan sangat jelas bagi semua orang karakter mana yang kami maksud.

Sekarang untuk kebingungan yang sering timbul

Memang benar bahwa terkadang pola bit pengkodean, jika Anda menafsirkannya sebagai bilangan biner, sama dengan titik kode Unicode dari karakter ini.

Sebagai contoh:

  • ASCII mengkodekan suatu sebagai 1100001, yang Anda dapat menafsirkan sebagai angka heksadesimal 0x61 , dan titik kode Unicode dari suatu adalah U + 0061 .
  • Latin-1 menyandikan á sebagai 11100001, yang dapat Anda tafsirkan sebagai angka heksadesimal 0xE1 , dan titik kode Unicode dari á adalah U + 00E1 .

Tentu saja, ini sengaja diatur seperti ini untuk kenyamanan. Tetapi Anda harus melihatnya sebagai kebetulan belaka . Pola bit yang digunakan untuk merepresentasikan karakter dalam memori tidak terikat dengan cara apa pun ke titik kode Unicode dari karakter ini.

Bahkan tidak ada yang mengatakan bahwa Anda harus menafsirkan string bit seperti 11100001 sebagai bilangan biner. Lihat saja sebagai urutan bit yang digunakan Latin-1 untuk menyandikan karakter á .

Kembali ke pertanyaan Anda

Pengkodean yang digunakan oleh penerjemah Python Anda adalah UTF-8 .

Inilah yang terjadi dalam contoh Anda:

Contoh 1

Berikut ini mengkodekan karakter á dalam UTF-8. Ini menghasilkan string bit 11000011 10100001, yang disimpan dalam variabel a.

>>> a = 'á'

Saat Anda melihat nilai a, isinya 11000011 10100001 diformat sebagai nomor hex 0xC3 0xA1 dan keluarannya sebagai '\xc3\xa1':

>>> a
'\xc3\xa1'

Contoh 2

Yang berikut ini menyimpan titik kode Unicode dari á, yaitu U + 00E1, dalam variabel ua(kami tidak tahu format data mana yang digunakan Python secara internal untuk mewakili titik kode U + 00E1 dalam memori, dan itu tidak penting bagi kami):

>>> ua = u'á'

Saat Anda melihat nilai ua, Python memberi tahu Anda bahwa itu berisi titik kode U + 00E1:

>>> ua
u'\xe1'

Contoh 3

Kode berikut menyandikan titik kode Unicode U + 00E1 (mewakili karakter á) dengan UTF-8, yang menghasilkan pola bit 11000011 10100001. Sekali lagi, untuk keluaran, pola bit ini direpresentasikan sebagai nomor hex 0xC3 0xA1:

>>> ua.encode('utf-8')
'\xc3\xa1'

Contoh 4

Berikut ini menyandikan titik kode Unicode U + 00E1 (mewakili karakter á) dengan Latin-1, yang menghasilkan pola bit 11100001. Untuk keluaran, pola bit ini direpresentasikan sebagai angka hex 0xE1, yang kebetulan sama dengan awal titik kode U + 00E1:

>>> ua.encode('latin1')
'\xe1'

Tidak ada hubungan antara objek Unicode uadan pengkodean Latin-1. Titik kode á adalah U + 00E1 dan pengkodean Latin-1 dari á adalah 0xE1 (jika Anda menafsirkan pola bit pengkodean sebagai bilangan biner) adalah murni kebetulan.


31

Terminal Anda kebetulan dikonfigurasi ke UTF-8.

Fakta bahwa percetakan aadalah suatu kebetulan; Anda menulis byte UTF-8 mentah ke terminal. aadalah nilai panjang dua , berisi dua byte, nilai hex C3 dan A1, sedangkan uanilai unicode panjang satu , berisi titik kode U + 00E1.

Perbedaan panjang ini adalah salah satu alasan utama untuk menggunakan nilai Unicode; Anda tidak dapat dengan mudah mengukur jumlah karakter teks dalam string byte; yang len()dari string byte memberitahu Anda berapa banyak byte yang digunakan, bukan berapa banyak karakter yang dikodekan.

Anda dapat melihat perbedaannya saat mengenkode nilai unicode ke enkode keluaran yang berbeda:

>>> a = 'á'
>>> ua = u'á'
>>> ua.encode('utf8')
'\xc3\xa1'
>>> ua.encode('latin1')
'\xe1'
>>> a
'\xc3\xa1'

Perhatikan bahwa 256 titik kode pertama dari standar Unicode cocok dengan standar Latin 1, sehingga titik kode U + 00E1 dikodekan ke Latin 1 sebagai byte dengan nilai hex E1.

Selain itu, Python menggunakan kode escape dalam representasi unicode dan string byte, dan poin kode rendah yang tidak dapat dicetak ASCII juga direpresentasikan menggunakan \x..nilai escape. Inilah sebabnya mengapa string Unicode dengan titik kode antara 128 dan 255 terlihat hanya seperti Latin 1 encoding. Jika Anda memiliki string unicode dengan titik kode di luar U + 00FF, urutan pelolosan yang berbeda \u....digunakan sebagai gantinya, dengan nilai hex empat digit.

Sepertinya Anda belum sepenuhnya memahami apa perbedaan antara Unicode dan pengkodean. Harap baca artikel berikut sebelum Anda melanjutkan:


Saya telah mengedit pertanyaan saya dengan pengujian lebih lanjut. Saya telah membaca unicode dan pengkodean yang berbeda untuk sementara waktu dan saya pikir saya memahami teorinya, tetapi ketika benar-benar menguji kode Python saya tidak menangkap apa yang terjadi
Caumons

1
Pengkodean latin-1 cocok dengan 256 titik kode pertama dari standar Unicode. Inilah sebabnya mengapa U + 00E1 dikodekan \xe1dalam bahasa Latin 1.
Martijn Pieters

2
Itulah satu-satunya aspek terpenting untuk Unicode. Ini bukan pengkodean . Ini adalah teks. Unicode adalah standar yang mencakup lebih banyak lagi, seperti informasi tentang titik kode apa itu angka, atau spasi atau kategori lain, harus ditampilkan dari kiri ke kanan atau kanan ke kiri, dll. Dll.
Martijn Pieters

1
Ini seperti mengatakan Unicode seperti "Antarmuka" dan Pengkodean seperti "Implementasi" yang sebenarnya.
Caumons

2
@Varun: Anda harus menggunakan Python 2 narrow build, yang menggunakan UCS-2 secara internal dan salah mengartikan apa pun di atas U + FFFF sebagai memiliki panjang dua. Python 3 dan UCS-2 (lebar) build akan menunjukkan kepada Anda panjangnya 1.
Martijn Pieters

2

Saat Anda mendefinisikan a sebagai unicode, karakter a dan á sama. Jika tidak, dihitung sebagai dua karakter. Coba len (a) dan len (au). Selain itu, Anda mungkin perlu memiliki pengkodean saat bekerja dengan lingkungan lain. Misalnya jika Anda menggunakan md5, Anda mendapatkan nilai yang berbeda untuk a dan ua

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.