2012-10-06 60 views
5

Estoy usando argparse para leer los argumentos de mi código python. Una de esas entradas es un título de un archivo [title] que puede contener caracteres Unicode. He estado usando 22少女時代22 como una cadena de prueba.Python Unicode Encoding

necesito para escribir el valor de la entrada title a un archivo, pero cuando trato de convertir la cadena en UTF-8 siempre genera un error:

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

He estado buscando alrededor y ver lo que necesito mi cadena está en el formulario u"foo" para llamar al .encode().

Cuando corro type() en mi entrada desde argparse veo:

<type 'str'> 

Estoy buscando para obtener una respuesta de:

<type 'unicode'> 

¿Cómo puedo conseguirlo en la forma correcta?

Idea:

Modificar argparse a tomar en un str pero la almacena como una cadena Unicode u"foo":

parser.add_argument(u'title', metavar='T', type=unicode, help='this will be unicode encoded.') 

Este enfoque no funciona en absoluto. ¿Pensamientos?

Edición 1:

algunos ejemplos de código, donde title es 22少女時代22:

inputs = vars(parser.parse_args()) 
title = inputs["title"] 
print type(title) 
print type(u'foo') 
title = title.encode('utf8') # This line throws the error 
print title 
+0

¿Qué codificación tienen sus datos de entrada? –

+0

@MarkTolonen Ok, editaré mi publicación. – Morrowind789

Respuesta

12

Parece que los datos de entrada se encuentra en SJIS encoding (una codificación legado para los japoneses), que produce el 0x8f byte en la posición 2 de la cadena de bytes:

>>> '22少女時代22'.encode('sjis') 
b'22\x8f\xad\x8f\x97\x8e\x9e\x91\xe322' 

(En Python 3 pronta)

ahora, que supongo que a "convertir la cadena en UTF-8", que utilizó algo así como

title.encode('utf8') 

el problema es que title es en realidad una cadena de bytes que contiene la cadena codificada SJIS. Debido a una falla de diseño en Python 2, las cadenas de bytes pueden ser directamente encode d, y se supone que la cadena de bytes está codificada en ASCII.Así que lo que tenemos es conceptualmente equivalente a

title.decode('ascii').encode('utf8') 

y por supuesto la llamada decode falla.

Usted debe decodificar vez explícitamente de SJIS a una cadena Unicode, antes de la codificación UTF-8:

title.decode('sjis').encode('utf8') 

Como Mark Tolonen señaló, es probable que escribir los caracteres en la consola, y es que la codificación de la consola es una codificación que no es Unicode.

Así que resulta que sys.stdin.encoding es cp932, que es la variante de Microsoft de SJIS. Para ello, utilice

title.decode('cp932').encode('utf8') 

Realmente debe cambiar la codificación de la consola a la UTF-8 estándar, pero no estoy seguro si eso es posible en Windows. Si lo hace, puede omitir el paso de descodificación/codificación y simplemente escribir la cadena de bytes de entrada en el archivo.

+0

El OP puede ejecutar 'import sys; imprima sys.stdin.encoding' en una consola para determinar la codificación de entrada, o simplemente use 'title.decode (sys.stdin.encoding)'. –

+0

En Python 2.7.2 Recibí 'cp932' de' print sys.stdin.encoding; ' – Morrowind789

+0

@Mechanicalsnail Hmm. Llamar a 'print title.decode ('cp932'). Encode ('utf8')' imprime '22 蟆 ウ ウ ウ' '' 22' que es una mutación impar de la cadena de entrada. ¿Pensamientos? – Morrowind789

2

Por lo tanto, esto realmente funciona para mí:

import argparse 
parser = argparse.ArgumentParser() 
parser.add_argument(u'title', metavar='T', type=str, help='this will be unicode encoded.') 
opts = parser.parse_args() 
print opts.title.decode('utf8') 

Mi emulador de terminal (Terminal de OS X .app) usa UTF-8. Si su terminal no está configurada para el funcionamiento de UTF-8, entonces no funcionará (y entonces es un problema terminal, no un problema de Python).

+0

Hmm interesante. Déjame volver a revisar mi entorno. Estoy usando 2.7.2. – Morrowind789

4

La configuración type=unicode es como usar unicode(arg) que se predetermina a la decodificación con ascii en Python 2.X. Si se ejecuta desde la consola, sys.stdin.encoding es la codificación utilizada para la entrada, así que algo como:

inputs = vars(parser.parse_args()) 
title = inputs["title"] 
print type(title) 
print type(u'foo') 
title = title.decode(sys.stdin.encoding) 
print title 

Algo que debe trabajar sin importar la codificación en Windows es la codificación mbcs, que representa la codificación actual utilizado por no Unicode Programas de Windows. Eso parece ser lo que está usando argparse, porque I sys.stdin.encoding es la OEM codificación de la consola que no es siempre la misma que la codificación de Windows. En los Estados Unidos de Windows, cp437 es el OEM codificación consola y cp1252 es la codificación de Windows:

import argparse 
import codecs 
parser = argparse.ArgumentParser() 
parser.add_argument(u'title', metavar='T', type=str, help='this will be unicode encoded.') 
opts = parser.parse_args() 
title = opts.title.decode('mbcs') 
with codecs.open('out.txt','w',encoding='utf-8-sig') as f: 
    f.write(title) 

out.txt debería mostrar la entrada original en el Bloc de notas.

La codificación utf-8-sig escribe la llamada marca de orden de bytes (BET) que le gusta a Windows al comienzo de los archivos UTF-8. utf-8 se puede usar si eso no se desea, pero al Bloc de notas le gusta.

+0

Buen punto que puede usar 'title.decode (sys.stdin.encoding)'. –

+0

@Mechanicalsnail, resulta que no funciona en Windows de Estados Unidos. Agregué una nota sobre por qué. Algunos sistemas de Windows no tienen la misma codificación para programas de consola y sin consola. –

+0

@MarkTolonen Puedo confirmar que esto funciona en mi caja ejecutando Win7 x64. [Ver imagen] (http://i.imgur.com/Wu29q.jpg) – Morrowind789