Saya menjalankan potongan ini dua kali, di terminal Ubuntu (pengkodean diatur ke utf-8), sekali dengan ./test.py
dan kemudian dengan ./test.py >out.txt
:
uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni
Tanpa pengalihan mencetak sampah. Dengan pengalihan saya mendapatkan UnicodeDecodeError. Adakah yang bisa menjelaskan mengapa saya mendapatkan kesalahan hanya dalam kasus kedua, atau bahkan lebih baik memberikan penjelasan rinci tentang apa yang terjadi di balik tirai dalam kedua kasus?
Jawaban:
Kunci utama untuk masalah encoding tersebut adalah untuk memahami bahwa pada prinsipnya ada dua konsep berbeda dari "string" : (1) string karakter , dan (2) string / array byte. Perbedaan ini sebagian besar telah diabaikan untuk waktu yang lama karena penyandian di mana-mana yang bersejarah dengan tidak lebih dari 256 karakter (ASCII, Latin-1, Windows-1252, Mac OS Roman,…): pengkodean ini memetakan sekumpulan karakter umum ke angka antara 0 dan 255 (yaitu byte); pertukaran file yang relatif terbatas sebelum munculnya web membuat situasi penyandian yang tidak kompatibel ini dapat ditoleransi, karena sebagian besar program dapat mengabaikan fakta bahwa ada beberapa penyandiaksaraan selama mereka menghasilkan teks yang tetap pada sistem operasi yang sama: program semacam itu hanya akan perlakukan teks sebagai byte (melalui pengkodean yang digunakan oleh sistem operasi). Tampilan modern yang benar memisahkan kedua konsep string ini dengan tepat, berdasarkan dua poin berikut:
Karakter sebagian besar tidak terkait dengan komputer : seseorang dapat menggambarnya di papan kapur, dll., Seperti misalnya بايثون, 中 蟒 dan 🐍. "Karakter" untuk mesin juga mencakup "instruksi menggambar" seperti misalnya spasi, carriage return, instruksi untuk mengatur arah penulisan (untuk bahasa Arab, dll.), Aksen, dll. Daftar karakter yang sangat besar disertakan dalam standar Unicode ; itu mencakup sebagian besar karakter yang dikenal.
Di sisi lain, komputer memang perlu merepresentasikan karakter abstrak dengan beberapa cara: untuk ini, mereka menggunakan array byte (termasuk angka antara 0 dan 255), karena memori mereka datang dalam potongan byte. Proses yang diperlukan untuk mengubah karakter menjadi byte disebut pengkodean . Jadi, komputer membutuhkan pengkodean untuk mewakili karakter. Teks apa pun yang ada di komputer Anda dikodekan (hingga ditampilkan), apakah itu dikirim ke terminal (yang mengharapkan karakter dikodekan dengan cara tertentu), atau disimpan dalam file. Agar dapat ditampilkan atau "dipahami" dengan benar (oleh, katakanlah, penerjemah Python), aliran byte didekodekan menjadi karakter. Beberapa pengkodean(UTF-8, UTF-16,…) didefinisikan oleh Unicode untuk daftar karakternya (Unicode mendefinisikan kedua daftar karakter dan pengkodean untuk karakter ini — masih ada tempat di mana seseorang melihat ekspresi "Unicode encoding" sebagai cara untuk merujuk ke UTF-8 di mana-mana, tetapi ini adalah terminologi yang salah, karena Unicode menyediakan banyak pengkodean).
Singkatnya, komputer perlu merepresentasikan karakter secara internal dengan byte , dan mereka melakukannya melalui dua operasi:
Beberapa pengkodean tidak dapat mengkodekan semua karakter (misalnya, ASCII), sementara (beberapa) pengkodean Unicode memungkinkan Anda untuk mengenkode semua karakter Unicode. Pengkodean juga belum tentu unik , karena beberapa karakter dapat direpresentasikan baik secara langsung atau sebagai kombinasi (misalnya, karakter dasar dan aksen).
Perhatikan bahwa konsep baris baru menambahkan lapisan kerumitan , karena dapat diwakili oleh karakter (kontrol) berbeda yang bergantung pada sistem operasi (ini adalah alasan mode membaca file baris baru universal Python ).
Sekarang, apa yang saya sebut "karakter" di atas adalah apa yang disebut Unicode sebagai " karakter yang dipersepsi pengguna ". Karakter tunggal yang dirasakan pengguna terkadang dapat direpresentasikan dalam Unicode dengan menggabungkan bagian karakter (karakter dasar, aksen,…) yang ditemukan di indeks berbeda dalam daftar Unicode, yang disebut " poin kode " —poin kode ini dapat digabungkan bersama untuk membentuk sebuah "cluster grafem". Unicode dengan demikian mengarah ke konsep string ketiga, yang dibuat dari urutan poin kode Unicode, yang berada di antara byte dan string karakter, dan yang lebih dekat dengan yang terakhir. Saya akan menyebutnya " string Unicode " (seperti di Python 2).
Sementara Python dapat mencetak string karakter (yang dirasakan pengguna), string non-byte Python pada dasarnya adalah urutan poin kode Unicode , bukan karakter yang dipersepsi pengguna. Nilai titik kode adalah yang digunakan dalam sintaks string Python
\u
dan\U
Unicode. Mereka tidak boleh bingung dengan pengkodean karakter (dan tidak harus memiliki hubungan apa pun dengannya: Titik kode Unicode dapat dikodekan dengan berbagai cara).Ini memiliki konsekuensi penting: panjang string Python (Unicode) adalah jumlah poin kodenya, yang tidak selalu jumlah karakter yang dirasakan pengguna : jadi
s = "\u1100\u1161\u11a8"; print(s, "len", len(s))
(Python 3) memberi각 len 3
meskipuns
memiliki satu yang dirasakan pengguna (Korea) karakter (karena diwakili dengan 3 titik kode — meskipun tidak harus, seperti yangprint("\uac01")
ditunjukkan). Namun, dalam banyak keadaan praktis, panjang string adalah jumlah karakter yang dianggap pengguna, karena banyak karakter biasanya disimpan oleh Python sebagai titik kode Unicode tunggal.Dalam Python 2 , string Unicode disebut… "Unicode strings" (
unicode
tipe, bentuk literalu"…"
), sedangkan array byte adalah "string" (str
jenis, di mana array byte dapat misalnya dibangun dengan string literal"…"
). Dalam Python 3 , string Unicode disebut "string" (str
tipe, bentuk literal"…"
), sedangkan array byte disebut "byte" (bytes
tipe, bentuk literalb"…"
). Akibatnya, sesuatu seperti"🐍"[0]
memberikan hasil yang berbeda dalam Python 2 ('\xf0'
, byte) dan Python 3 ("🐍"
, karakter pertama dan satu-satunya).Dengan beberapa poin kunci ini, Anda seharusnya dapat memahami sebagian besar pertanyaan terkait pengkodean!
Biasanya, saat Anda mencetak
u"…"
ke terminal , Anda tidak akan mendapatkan sampah: Python mengetahui pengkodean terminal Anda. Nyatanya, Anda dapat memeriksa pengkodean apa yang diharapkan terminal:Jika karakter input Anda dapat dikodekan dengan pengkodean terminal, Python akan melakukannya dan akan mengirimkan byte yang sesuai ke terminal Anda tanpa mengeluh. Terminal kemudian akan melakukan yang terbaik untuk menampilkan karakter setelah mendekode byte input (paling buruk font terminal tidak memiliki beberapa karakter dan sebaliknya akan mencetak beberapa jenis kosong).
Jika karakter input Anda tidak dapat dikodekan dengan pengkodean terminal, itu berarti terminal tidak dikonfigurasi untuk menampilkan karakter ini. Python akan mengeluh (dalam Python dengan a
UnicodeEncodeError
karena string karakter tidak dapat dikodekan dengan cara yang sesuai dengan terminal Anda). Satu-satunya solusi yang mungkin adalah menggunakan terminal yang dapat menampilkan karakter (baik dengan mengkonfigurasi terminal sehingga menerima pengkodean yang dapat mewakili karakter Anda, atau dengan menggunakan program terminal yang berbeda). Ini penting ketika Anda mendistribusikan program yang dapat digunakan di lingkungan yang berbeda: pesan yang Anda cetak harus dapat diwakili di terminal pengguna. Terkadang yang terbaik adalah tetap menggunakan string yang hanya berisi karakter ASCII.Namun, ketika Anda mengalihkan atau menyalurkan output dari program Anda, maka umumnya tidak mungkin untuk mengetahui apa pengkodean input dari program penerima, dan kode di atas mengembalikan beberapa pengkodean default: Tidak Ada (Python 2.7) atau UTF-8 ( Python 3):
Pengkodean stdin, stdout dan stderr dapat diatur melalui
PYTHONIOENCODING
variabel lingkungan, jika diperlukan:Jika pencetakan ke terminal tidak menghasilkan apa yang Anda harapkan, Anda dapat memeriksa apakah pengkodean UTF-8 yang Anda masukkan secara manual sudah benar; misalnya, karakter pertama Anda (
\u001A
) tidak dapat dicetak, jika saya tidak salah .Di http://wiki.python.org/moin/PrintFails , Anda dapat menemukan solusi seperti berikut, untuk Python 2.x:
Untuk Python 3, Anda dapat memeriksa salah satu pertanyaan yang ditanyakan sebelumnya di StackOverflow.
sumber
Python selalu mengkodekan string Unicode saat menulis ke terminal, file, pipa, dll. Saat menulis ke terminal Python biasanya dapat menentukan pengkodean terminal dan menggunakannya dengan benar. Saat menulis ke file atau pipa, Python default ke pengkodean 'ascii' kecuali secara eksplisit diberitahu sebaliknya. Python dapat diberi tahu apa yang harus dilakukan saat menyalurkan output melalui
PYTHONIOENCODING
variabel lingkungan. Sebuah shell dapat mengatur variabel ini sebelum mengarahkan keluaran Python ke file atau pipa sehingga pengkodean yang benar diketahui.Dalam kasus Anda, Anda telah mencetak 4 karakter tidak umum yang tidak didukung terminal Anda dalam fontnya. Berikut beberapa contoh untuk membantu menjelaskan perilakunya, dengan karakter yang sebenarnya didukung oleh terminal saya (yang menggunakan cp437, bukan UTF-8).
Contoh 1
Perhatikan bahwa
#coding
komentar tersebut menunjukkan pengkodean di mana file sumber disimpan. Saya memilih utf8 sehingga saya dapat mendukung karakter dalam sumber yang tidak dapat dilakukan oleh terminal saya. Pengkodean dialihkan ke stderr sehingga bisa dilihat saat diarahkan ke file.Output (dijalankan langsung dari terminal)
Python menentukan pengkodean terminal dengan benar.
Output (dialihkan ke file)
Python tidak dapat menentukan pengkodean (Tidak Ada) jadi gunakan 'ascii' default. ASCII hanya mendukung pengubahan 128 karakter pertama Unicode.
Output (diarahkan ke file, PYTHONIOENCODING = cp437)
dan file keluaran saya benar:
Contoh 2
Sekarang saya akan memasukkan karakter di sumber yang tidak didukung oleh terminal saya:
Output (dijalankan langsung dari terminal)
Terminal saya tidak memahami karakter China terakhir itu.
Output (jalankan langsung, PYTHONIOENCODING = 437: ganti)
Penangan kesalahan dapat ditentukan dengan pengkodean. Dalam hal ini karakter yang tidak diketahui diganti dengan
?
.ignore
danxmlcharrefreplace
beberapa opsi lainnya. Saat menggunakan UTF8 (yang mendukung pengkodean semua karakter Unicode) penggantian tidak akan pernah dilakukan, tetapi font yang digunakan untuk menampilkan karakter harus tetap mendukungnya.sumber
PYTHONIOENCODING
. Melakukanprint string.encode("UTF-8")
seperti yang disarankan oleh @Ismail berhasil untuk saya.chcp
codepage tidak mendukungnya. Untuk menghindarinyaUnicodeEncodeError: 'charmap'
, Anda bisa memasangwin-unicode-console
paket.PYTHONIOENCODING=utf-8
memecahkan masalah.Encode itu saat mencetak
Ini karena ketika Anda menjalankan skrip secara manual python mengkodekannya sebelum mengeluarkannya ke terminal, ketika Anda menyalurkannya python tidak menyandikannya sendiri sehingga Anda harus menyandikannya secara manual saat melakukan I / O.
sumber
win-unicode-console
(Windows), atau terima parameter baris perintah (jika harus).