Ustawienie poprawnego kodowania podczas instalacji potokowej w Pythonie

343

Podczas przesyłania potokowego danych wyjściowych programu w języku Python interpreter języka Python ma problem z zakodowaniem i ustawia go na Brak. Oznacza to taki program:

# -*- coding: utf-8 -*-
print u"åäö"

będzie działał dobrze, gdy będzie działał normalnie, ale zawiedzie z:

UnicodeEncodeError: Kodek „ascii” nie może zakodować znaku u '\ xa0' w pozycji 0: porządek poza zakresem (128)

gdy jest używany w sekwencji rur.

Jaki jest najlepszy sposób, aby to zadziałało podczas instalacji rurowej? Czy mogę po prostu powiedzieć mu, aby używało dowolnego kodowania powłoki / systemu plików / czegokolwiek używa?

Sugerowane do tej pory sugestie to bezpośrednia modyfikacja pliku site.py lub zakodowanie domyślnego kodowania za pomocą tego hacka:

# -*- coding: utf-8 -*-
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
print u"åäö"

Czy istnieje lepszy sposób na wykonanie instalacji rurowej?

Joakim Lundborg
źródło
1
Zobacz także stackoverflow.com/questions/4545661/…
ShreevatsaR
2
Jeśli masz ten problem w systemie Windows, możesz również uruchomić chcp 65001przed uruchomieniem skryptu. Może to powodować problemy, ale często pomaga i nie wymaga dużo pisania (mniej niż set PYTHONIOENCODING=utf_8).
Tomasz Gandor,
polecenie chcp to nie to samo, co ustawienie PYTHONIOENCODING. Myślę, że chcp to tylko konfiguracja samego terminala i nie ma nic wspólnego z zapisywaniem do pliku (co robisz, gdy instalujesz stdout). Postaraj setx PYTHONENCODING utf-8się, aby było trwałe, jeśli chcesz oszczędzać na pisaniu.
ejm
Napotkałem

Odpowiedzi:

162

Kod działa, gdy jest uruchamiany w skrypcie, ponieważ Python koduje dane wyjściowe do dowolnego kodowania używanego przez aplikację terminala. Jeśli korzystasz z pipingu, musisz go sam zakodować.

Ogólna zasada brzmi: zawsze używaj wewnętrznego Unicode. Dekoduj otrzymane wiadomości i koduj to, co wysyłasz.

# -*- coding: utf-8 -*-
print u"åäö".encode('utf-8')

Innym przykładem dydaktycznym jest program w języku Python do konwersji między ISO-8859-1 i UTF-8, dzięki czemu wszystko jest wielkie między nimi.

import sys
for line in sys.stdin:
    # Decode what you receive:
    line = line.decode('iso8859-1')

    # Work with Unicode internally:
    line = line.upper()

    # Encode what you send:
    line = line.encode('utf-8')
    sys.stdout.write(line)

Ustawienie domyślnego kodowania systemu jest złym pomysłem, ponieważ niektóre używane moduły i biblioteki mogą polegać na tym, że jest to ASCII. Nie rób tego

nosklo
źródło
11
Problem polega na tym, że użytkownik nie chce jawnie określać kodowania. On chce po prostu użyć Unicode dla IO. A kodowanie, którego używa, powinno być kodowaniem określonym w ustawieniach regionalnych, a nie w ustawieniach aplikacji terminalowej. AFAIK, w tym przypadku Python 3 używa kodowania ustawień regionalnych . Zmiana sys.stdoutwydaje się przyjemniejszym sposobem.
Andrey Vlasovskikh
4
Kodowanie / dekodowanie każdego łańcucha w sposób ścisły powoduje błędy, gdy brakuje połączenia kodującego lub dekodującego lub gdzieś jest dodawane do wielu innych elementów. Kodowanie wyjściowe można ustawić, gdy wyjściem jest terminal, więc można je ustawić, gdy wyjściem nie jest terminal. Istnieje nawet standardowe środowisko LC_CTYPE, aby je określić. Jest w pythonie, ale nie szanuje tego.
Rasmus Kaj
65
Ta odpowiedź jest zła. Państwo powinno nie być ręcznie konwersji na każdym wejściu i wyjściu z programu; to kruche i całkowicie nie do utrzymania.
Glenn Maynard
29
@Glenn Maynard: więc jaka jest właściwa odpowiedź IYO? Bardziej pomocne jest powiedzenie nam niż tylko powiedzenie „Ta odpowiedź jest zła”
smci
14
@smci: odpowiedź brzmi: nie modyfikuj skryptu, ustaw, PYTHONIOENCODINGjeśli przekierowujesz standardowe wyjście skryptu w Pythonie 2.
jfs
168

Po pierwsze, w odniesieniu do tego rozwiązania:

# -*- coding: utf-8 -*-
print u"åäö".encode('utf-8')

Nie jest praktyczne jednoznaczne drukowanie za pomocą danego kodowania za każdym razem. To byłoby powtarzalne i podatne na błędy.

Lepszym rozwiązaniem jest zmiana sys.stdoutna początku programu, aby zakodować za pomocą wybranego kodowania. Oto jedno rozwiązanie, które znalazłem w Pythonie: Jak wybiera się sys.stdout.encoding? , w szczególności komentarz „toka”:

import sys
import codecs
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
Craig McQueen
źródło
7
niestety zmiana sys.stdout, aby akceptowała tylko Unicode, psuje wiele bibliotek, które oczekują, że zaakceptuje zakodowane testy.
nosklo
6
nosklo: Jak więc może działać niezawodnie i automatycznie, gdy wyjściem jest terminal?
Rasmus Kaj
3
@Rasmus Kaj: po prostu zdefiniuj własną funkcję drukowania w trybie Unicode i używaj jej za każdym razem, gdy chcesz wydrukować Unicode: def myprint(unicodeobj): print unicodeobj.encode('utf-8')- automatycznie wykrywasz kodowanie terminala poprzez inspekcję sys.stdout.encoding, ale powinieneś wziąć pod uwagę przypadek, w którym się znajduje None(tj. Gdy przekierowujesz wyjście do pliku) więc i tak potrzebujesz osobnej funkcji.
nosklo
3
@nosklo: To nie powoduje, że sys.stdout akceptuje tylko Unicode. Możesz przekazać zarówno str, jak i Unicode do StreamWriter.
Glenn Maynard
9
Zakładam, że ta odpowiedź była przeznaczona dla python2. Uważaj na ten kod, który ma obsługiwać zarówno python2, jak i python3 . Dla mnie to psuje rzeczy, kiedy działa pod python3.
wim
130

Możesz spróbować zmienić zmienną środowiskową „PYTHONIOENCODING” na „utf_8”. Napisałem stronę na temat mojej próby z tym problemem .

Tl; dr na blogu:

import sys, locale, os
print(sys.stdout.encoding)
print(sys.stdout.isatty())
print(locale.getpreferredencoding())
print(sys.getfilesystemencoding())
print(os.environ["PYTHONIOENCODING"])
print(chr(246), chr(9786), chr(9787))

daje Ci

utf_8
False
ANSI_X3.4-1968
ascii
utf_8
ö ☺ ☻
daveagp
źródło
2
Zmiana sys.stdout.encoding może nie działać, ale zmieniając sys.stdout działa: sys.stdout = codecs.getwriter(encoding)(sys.stdout). Można to zrobić z poziomu programu python, aby użytkownik nie był zmuszony do ustawiania zmiennej env.
blueFast
7
@ jeckyll2hide: PYTHONIOENCODINGdziała. Sposób interpretowania bajtów jako tekstu jest określany przez środowisko użytkownika . Twój skrypt nie powinien zakładać i dyktować środowisku użytkownika, jakiego kodowania znaków użyć. Jeśli Python nie pobierze ustawień automatycznie, PYTHONIOENCODINGmożna ustawić skrypt. Nie powinieneś go potrzebować, chyba że dane wyjściowe zostaną przekierowane do pliku / potoku.
jfs
8
+1. Szczerze mówiąc, myślę, że to błąd w Pythonie. Kiedy przekierowuję dane wyjściowe, chcę tych samych bajtów, które byłyby na terminalu, ale w pliku. Może to nie jest dla wszystkich, ale jest to dobry domyślny. Mocne zawieszanie się bez wyjaśnienia trywialnej operacji, która zwykle „po prostu działa”, jest złym ustawieniem domyślnym.
SnakE,
@SnakE: jedynym sposobem, który mogę zracjonalizować, dlaczego implementacja Pythona celowo wymusiłaby żelazny i stały wybór kodowania na standardowym wyjściu w czasie uruchamiania, może być w celu zapobieżenia pojawianiu się później źle zakodowanych rzeczy. Lub zmiana jest tylko nie zaimplementowaną funkcją, w którym to przypadku umożliwienie użytkownikowi późniejszej zmiany byłoby rozsądnym żądaniem funkcji Pythona.
daveagp
2
@daveagp Chodzi mi o to, że zachowanie mojego programu nie powinno zależeć od tego, czy jest przekierowane czy nie --- chyba że naprawdę tego chcę, w którym to przypadku sam go wdrażam. Python zachowuje się wbrew mojemu doświadczeniu z innymi narzędziami konsolowymi. To narusza zasadę najmniejszego zaskoczenia. Uważam to za wadę projektową, chyba że istnieją bardzo mocne uzasadnienia.
SnakE,
62
export PYTHONIOENCODING=utf-8

wykonuj zadanie, ale nie możesz ustawić go na samym pythonie ...

co możemy zrobić, to sprawdzić, czy nie jest ustawiony i powiedzieć użytkownikowi, aby ustawił go przed wywołaniem skryptu za pomocą:

if __name__ == '__main__':
    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)

Zaktualizuj, aby odpowiedzieć na komentarz: problem istnieje po prostu podczas instalacji na stdout. Testowałem w Fedorze 25 Python 2.7.13

python --version
Python 2.7.13

kot b.py

#!/usr/bin/env python
#-*- coding: utf-8 -*-
import sys

print sys.stdout.encoding

działa ./b.py

UTF-8

uruchomiony ./b.py | mniej

None
Sérgio
źródło
2
Ta kontrola nie działa w Pythonie 2.7.13. sys.stdout.encodingjest ustawiany automatycznie na podstawie LC_CTYPEwartości ustawień regionalnych.
amfetamachina
1
mail.python.org/pipermail/python-list/2011-June/605938.html przykład nadal działa, tzn. gdy używasz ./a.py> out.txt sys.stdout.encoding to None
Sérgio
Miałem podobny problem ze skryptem synchronizacji z Backblaze B2 i eksport PYTHONIOENCODING = utf-8 rozwiązał mój problem. Python 2.7 w wersji Debian Stretch.
0x3333
5

Miałem podobny problem w zeszłym tygodniu . Łatwo było to naprawić w moim IDE (PyCharm).

Oto moja poprawka:

Zaczynając od paska menu PyCharm: Plik -> Ustawienia ... -> Edytor -> Kodowanie plików, następnie ustaw: „Kodowanie IDE”, „Kodowanie projektu” i „Domyślne kodowanie plików właściwości” WSZYSTKIE na UTF-8 i teraz działa jak urok.

Mam nadzieję że to pomoże!

CLaFarge
źródło
4

Argumentowana zdezynfekowana wersja odpowiedzi Craiga McQueena.

import sys, codecs
class EncodedOut:
    def __init__(self, enc):
        self.enc = enc
        self.stdout = sys.stdout
    def __enter__(self):
        if sys.stdout.encoding is None:
            w = codecs.getwriter(self.enc)
            sys.stdout = w(sys.stdout)
    def __exit__(self, exc_ty, exc_val, tb):
        sys.stdout = self.stdout

Stosowanie:

with EncodedOut('utf-8'):
    print u'ÅÄÖåäö'
Tompa
źródło
2

Mógłbym to „zautomatyzować”, dzwoniąc do:

def __fix_io_encoding(last_resort_default='UTF-8'):
  import sys
  if [x for x in (sys.stdin,sys.stdout,sys.stderr) if x.encoding is None] :
      import os
      defEnc = None
      if defEnc is None :
        try:
          import locale
          defEnc = locale.getpreferredencoding()
        except: pass
      if defEnc is None :
        try: defEnc = sys.getfilesystemencoding()
        except: pass
      if defEnc is None :
        try: defEnc = sys.stdin.encoding
        except: pass
      if defEnc is None :
        defEnc = last_resort_default
      os.environ['PYTHONIOENCODING'] = os.environ.get("PYTHONIOENCODING",defEnc)
      os.execvpe(sys.argv[0],sys.argv,os.environ)
__fix_io_encoding() ; del __fix_io_encoding

Tak, możliwe jest uzyskanie nieskończonej pętli tutaj, jeśli to „setenv” zawiedzie.

jno
źródło
1
ciekawe, ale fajka nie wydaje się być z tego zadowolona
n611x007,
2

Pomyślałem, że wspomnę o czymś, z czym musiałem długo eksperymentować, zanim w końcu zrozumiałem, co się dzieje. Może to być tak oczywiste dla wszystkich tutaj, że nie zadali sobie trudu, aby o tym wspomnieć. Ale pomogłoby mi, gdyby tak zrobili, więc na tej zasadzie ...!

Uwaga: Używam Jython specjalnie, wersja 2.7, więc być może nie dotyczy to CPython ...

NB2: pierwsze dwie linie mojego pliku .py to:

# -*- coding: utf-8 -*-
from __future__ import print_function

Mechanizm ciągów znaków „%” (AKA „operator interpolacji”) powoduje również DODATKOWE problemy ... Jeśli domyślnym kodowaniem „środowiska” jest ASCII i spróbujesz zrobić coś takiego

print( "bonjour, %s" % "fréd" )  # Call this "print A"

Nie będziesz mieć problemów z uruchomieniem w Eclipse ... W Windows CLI (okno DOS) przekonasz się, że kodowanie to strona kodowa 850 (mój system operacyjny Windows 7) lub coś podobnego, co może obsłużyć przynajmniej europejskie znaki akcentowane, więc będę pracować.

print( u"bonjour, %s" % "fréd" ) # Call this "print B"

będzie również działać.

Jeśli, OTOH, przekierujesz do pliku z CLI, standardowym kodowaniem będzie None, który będzie domyślnie ustawiony na ASCII (w moim systemie operacyjnym i tak), który nie będzie w stanie obsłużyć żadnego z powyższych wydruków ... (przerażające kodowanie błąd).

Więc możesz pomyśleć o przekierowaniu stdout za pomocą

sys.stdout = codecs.getwriter('utf8')(sys.stdout)

i spróbuj uruchomić w potoku CLI do pliku ... Bardzo dziwnie, wydruk A powyżej zadziała ... Ale wydruk B powyżej spowoduje błąd kodowania! Następujące działania będą jednak działać poprawnie:

print( u"bonjour, " + "fréd" ) # Call this "print C"

Doszedłem do wniosku (tymczasowo), że jeśli ciąg znaków, który jest określony jako ciąg Unicode przy użyciu prefiksu „u”, zostanie przesłany do mechanizmu obsługi%, wydaje się, że wiąże się to z użyciem domyślnego kodowania środowiska, niezależnie od czy ustawiłeś stdout na przekierowywanie!

Sposób, w jaki ludzie sobie z tym radzą, jest kwestią wyboru. Z zadowoleniem powitałbym eksperta od Unicode, który powiedziałby, dlaczego tak się dzieje, czy w jakiś sposób popełniłem błąd, jakie jest preferowane rozwiązanie tego problemu, czy dotyczy to również CPython , czy dzieje się to w Pythonie 3 itp. Itp.

gryzoń mike
źródło
To nie jest dziwne, ponieważ "fréd"jest to sekwencja bajtów, a nie ciąg Unicode, więccodecs.getwriter opakowanie pozostawi to w spokoju. Potrzebujesz wiodącego ulub from __future__ import unicode_literals.
Matthias Urlichs
@MatthiasUrlichs OK ... dzięki ... Ale właśnie znalazłem kodowanie jednego z najbardziej irytujących aspektów IT. Skąd czerpiesz zrozumienie? Na przykład właśnie zamieściłem tutaj inne pytanie dotyczące kodowania: stackoverflow.com/questions/44483067/… : chodzi o Javę, Eclipse, Cygwin i Gradle. Jeśli Twoja wiedza sięga tak daleko, pomóż ... przede wszystkim chciałbym wiedzieć, gdzie dowiedzieć się więcej!
Mike Rodent
1

Natrafiłem na ten problem w starszej aplikacji i trudno było ustalić, gdzie wydrukowano. Pomogłem sobie z tym hackiem:

# encoding_utf8.py
import codecs
import builtins


def print_utf8(text, **kwargs):
    print(str(text).encode('utf-8'), **kwargs)


def print_utf8(fn):
    def print_fn(*args, **kwargs):
        return fn(str(*args).encode('utf-8'), **kwargs)
    return print_fn


builtins.print = print_utf8(print)

Oprócz mojego skryptu test.py:

import encoding_utf8
string = 'Axwell Λ Ingrosso'
print(string)

Zauważ, że to zmienia WSZYSTKIE wywołania drukowania, aby używały kodowania, więc Twoja konsola wydrukuje to:

$ python test.py
b'Axwell \xce\x9b Ingrosso'
procesor
źródło
1

W systemie Windows ten problem występował bardzo często podczas uruchamiania kodu w języku Python z edytora (takiego jak Sublime Text), ale nie w przypadku uruchamiania go z wiersza polecenia.

W takim przypadku sprawdź parametry edytora. W przypadku SublimeText Python.sublime-buildrozwiązało to:

{
  "cmd": ["python", "-u", "$file"],
  "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
  "selector": "source.python",
  "encoding": "utf8",
  "env": {"PYTHONIOENCODING": "utf-8", "LANG": "en_US.UTF-8"}
}
Basj
źródło