Dlaczego NIE powinniśmy używać sys.setdefaultencoding („utf-8”) w skrypcie py?

166

Widziałem kilka skryptów py, które używają tego na początku skryptu. W jakich przypadkach należy go używać?

import sys
reload(sys)
sys.setdefaultencoding("utf-8")
mlzboy
źródło
2
jest problem z używaniem tego w ipythonie, czas% przestaje działać github.com/ipython/ipython/issues/8071
seanv507
3
@ seanv507, przeczytaj odpowiedzi - używanie ich jest poważnie odradzane
Alastair McCormack
2
Dlaczego nie jest to dokładny duplikat Dangers of sys.setdefaultencoding („utf-8”) ? Chociaż to (2010) pytanie poprzedza to (2015)? Ale to pytanie ma też dobre odpowiedzi. Co robić? Poza tym, aby było jasne, to pytanie ma sens tylko w Pythonie 2, a nie 3, ale nigdzie nie zostało to oznaczone ani wspomniane.
smci
warto przeczytać, zanim zagłębimy się
ccpizza

Odpowiedzi:

141

Zgodnie z dokumentacją: Pozwala to na przełączenie się z domyślnego ASCII na inne kodowanie, takie jak UTF-8, którego środowisko wykonawcze Pythona będzie używać, gdy będzie musiało zdekodować bufor ciągów na Unicode.

Ta funkcja jest dostępna tylko podczas uruchamiania Pythona, kiedy Python skanuje środowisko. Należy go wywołać w module ogólnosystemowym. sitecustomize.pyPo ocenie tego modułu setdefaultencoding()funkcja jest usuwana z sysmodułu.

Jedynym sposobem faktycznego użycia tego atrybutu jest hack przeładowania, który przywraca atrybut.

Ponadto, używanie sys.setdefaultencoding()zawsze było odradzane i stało się opcją w py3k. Kodowanie py3k jest na stałe połączone z „utf-8”, a jego zmiana powoduje błąd.

Proponuję kilka wskazówek do czytania:

pyfunc
źródło
6
Świetna rzecz, chociaż jest tu trochę śmierci z powodu zbyt dużej ilości informacji. Najwięcej nauczyłem się skupiając się na tym artykule: blog.notdot.net/2010/07/Getting-unicode-right-in-Python
mbb
3
Chciałbym dodać, że domyślne kodowanie jest również używane do kodowania (podczas pisania do, sys.stdoutgdy ma Nonekodowanie, na przykład podczas przekierowywania wyjścia programu w Pythonie).
Eric O Lebigot
14
+1 za „używanie sys.setdefaultencoding()zawsze było odradzane”
jfs,
7
„Hard-wired to utf-8” nie jest prawdą, nie jest podłączony na stałe i nie zawsze UTF-8. LC_ALL=en_US.UTF-8 python3 -c 'import sys; print(sys.stdout.encoding)'daje, UTF-8ale LC_ALL=C python3 -c 'import sys; print(sys.stdout.encoding)'daje ANSI_X3.4-1968(a może coś innego)
Tino
7
@Tino, kodowanie konsoli jest niezależne od kodowania domyślnego.
Alastair McCormack
59

tl; dr

Odpowiedź brzmi: NIGDY ! (chyba że naprawdę wiesz, co robisz)

9/10 razy rozwiązanie można rozwiązać przy odpowiednim zrozumieniu kodowania / dekodowania.

1/10 osób ma nieprawidłowo zdefiniowaną lokalizację lub środowisko i musi ustawić:

PYTHONIOENCODING="UTF-8"  

w ich środowisku, aby naprawić problemy z drukowaniem konsoli.

Co to robi?

sys.setdefaultencoding("utf-8")(przekreślony, aby uniknąć ponownego użycia) zmienia domyślne kodowanie / dekodowanie używane zawsze, gdy Python 2.x musi przekonwertować Unicode () na str () (i odwrotnie), a kodowanie nie jest podane. To znaczy:

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

W Pythonie 2.x domyślne kodowanie jest ustawione na ASCII, a powyższe przykłady zakończą się niepowodzeniem z:

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

(Moja konsola jest skonfigurowana jako UTF-8, więc "€" = '\xe2\x82\xac'wyjątek włączony \xe2)

lub

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

sys.setdefaultencoding("utf-8")pozwoli im działać dla mnie , ale niekoniecznie będzie działać dla osób, które nie używają UTF-8. Domyślne ASCII zapewnia, że ​​założenia kodowania nie są wprowadzane do kodu

Konsola

sys.setdefaultencoding("utf-8")ma również efekt uboczny polegający na poprawianiu sys.stdout.encoding, używanym podczas drukowania znaków na konsoli. Python używa ustawień regionalnych użytkownika (Linux / OS X / Un * x) lub strony kodowej (Windows), aby to ustawić. Czasami ustawienia regionalne użytkownika są zepsute i wymagają tylko PYTHONIOENCODINGnaprawy kodowania konsoli .

Przykład:

$ 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"
€

Co jest takiego złego w sys.setdefaultencoding („utf-8”) ?

Od 16 lat ludzie rozwijają się przeciwko Pythonowi 2.x, wiedząc, że domyślnym kodowaniem jest ASCII. UnicodeErrormetody obsługi wyjątków zostały napisane w celu obsługi konwersji ciągów na Unicode w ciągach, które zawierają inne niż ASCII.

Od 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"))

Przed ustawieniem domyślnego kodowania ten kod nie byłby w stanie zdekodować „Å” w kodowaniu ascii, a następnie wprowadziłby procedurę obsługi wyjątku, aby odgadnąć kodowanie i poprawnie przekształcić go w Unicode. Drukowanie: Angstrom (Å®) prowadzi Twoją firmę. Po ustawieniu domyślnego kodowania na utf-8, kod stwierdzi, że bajt_string można zinterpretować jako utf-8, więc zmieni dane i zwróci to: Angstrom (Ů) prowadzi Twoją firmę.

Zmiana tego, co powinno być stałą, będzie miała dramatyczny wpływ na moduły, na których polegasz. Lepiej jest po prostu naprawić dane przychodzące i wychodzące z kodu.

Przykładowy problem

Chociaż ustawienie domyślnego kodowania na UTF-8 nie jest główną przyczyną w poniższym przykładzie, pokazuje, jak problemy są maskowane i jak, gdy zmienia się kodowanie wejściowe, kod psuje się w nieoczywisty sposób: UnicodeDecodeError: kodek 'utf8' może 't dekoduj bajt 0x80 na pozycji 3131: nieprawidłowy bajt początkowy

Alastair McCormack
źródło
2
Chociaż są w sys.setdefaultencoding("utf-8")nim niespodzianki , dobrze jest sprawić, by kod zachowywał się bardziej jak Python 3. Teraz jest rok 2017. Nawet jeśli napisałeś odpowiedź w 2015 roku, myślę, że już lepiej było patrzeć w przyszłość niż w przeszłość. To było dla mnie najprostsze rozwiązanie, gdy zauważyłem, że mój kod zachowuje się inaczej w Pythonie 2 w zależności od tego, czy dane wyjściowe są przekierowywane (bardzo nieprzyjemny problem dla Pythona 2). Nie trzeba dodawać, że już mam # coding: utf-8i nie potrzebuję żadnych obejść dla Pythona 3 (właściwie muszę zamaskować setdefaultencodingsprawdzanie wersji przy użyciu).
Yongwei Wu
To świetnie i działa dla Ciebie, ale sys.setdefaultencoding("utf-8")nie sprawia, że ​​kod Py 2.x jest zgodny z Pythonem 3. Nie naprawia też zewnętrznych modułów, które zakładają, że domyślnym kodowaniem jest ASCII. Dostosowanie kodu do Pythona 3 jest bardzo proste i nie wymaga tego okropnego hackowania. Na przykład, dlaczego powoduje to bardzo realne problemy, zobacz moje doświadczenia z Amazon mieszające się z tym założeniem: stackoverflow.com/questions/39465220/ ...
Alastair McCormack
1
@AlastairMcCormack you rock, Moja witryna istnieje od miesięcy i nie mogłem się dowiedzieć, co robić. Wreszcie PYTHONIOENCODING="UTF-8"pomogło mojemu środowisku Python2.7 Django-1.11. Dzięki.
sam
Wiem, że skopiowałeś przykład, ale mogę znaleźć pakiet detect_encoding.
dlamblin
@dlamblin Przykładowy kod służy do udowodnienia cytatu i nie powinien być używany w kodzie. Wyobraź sobie, że detect_encodingjest to metoda, która mogłaby wykryć kodowanie łańcucha na podstawie wskazówek językowych.
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)

w powłoce działa, wysyłanie do sdtout nie, więc jest to jedno obejście, aby pisać na standardowe wyjście.

Zrobiłem inne podejście, które nie jest uruchamiane, jeśli sys.stdout.encoding nie jest zdefiniowane, lub innymi słowy, musisz najpierw wyeksportować PYTHONIOENCODING = UTF-8, aby zapisać na standardowe wyjście.

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)


więc, używając tego samego przykładu:

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

będzie działać

Sérgio
źródło
3
To nie odpowiada na zadane pytanie. Raczej styczne przemyślenia na ten temat.
ivan_pozdeev
3
  • Pierwsze niebezpieczeństwo tkwi w reload(sys).

    Kiedy przeładowujesz moduł, w rzeczywistości otrzymujesz dwie kopie modułu w swoim środowisku wykonawczym. Stary moduł jest obiektem Pythona, jak wszystko inne, i pozostaje żywy, dopóki istnieją do niego odniesienia. Tak więc połowa obiektów będzie wskazywała na stary moduł, a połowa na nowy. Kiedy wprowadzisz jakąś zmianę, nigdy nie zobaczysz, że nadchodzi, gdy jakiś losowy obiekt nie zauważy zmiany:

    (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>
  • Teraz sys.setdefaultencoding()dobrze

    Wszystko, na co ma wpływ, to niejawna konwersjastr<->unicode . Czy utf-8jest to najbardziej rozsądne kodowanie na tej planecie (kompatybilne wstecz z ASCII i wszystkimi innymi), konwersja „po prostu działa”, co może się nie udać?

    Cóż, wszystko. I to jest niebezpieczeństwo.

    • Może istnieć kod, który opiera się na UnicodeErrorwyrzucaniu dla danych wejściowych innych niż ASCII lub transkodowanie z obsługą błędów, co teraz daje nieoczekiwany wynik. A ponieważ cały kod jest testowany z ustawieniem domyślnym, jesteś tutaj ściśle na „nieobsługiwanym” terytorium i nikt nie daje Ci gwarancji, jak będzie się zachowywał jego kod.
    • Transkodowanie może dać nieoczekiwane lub bezużyteczne wyniki, jeśli nie wszystko w systemie używa UTF-8, ponieważ Python 2 w rzeczywistości ma wiele niezależnych „domyślnych kodowań ciągów” . (Pamiętaj, program musi działać dla klienta, na jego wyposażeniu).
      • Ponownie, najgorsze jest to, że nigdy się tego nie dowiesz, ponieważ konwersja jest niejawna - tak naprawdę nie wiesz, kiedy i gdzie się dzieje. (Python Zen, koan 2 ahoy!) Nigdy nie dowiesz się, dlaczego (i czy) twój kod działa w jednym systemie, a psuje się w innym. (Lub jeszcze lepiej, działa w IDE i psuje się w konsoli).
ivan_pozdeev
źródło