2012-02-10 9 views
14

Pensavo di sapere tutto sulle codifiche e su Python, ma oggi mi sono imbattuto in uno strano problema: sebbene la console sia impostata sulla codepage 850 - e Python la riporta correttamente - i parametri che ho messo sulla riga di comando sembrano essere codificati nella code page 1252. Se provo a decodificarli con sys.stdin.encoding, ottengo il risultato sbagliato. Se presumo 'cp1252', ignorando ciò che sys.stdout.encoding segnala, funziona.Python, windows console e codifiche (cp 850 vs cp1252)

Mi manca qualcosa o si tratta di un bug in Python? Finestre ? Nota: eseguo Python 2.6.6 su Windows 7 EN, locale impostato su Francese (Svizzera).

Nel programma di test di seguito, controllo che i valori letterali siano interpretati correttamente e che possano essere stampati - questo funziona. Ma tutti i valori che passano sulla linea di comando sembrano essere codificati erroneamente:

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

literal_mb = 'utf-8 literal: üèéÃÂç€ÈÚ' 
literal_u = u'unicode literal: üèéÃÂç€ÈÚ' 
print "Testing literals" 
print literal_mb.decode('utf-8').encode(sys.stdout.encoding,'replace') 
print literal_u.encode(sys.stdout.encoding,'replace') 

print "Testing arguments (stdin/out encodings:",sys.stdin.encoding,"/",sys.stdout.encoding,")" 
for i in range(1,len(sys.argv)): 
    arg = sys.argv[i] 
    print "arg",i,":",arg 
    for ch in arg: 
     print " ",ch,"->",ord(ch), 
     if ord(ch)>=128 and sys.stdin.encoding == 'cp850': 
      print "<-",ch.decode('cp1252').encode(sys.stdout.encoding,'replace'),"[assuming input was actually cp1252 ]" 
     else: 
      print "" 

In una console di nuova creazione, quando si esegue

C:\dev>test-encoding.py abcé€ 

ottengo il seguente output

Testing literals 
utf-8 literal: üèéÃÂç?ÈÚ 
unicode literal: üèéÃÂç?ÈÚ 
Testing arguments (stdin/out encodings: cp850/cp850) 
arg 1 : abcÚÇ 
    a -> 97 
    b -> 98 
    c -> 99 
    Ú -> 233 <- é [assuming input was actually cp1252 ] 
    Ç -> 128 <- ? [assuming input was actually cp1252 ] 

mentre Mi aspetto che il quarto carattere abbia un valore ordinale di anziché 233 (vedere le tabelle codici 850 e 1252).

Note: il valore di 128 per il simbolo dell'euro è un mistero - poiché cp850 non ce l'ha. Altrimenti, il '?' sono attesi: cp850 non può stampare i caratteri e ho usato 'replace' nelle conversioni.

Se cambio la tabella codici della console al 1252 mediante l'emissione di chcp 1252 e eseguire lo stesso comando, I (correttamente) ottengo

Testing literals 
utf-8 literal: üèéÃÂç€ÈÚ 
unicode literal: üèéÃÂç€ÈÚ 
Testing arguments (stdin/out encodings: cp1252/cp1252) 
arg 1 : abcé€ 
    a -> 97 
    b -> 98 
    c -> 99 
    é -> 233 
    € -> 128 

Tutte le idee che mi manca?

Modifica 1: Ho appena provato leggendo sys.stdin. Funziona come previsto: in cp850, digitando 'é' si ottiene un valore ordinale di 130. Quindi il problema riguarda solo la riga di comando. Quindi, la riga di comando è trattata in modo diverso rispetto allo standard input?

Modifica 2: Sembra che avessi le parole chiave sbagliate. Ho trovato un altro argomento molto vicino su SO: Read Unicode characters from command-line arguments in Python 2.x on Windows. Tuttavia, se la riga di comando non è codificata come sys.stdin, e poiché sys.getdefaultencoding() riporta 'ascii', sembra che non ci sia modo di conoscere la sua effettiva codifica. Trovo la risposta usando le estensioni win32 piuttosto hacky.

risposta

21

Risposta me:

In Windows, la codifica utilizzata dalla console (quindi, quello di sys.stdin/out) differisce dalla codifica di varie stringhe OS-disponibile - ottenuta attraverso ad esempio os.getenv(), sys.argv e sicuramente molti altri.

La codifica fornita da sys.getdefaultencoding() è proprio questo: un valore predefinito, scelto dagli sviluppatori Python per far corrispondere la "codifica più ragionevole" che l'interprete utilizza in casi estremi. Ottengo 'ascii' sul mio Python 2.6 e ho provato con il portatile Python 3.1, che produce 'utf-8'. Entrambi non sono ciò che stiamo cercando: sono solo delle considerazioni di fallback per codificare le funzioni di conversione.

Come sembra indicare this page, la codifica utilizzata dalle stringhe fornite dal sistema operativo è regolata dalla pagina di codice attivo (ACP). Dal momento che Python non ha una funzione nativa di recuperarlo, ho dovuto usare ctypes:

from ctypes import cdll 
os_encoding = 'cp' + str(cdll.kernel32.GetACP()) 

Edit: Ma come suggerisce Jacek, c'è in realtà un modo più robusto e Pythonic di farlo (semantics avrebbe bisogno la convalida, ma fino a prova sbagliato, io uso questo)

import locale 
os_encoding = locale.getpreferredencoding() 
# This returns 'cp1252' on my system, yay! 

e poi

u_argv = [x.decode(os_encoding) for x in sys.argv] 
u_env = os.getenv('myvar').decode(os_encoding) 

Sul mio sistema, os_encoding = 'cp1252', quindi funziona. Sono abbastanza sicuro che ciò si interromperebbe su altre piattaforme, quindi sentitevi liberi di modificare e renderlo più generico. Avremmo certamente bisogno di un qualche tipo di tabella di conversione tra l'ACP segnalato da Windows e il nome di codifica Python - qualcosa di meglio che solo anteporre "cp".

Questo è purtroppo un hack, anche se lo trovo un po 'meno invadente di quello suggerito da this ActiveState Code Recipe (collegato alla domanda SO menzionata in Modifica 2 della mia domanda). Il vantaggio che vedo qui è che questo può essere applicato a os.getenv(), e non solo a sys.argv.

+2

Per Linux in genere 'locale.getpreferredencoding()' o, dopo aver usato 'locale.setlocale()' - 'locale.getlocale() [1]' fornisce la codifica corretta per l'accesso alla console e all'ambiente. Sebbene, l'UTF-8 hardcoded sia spesso abbastanza buono per la maggior parte dei sistemi moderni (quindi è il miglior valore di fallback). –

1

Ho provato le soluzioni. Potrebbe ancora avere alcuni problemi di codifica. Dobbiamo usare caratteri di tipo true. Fix:

  1. Run chcp 65001 cmd per modificare la codifica UTF-8.
  2. Change cmd font ad un True-Type uno come Lucida Console che supporta le pagine di codice precedenti prima di 65001

Ecco la mia soluzione completa per l'errore di codifica:

def fixCodePage(): 
    import sys 
    import codecs 
    import ctypes 
    if sys.platform == 'win32': 
     if sys.stdout.encoding != 'cp65001': 
      os.system("echo off") 
      os.system("chcp 65001") # Change active page code 
      sys.stdout.write("\x1b[A") # Removes the output of chcp command 
      sys.stdout.flush() 
     LF_FACESIZE = 32 
     STD_OUTPUT_HANDLE = -11 
     class COORD(ctypes.Structure): 
     _fields_ = [("X", ctypes.c_short), ("Y", ctypes.c_short)] 

     class CONSOLE_FONT_INFOEX(ctypes.Structure): 
      _fields_ = [("cbSize", ctypes.c_ulong), 
      ("nFont", ctypes.c_ulong), 
      ("dwFontSize", COORD), 
      ("FontFamily", ctypes.c_uint), 
      ("FontWeight", ctypes.c_uint), 
      ("FaceName", ctypes.c_wchar * LF_FACESIZE)] 

     font = CONSOLE_FONT_INFOEX() 
     font.cbSize = ctypes.sizeof(CONSOLE_FONT_INFOEX) 
     font.nFont = 12 
     font.dwFontSize.X = 7 
     font.dwFontSize.Y = 12 
     font.FontFamily = 54 
     font.FontWeight = 400 
     font.FaceName = "Lucida Console" 
     handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE) 
     ctypes.windll.kernel32.SetCurrentConsoleFontEx(handle, ctypes.c_long(False), ctypes.pointer(font)) 

Nota: È possibile vedere un cambiamento di carattere durante l'esecuzione del programma.

Problemi correlati