Mengapa kita TIDAK harus menggunakan sys.setdefaultencoding ("utf-8") dalam skrip py?

166

Saya telah melihat beberapa skrip py yang menggunakan ini di bagian atas skrip. Dalam kasus apa seseorang harus menggunakannya?

import sys
reload(sys)
sys.setdefaultencoding("utf-8")
mlzboy
sumber
2
ada masalah dengan menggunakan ini di ipython,% waktu berhenti bekerja github.com/ipython/ipython/issues/8071
seanv507
3
@ seanv507, baca jawaban - menggunakannya sangat tidak disarankan
Alastair McCormack
2
Bagaimana ini bukan duplikat tepat dari Bahaya sys.setdefaultencoding ('utf-8') ? Meskipun ini (2010) bertanya sebelum itu (2015)? Tetapi pertanyaan itu juga memiliki jawaban yang bagus. Apa yang harus dilakukan? Juga, untuk menjadi jelas, pertanyaan ini hanya masuk akal pada Python 2 bukan 3, namun itu tidak ditandai atau disebutkan.
smci
layak dibaca sebelum menyelam ke jawaban SO: pythonhosted.org/kitchen/unicode-frustrations.html
ccpizza

Jawaban:

141

Sesuai dokumentasi: Ini memungkinkan Anda untuk beralih dari ASCII default ke penyandian lain seperti UTF-8, yang akan digunakan oleh runtime Python setiap kali harus mendekode buffer string ke unicode.

Fungsi ini hanya tersedia pada waktu mulai Python, ketika Python memindai lingkungan. Itu harus disebut dalam modul sistem-lebar sitecustomize.py,, Setelah modul ini dievaluasi, setdefaultencoding()fungsi dihapus dari sysmodul.

Satu-satunya cara untuk benar-benar menggunakannya adalah dengan retas retas yang mengembalikan atribut.

Juga, penggunaan sys.setdefaultencoding()selalu tidak disarankan , dan telah menjadi larangan di py3k. Pengkodean py3k adalah terprogram untuk "utf-8" dan mengubahnya menimbulkan kesalahan.

Saya menyarankan beberapa petunjuk untuk membaca:

pyfunc
sumber
6
Sangat bagus, meskipun ada sedikit kematian karena terlalu banyak informasi di sini. Saya belajar paling hanya berfokus pada artikel ini: blog.notdot.net/2010/07/Getting-unicode-right-in-Python
mbb
3
Saya ingin menambahkan bahwa pengkodean default juga digunakan untuk pengkodean (saat menulis ke sys.stdoutketika memiliki Nonepengkodean, seperti ketika mengarahkan output dari program Python).
Eric O Lebigot
14
+1 untuk "penggunaan sys.setdefaultencoding()selalu tidak disarankan"
jfs
7
'kabel-kabel ke utf-8' tidak benar, itu tidak bawaan dan tidak selalu UTF-8. LC_ALL=en_US.UTF-8 python3 -c 'import sys; print(sys.stdout.encoding)'memberi UTF-8tetapi LC_ALL=C python3 -c 'import sys; print(sys.stdout.encoding)'memberi ANSI_X3.4-1968(atau mungkin sesuatu yang lain)
Tino
7
@Tino, pengkodean konsol terpisah dari pengodean default.
Alastair McCormack
59

tl; dr

Jawabannya TIDAK PERNAH ! (kecuali jika Anda benar-benar tahu apa yang Anda lakukan)

9/10 kali solusinya dapat diselesaikan dengan pemahaman yang tepat tentang pengkodean / decoding.

1/10 orang memiliki lokal atau lingkungan yang tidak didefinisikan dengan benar dan perlu mengatur:

PYTHONIOENCODING="UTF-8"  

di lingkungan mereka untuk memperbaiki masalah pencetakan konsol.

Apa fungsinya?

sys.setdefaultencoding("utf-8")(dipukul untuk menghindari penggunaan kembali) mengubah pengkodean / dekode default yang digunakan setiap kali Python 2.x perlu mengubah Unicode () ke str () (dan sebaliknya) dan pengkodean tidak diberikan. Yaitu:

str(u"\u20AC")
unicode("€")
"{}".format(u"\u20AC") 

Dalam Python 2.x, penyandian default diatur ke ASCII dan contoh di atas akan gagal dengan:

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 0: ordinal not in range(128)

(Konsol saya dikonfigurasi sebagai UTF-8, jadi "€" = '\xe2\x82\xac', karenanya pengecualian pada \xe2)

atau

UnicodeEncodeError: 'ascii' codec can't encode character u'\u20ac' in position 0: ordinal not in range(128)

sys.setdefaultencoding("utf-8")akan memungkinkan ini bekerja untuk saya , tetapi tidak akan selalu berfungsi untuk orang yang tidak menggunakan UTF-8. Default ASCII memastikan bahwa asumsi pengkodean tidak dimasukkan ke dalam kode

Menghibur

sys.setdefaultencoding("utf-8")juga memiliki efek samping muncul untuk memperbaiki sys.stdout.encoding, digunakan saat mencetak karakter ke konsol. Python menggunakan lokal pengguna (Linux / OS X / Un * x) atau codepage (Windows) untuk mengatur ini. Kadang-kadang, lokal pengguna rusak dan hanya perlu PYTHONIOENCODINGmemperbaiki pengkodean konsol .

Contoh:

$ export LANG=en_GB.gibberish
$ python
>>> import sys
>>> sys.stdout.encoding
'ANSI_X3.4-1968'
>>> print u"\u20AC"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u20ac' in position 0: ordinal not in range(128)
>>> exit()

$ PYTHONIOENCODING=UTF-8 python
>>> import sys
>>> sys.stdout.encoding
'UTF-8'
>>> print u"\u20AC"
€

Apa yang buruk dengan sys.setdefaultencoding ("utf-8") ?

Orang-orang telah mengembangkan terhadap Python 2.x selama 16 tahun dengan pemahaman bahwa penyandian default adalah ASCII. UnicodeErrormetode penanganan pengecualian telah ditulis untuk menangani konversi string ke Unicode pada string yang ternyata mengandung non-ASCII.

Dari https://anonbadger.wordpress.com/2015/06/16/why-sys-setdefaultencoding-will-break-code/

def welcome_message(byte_string):
    try:
        return u"%s runs your business" % byte_string
    except UnicodeError:
        return u"%s runs your business" % unicode(byte_string,
            encoding=detect_encoding(byte_string))

print(welcome_message(u"Angstrom (Å®)".encode("latin-1"))

Sebelum menetapkan defaultencoding, kode ini tidak akan dapat men-decode "Å" dalam encoding ascii dan kemudian akan memasukkan pengendali pengecualian untuk menebak encoding dan mengubahnya dengan benar menjadi unicode. Mencetak: Angstrom (Å®) menjalankan bisnis Anda. Setelah Anda menetapkan defaultencoding ke utf-8 kode akan menemukan bahwa byte_string dapat diartikan sebagai utf-8 dan itu akan memotong-motong data dan mengembalikan ini sebagai gantinya: Angstrom (Ů) menjalankan bisnis Anda.

Mengubah apa yang seharusnya konstan akan memiliki efek dramatis pada modul yang Anda andalkan. Lebih baik memperbaiki data yang masuk dan keluar dari kode Anda.

Contoh masalah

Sementara pengaturan defaultencoding ke UTF-8 bukan penyebab utama dalam contoh berikut, ini menunjukkan bagaimana masalah ditutup dan bagaimana, ketika input encoding berubah, kode tersebut terputus dengan cara yang tidak jelas: UnicodeDecodeError: codec 'utf8' dapat mendekode byte 0x80 di posisi 3131: byte awal tidak valid

Alastair McCormack
sumber
2
Meskipun ada kejutan di sys.setdefaultencoding("utf-8")dalamnya, ada baiknya membuat kode berperilaku lebih seperti Python 3. Sekarang 2017. Bahkan ketika Anda menulis jawabannya pada tahun 2015, saya pikir sudah lebih baik untuk melihat ke depan daripada ke belakang. Itu sebenarnya solusi paling sederhana bagi saya, ketika saya menemukan kode saya berperilaku berbeda di Python 2 tergantung pada apakah output diarahkan (masalah yang sangat buruk untuk Python 2). Tak perlu dikatakan, saya sudah punya # coding: utf-8, dan saya tidak perlu ada solusi untuk Python 3 (saya benar-benar harus menutupi setdefaultencodingcek menggunakan versi).
Yongwei Wu
Itu bagus dan berfungsi untuk Anda tetapi sys.setdefaultencoding("utf-8")tidak membuat kode Py 2.x Anda kompatibel dengan Python 3. Juga tidak memperbaiki modul eksternal yang menganggap pengkodean default adalah ASCII. Membuat kode Anda kompatibel dengan Python 3 sangat sederhana dan tidak memerlukan peretasan jahat ini. Misalnya mengapa ini menyebabkan masalah yang sangat nyata, lihat pengalaman saya dengan Amazon mengacaukan asumsi ini: stackoverflow.com/questions/39465220/…
Alastair McCormack
1
@AlastairMcCormack you rock, Situs saya sudah sejak berbulan-bulan dan tidak tahu apa yang harus dilakukan. Akhirnya, PYTHONIOENCODING="UTF-8"membantu lingkungan Python2.7 Django-1.11 saya. Terima kasih.
sam
Saya tahu Anda menyalin contohnya, tetapi saya dapat menemukan paket apa yang dimiliki detect_encoding.
dlamblin
@ dlamblin Contoh kode adalah untuk membuktikan kutipan dan tidak seharusnya digunakan dalam kode Anda. Bayangkan itu detect_encodingadalah metode yang bisa mendeteksi pengkodean string berdasarkan petunjuk bahasa.
Alastair McCormack
18
#!/usr/bin/env python
#-*- coding: utf-8 -*-
u = u'moçambique'
print u.encode("utf-8")
print u

chmod +x test.py
./test.py
moçambique
moçambique

./test.py > output.txt
Traceback (most recent call last):
  File "./test.py", line 5, in <module>
    print u
UnicodeEncodeError: 'ascii' codec can't encode character 
u'\xe7' in position 2: ordinal not in range(128)

pada shell bekerja, mengirim ke sdtout tidak, jadi itu adalah satu solusi, untuk menulis ke stdout

Saya membuat pendekatan lain, yang tidak berjalan jika sys.stdout.encoding tidak mendefinisikan, atau dengan kata lain, perlu ekspor PYTHONIOENCODING = UTF-8 terlebih dahulu untuk menulis ke stdout.

import sys
if (sys.stdout.encoding is None):            
    print >> sys.stderr, "please set python env PYTHONIOENCODING=UTF-8, example: export PYTHONIOENCODING=UTF-8, when write to stdout." 
    exit(1)


jadi, dengan menggunakan contoh yang sama:

export PYTHONIOENCODING=UTF-8
./test.py > output.txt

akan bekerja

Sérgio
sumber
3
Ini tidak menjawab pertanyaan seperti yang ditanyakan. Melainkan beberapa pemikiran tangensial pada subjek.
ivan_pozdeev
3
  • Bahaya pertama terletak pada reload(sys).

    Ketika Anda memuat ulang modul, Anda sebenarnya mendapatkan dua salinan dari modul di runtime Anda. Modul lama adalah objek Python seperti yang lainnya, dan tetap hidup selama ada referensi untuk itu. Jadi, setengah dari objek akan menunjuk ke modul lama, dan setengah ke yang baru. Ketika Anda membuat beberapa perubahan, Anda tidak akan pernah melihatnya datang ketika beberapa objek acak tidak melihat perubahan:

    (This is IPython shell)
    
    In [1]: import sys
    
    In [2]: sys.stdout
    Out[2]: <colorama.ansitowin32.StreamWrapper at 0x3a2aac8>
    
    In [3]: reload(sys)
    <module 'sys' (built-in)>
    
    In [4]: sys.stdout
    Out[4]: <open file '<stdout>', mode 'w' at 0x00000000022E20C0>
    
    In [11]: import IPython.terminal
    
    In [14]: IPython.terminal.interactiveshell.sys.stdout
    Out[14]: <colorama.ansitowin32.StreamWrapper at 0x3a9aac8>
  • Sekarang, sys.setdefaultencoding()tepat

    Semua yang dipengaruhinya adalah konversi implisitstr<->unicode . Sekarang, utf-8apakah penyandian paling baik di planet ini (kompatibel dengan ASCII dan yang lainnya), konversi sekarang "hanya berfungsi", apa yang mungkin salah?

    Yah, apapun. Dan itu adalah bahayanya.

    • Mungkin ada beberapa kode yang bergantung pada UnicodeErroryang dilemparkan untuk input non-ASCII, atau melakukan transcoding dengan penangan kesalahan, yang sekarang menghasilkan hasil yang tidak terduga. Dan karena semua kode diuji dengan pengaturan default, Anda benar-benar berada di wilayah "tidak didukung" di sini , dan tidak ada yang memberi Anda jaminan tentang bagaimana kode mereka akan berperilaku.
    • Transcoding dapat menghasilkan hasil yang tidak terduga atau tidak dapat digunakan jika tidak semua sistem menggunakan UTF-8 karena Python 2 sebenarnya memiliki beberapa "penyandian string default" yang independen . (Ingat, suatu program harus bekerja untuk pelanggan, pada peralatan pelanggan.)
      • Sekali lagi, hal terburuknya adalah Anda tidak akan pernah tahu itu karena konversi itu implisit - Anda tidak benar-benar tahu kapan dan di mana itu terjadi. (Python Zen, koan 2 ahoy!) Anda tidak akan pernah tahu mengapa (dan jika) kode Anda bekerja pada satu sistem dan merusak yang lain. (Atau lebih baik lagi, bekerja di IDE dan istirahat di konsol.)
ivan_pozdeev
sumber