2014-12-07 21 views
7

Próbuję znaleźć ogólne rozwiązanie do drukowania ciągów unicode ze skryptu Pythona.Drukuj ciąg znaków unicode w pythoniu niezależnie od środowiska

Wymagania są takie, że musi on działać zarówno w pythonie 2.7, jak i w wersji 3.x, na dowolnej platformie, a także z dowolnymi ustawieniami terminali i zmiennymi środowiskowymi (na przykład LANG = C lub LANG = en_US.UTF-8).

Funkcja drukowania Pythona automatycznie próbuje kodować terminal podczas drukowania, ale jeśli kodowanie terminalu jest ascii, nie powiedzie się.

Na przykład, następujące prace, gdy środowisko "LANG = enUS.UTF-8":

x = u'\xea' 
print(x) 

Ale to nie w Pythonie 2.7, gdy "LANG = C":

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

Poniższe działa niezależnie od ustawienia LANG, ale nie wyświetla poprawnie znaków Unicode, jeśli terminal używał innego kodowania Unicode:

print(x.encode('utf-8')) 

Pożądanym zachowaniem byłoby zawsze pokazywanie w terminalu kodu unicode i pokazywanie kodowania, jeśli terminal nie obsługuje kodu unicode. Na przykład dane wyjściowe będą kodowane w UTF-8, jeśli terminal obsługuje tylko ASCII. Zasadniczo celem jest zrobienie tego samego, co funkcja drukowania Pythona, gdy działa, ale w przypadkach, gdy funkcja drukowania nie działa, użyj domyślnego kodowania.

+0

• Python 3.0 zapewnia alternatywny typ łańcucha dla danych binarnych i obsługuje tekst w zwykłym typie ciągu znaków (kod ASCII jest traktowany jako prosty typ kodu Unicode). • Python 2.6 udostępnia alternatywny typ łańcucha dla tekstu Unicode w formacie innym niż ASCII, a obsługuje zarówno proste dane tekstowe, jak i binarne w zwykłym typie ciągu. , więc o co pytasz? – Kasramvd

+0

* Jakieś * ustawienia terminala i zmienne środowiskowe? W tym nieprawidłowe? : ^) –

+0

Tak, ponieważ dostaję zgłoszenia błędów nawet wtedy, gdy głównym problemem jest środowisko użytkownika, dlatego chciałbym, aby kod był tak solidny, jak to tylko możliwe. – clark800

Odpowiedz

-1

można obsłużyć wyjątek:

def always_print(s): 
    try: 
     print(s) 
    except UnicodeEncodeError: 
     print(s.encode('utf-8')) 
+0

Co, jeśli kodowanie terminali jest czymś zupełnie niezwiązanym z ASCII? Kodowanie jako utf-8 sprawiłoby, że wyglądałoby to jak bełkot. – clark800

+0

Tak, to by było. Dlatego najpierw wypróbujesz zwykłe wywołanie 'print', aby użyć kodowania terminala. – Amber

+0

Co mam na myśli to, że kodowanie terminali jest kodowaniem _non-unicode_ niezwiązanym z ascii, więc nadal nie drukuje bezpośrednio, a następnie wyświetla bełkot z powodu niewłaściwego kodowania. – clark800

8

można obsłużyć przypadek LANG=C mówiąc sys.stdout domyślnych na UTF-8 w przypadkach, gdy w przeciwnym razie domyślnie ASCII.

import sys, codecs 

if sys.stdout.encoding is None or sys.stdout.encoding == 'ANSI_X3.4-1968': 
    utf8_writer = codecs.getwriter('UTF-8') 
    if sys.version_info.major < 3: 
     sys.stdout = utf8_writer(sys.stdout, errors='replace') 
    else: 
     sys.stdout = utf8_writer(sys.stdout.buffer, errors='replace') 

print(u'\N{snowman}') 

Powyższy fragment spełnia wymagania: to działa w Pythonie 2.7 i 3.4, i to nie łamie gdy LANG jest w nie-UTF-8 ustawień takich jak C.

To jest not a new technique, ale jest zaskakująco trudno znaleźć w dokumentacji. Jak przedstawiono powyżej, rzeczywiście respektuje ustawienia inne niż UTF-8, takie jak ISO 8859-*. Domyślnie jest to UTF-8, jeśli Python miałby ustawioną domyślnie wartość ASCII, powodując przerwanie aplikacji.

1

Nie sądzę, że powinieneś spróbować rozwiązać ten problem na poziomie Pythona. Dokumentuj swoje wymagania aplikacji, rejestruj ustawienia regionalne systemów, które możesz włączyć do raportów o błędach i zostaw to.

Jeśli chcesz iść tą trasą, przynajmniej rozróżnij między terminalami i rurami; nie powinieneś nigdy wysyłać danych do terminala, którego terminal nie może jawnie obsłużyć; nie generuj na przykład UTF-8, ponieważ kodowane punkty niedrukowalne> U + 007F mogą zostać zinterpretowane jako kody kontrolne podczas kodowania.

W przypadku potoku domyślnie ustaw wyjście UTF-8 i skonfiguruj go.

Dzięki temu można wykryć, czy jest używany TTY, a następnie obsługiwać kodowanie w oparciu o to; dla terminala ustaw funkcję obsługi błędów (wybierz jedną z replace lub backslashreplace, aby podać znaki zastępcze lub sekwencje specjalne, których znaki nie mogą być obsługiwane). W przypadku potoku użyj konfigurowalnego kodeka.

import codecs 
import os 
import sys 

if os.istty(sys.stdout.fileno): 
    output_encoding = sys.stdout.encoding 
    errors = 'replace' 
else: 
    output_encoding = 'utf-8' # allow override from settings 
    errors = None # perhaps parse from settings, not needed for UTF8 
sys.stdout = codecs.getwriter(output_encoding)(sys.stdout, errors=errors) 
+0

Ustawienie zmiennych środowiskowych jest sprzeczne z wymaganiami pytania: "z dowolnymi ustawieniami terminali i zmiennymi środowiskowymi". Druga opcja i dlaczego nie działa, jest już wspomniana w pytaniu. – clark800

+0

@ clark800: w prawo. Myślę, że to nie jest dobry pomysł. W każdym razie dałem ci opcje, ale zastanów się, że problem leży naprawdę w rękach użytkownika. –

+0

Czasami kodowanie wyjściowe jest niedostępne bez ważnego powodu, np. tylko dlatego, że skrypt jest uruchamiany przez 'cron' lub w locale" C "lub w potoku zamiast na TTY. Gdy kodowanie wyjściowe jest niedostępne, domyślne ustawienie UTF-8 jest całkowicie uzasadnione i jest to, co i tak nowoczesne systemy domyślnie. Z pewnością rozsądniej jest stosować domyślne ustawienia ASCII i zgłaszać wyjątek u niczego niepodejrzewającego użytkownika. "Dokumentowanie wymagań aplikacji" nie pomaga w przypadku skryptu zaprojektowanego do uruchamiania przez rzeczywistych użytkowników końcowych w przeciwieństwie do administratorów systemu lub programistów. – user4815162342

0

można zakodować String się ze specjalnego parametru 'backslashreplace' tak, że znaki są zamieniane na nieprzedstawialne sekwencje. W Pythonie 2 możesz bezpośrednio wydrukować wynik z encode, ale w Pythonie 3 musisz najpierw wrócić do Unicode.

import sys 
encoding = sys.stdout.encoding 
print(s.encode(encoding, 'backslashreplace').decode(encoding)) 

Jeśli sys.stdout.encoding nie dostarcza wartość, że terminal może obsłużyć, to oddzielny problem, który trzeba sobie poradzić.