2011-05-21 16 views
9

Deseo escribir un programa python que lea archivos que contengan texto unicode. Estos archivos normalmente están codificados con UTF-8, pero podrían no serlo; si no lo están, la codificación alternativa se declarará explícitamente al comienzo del archivo. Más precisamente, se declarará utilizando exactamente las mismas reglas que Python usa para permitir que el código fuente de Python tenga una codificación explícitamente declarada (como en PEP 0263, vea https://www.python.org/dev/peps/pep-0263/ para más detalles). Para que quede claro, los archivos que se procesan no son en realidad fuente de Python, pero sí declaran sus codificaciones (cuando no están en UTF-8) usando las mismas reglas.Lea un archivo Unicode en python que declare su codificación de la misma manera que la fuente python

Si conoce la codificación de un archivo antes de abrirlo, Python proporciona una manera muy fácil de leer el archivo con decodificación automática: el comando codecs.open; por ejemplo, se podría hacer:

import codecs 
f = codecs.open('unicode.rst', encoding='utf-8') 
for line in f: 
    print repr(line) 

y cada line nos ponemos en el bucle habrá una cadena Unicode. ¿Hay una biblioteca de Python que hace algo similar, pero eligiendo la codificación de acuerdo con las reglas anteriores (que son las reglas de Python 3.0, creo)? (por ejemplo, ¿muestra Python el "archivo de lectura con codificación autodeclarada" que utiliza para leer la fuente del idioma?) De lo contrario, ¿cuál es la forma más fácil de lograr el efecto deseado?

Una idea es abrir el archivo usando el open habitual, leer las dos primeras líneas, interpretarlas como UTF-8, buscar una declaración de codificación usando la expresión regular en el PEP, y si se encuentra una decodificación de inicio todos los siguientes líneas usando la codificación declarada. Para que esto funcione, necesitamos saber que para todas las codificaciones que Python permite en el código fuente de Python, el Python readline normal dividirá el archivo en líneas, es decir, necesitamos saber que para todas las codificaciones Python lo permite en la fuente de Python, la cadena de bytes '\ n' siempre significa realmente nueva línea, y no es parte de una secuencia de varios bytes que codifica otro carácter. (De hecho, también tengo que preocuparme por '\ r \ n' también). ¿Alguien sabe si esto es cierto? Los documentos no fueron muy específicos.

Otra idea es buscar en las fuentes de Python. ¿Alguien sabe en qué parte de la fuente de Python se realiza el procesamiento de codificación de código fuente?

+0

Parece que Python 3.4 responde a su pregunta - vea mi respuesta. –

Respuesta

7

Debería poder hacer rodar su propio decodificador en Python. admitiendo codificaciones de 8 bits que son superconjuntos de ASCII, el siguiente código debería funcionar como está.

Si necesita soporte de 2 bytes encodings like UTF-16, necesita aumentar el patrón para que coincida con \x00c\x00o.. o al revés, según the byte order mark. En primer lugar, generan unos archivos de prueba que anuncian su codificación:

import codecs, sys 
for encoding in ('utf-8', 'cp1252'): 
    out = codecs.open('%s.txt' % encoding, 'w', encoding) 
    out.write('# coding = %s\n' % encoding) 
    out.write(u'\u201chello se\u00f1nor\u201d') 
    out.close() 

luego escribir el decodificador:

import codecs, re 

def open_detect(path): 
    fin = open(path, 'rb') 
    prefix = fin.read(80) 
    encs = re.findall('#\s*coding\s*=\s*([\w\d\-]+)\s+', prefix) 
    encoding = encs[0] if encs else 'utf-8' 
    fin.seek(0) 
    return codecs.EncodedFile(fin, 'utf-8', encoding) 

for path in ('utf-8.txt','cp1252.txt'): 
    fin = open_detect(path) 
    print repr(fin.readlines()) 

de salida:

['# coding = utf-8\n', '\xe2\x80\x9chello se\xc3\xb1nor\xe2\x80\x9d'] 
['# coding = cp1252\n', '\xe2\x80\x9chello se\xc3\xb1nor\xe2\x80\x9d'] 
+0

¡Gracias! Esto es agradable y simple, y funcionará bastante bien en ejemplos prácticos. ¡Es probablemente la mejor solución en la mayoría de los casos! Para mis propios requisitos, la fidelidad exacta a lo que hace Python es importante, así que terminaré haciendo algo más elaborado, como se describe en mi respuesta a continuación, que incluyo por si alguien más que se preocupa por la fidelidad exacta encuentra esta pregunta en StackOverflow . –

2

A partir de dicha PEP (0268):

combinado tokenizer/compilador de Python necesidad de ser actualizado para funcionar de la siguiente manera:

  1. leer el archivo

  2. decodificación en Unicode asumiendo una codificación fija por archivo

  3. convertirlo en una cadena UTF-8 bytes

  4. tokenize el contenido UTF-8

  5. compilarlo, la creación de objetos Unicode partir de los datos Unicode dados y la creación de objetos de cadena a partir de los datos literales Unicode por primera recodificar los datos UTF-8 en datos de cadena de 8 bits utilizando la codificación de archivo dado

De hecho, si marca Parser/tokenizer.c en la fuente de Python encontrará las funciones get_coding_spec y check_coding_spec que son responsables de encontrar esta información en una línea que se está examinando en decoding_fgets.

Parece que esta capacidad no está expuesta en ninguna parte como una API de Python (al menos estas funciones específicas no tienen el prefijo Py, por lo que sus opciones son una biblioteca de terceros o reasignar estas funciones como una extensión. No conozco personalmente ninguna biblioteca de terceros. Tampoco puedo ver esta funcionalidad en la biblioteca estándar.

+0

Gracias por el enlace a las fuentes. Estoy de acuerdo con usted en que parece que la funcionalidad no está expuesta en ninguna parte, que es a lo que le tenía miedo. Creo que planeo analizar el algoritmo en las fuentes de Python y luego recodificarlo en Python ... –

3

I examinó las fuentes de tokenizer.c (gracias a @Ninefingers por sugerir esto en otra respuesta y dar un enlace al navegador de origen). Parece que el algoritmo exacto utilizado por Python es (equivalente a) lo siguiente. En varios lugares describiré el algoritmo como byte de lectura por byte --- obviamente uno quiere hacer algo amortiguado en la práctica, pero es más fácil describirlo de esta manera. La parte inicial del archivo se procesa de la siguiente manera:

  1. Al abrir un archivo, intente reconocer la lista de materiales UTF-8 al comienzo del archivo. Si lo ves, cómelo y toma nota del hecho de que lo viste. No reconozca la marca de orden de bytes UTF-16.
  2. Leer 'una línea' de texto del archivo. 'A line' se define de la siguiente manera: sigue leyendo bytes hasta que ve una de las cadenas '\ n', '\ r' o '\ r \ n' (tratando de hacer coincidir la cadena más larga posible --- esto significa que si ve '\ r' tiene que leer especulativamente el siguiente caracter, y si no es '\ n', vuelva a colocarlo). El terminador está incluido en la línea, como es habitual en la práctica de Python.
  3. Decodifique esta cadena utilizando el códec UTF-8. A menos que haya visto la BOM UTF-8, genere un mensaje de error si ve caracteres no ASCII (es decir, cualquier carácter por encima de 127). (Python 3.0 no genera, por supuesto, un error aquí.) Pase esta línea descodificada al usuario para su procesamiento.
  4. Intenta interpretar esta línea como un comentario que contiene una declaración de codificación, utilizando la expresión regular en PEP 0263. Si encuentra una declaración de codificación, salte a las instrucciones a continuación para 'Encontré una declaración de codificación'.
  5. OK, por lo que no encontró una declaración de codificación. Lea otra línea de la entrada, usando las mismas reglas que en el paso 2 anterior.
  6. Decodifíquelo usando las mismas reglas que el paso 3 y páselo al usuario para su procesamiento.
  7. Intente de nuevo intervenir esta línea como un comentario de declaración de codificación, como en el paso 4. Si encuentra una, salte a las instrucciones a continuación para 'Encontré una declaración de codificación'.
  8. OK. Ahora hemos comprobado las dos primeras líneas. Según PEP 0263, si iba a haber una declaración de codificación, habría estado en las dos primeras líneas, entonces ahora sabemos que no vamos a ver una. Ahora leemos el resto del archivo usando las mismas instrucciones de lectura que usamos para leer las dos primeras líneas: leemos las líneas usando las reglas en el paso 2, decodificamos usando las reglas en el paso 3 (cometemos un error si vemos no- Bytes ASCII a menos que veamos una lista de materiales).

Ahora las reglas para saber qué hacer cuando 'me encontré con una declaración de codificación':

  1. Si antes vimos un BOM UTF-8, compruebe que la declaración de codificación dice 'UTF-8 'de alguna forma. Lanzar un error de lo contrario. ('' utf-8 'de alguna forma' significa cualquier cosa que, después de convertir a minúsculas y convertir guiones bajos a guiones, sea la cadena literal 'utf-8', o algo que empiece por 'utf-8-'.)
  2. Lea el resto del archivo usando el decodificador asociado a la codificación dada en el módulo Python codecs. En particular, la división del resto de los bytes en el archivo en líneas es el trabajo de la nueva codificación.
  3. Una última arruga: cosas universales tipo newline. Las reglas aquí son las siguientes. Si la codificación es algo más que 'utf-8' de alguna forma o 'latin-1' de alguna forma, no hagas ninguna cosa de universal-newline; simplemente pase las líneas exactamente como vienen del decodificador en el módulo codecs. Por otro lado, si la codificación es 'utf-8' en alguna forma o 'latin-1' en alguna forma, las líneas de transformación terminan '\ r' o '\ r \ n' en líneas que terminan '\ n'. ('' utf-8 'de alguna forma' significa lo mismo que antes. '' latin-1 'de alguna forma' significa cualquier cosa que, después de convertir a minúsculas y convertir guiones bajos a guiones, es una de las cadenas literales 'latin-1' , 'iso-latin-1' o 'iso-8859-1', o cualquier cadena que comienza con uno de 'latin-1-', 'iso-latin-1-' o 'iso-8859-1-'.

Por lo que estoy haciendo, la fidelidad a la conducta de Python es importante. Mi plan es rodar una implementación del algoritmo anterior en Python, y use esto. ¡Gracias a todos los que respondieron!

+0

_esto significa que si ve '\ r' tiene que leer especulativamente el siguiente carácter, y si no es '\ n', devolverlo_ ¿Por qué no trata a '\ r' como un carácter de EOL válido? _Decode esta cadena usando el códec UTF-8. A menos que haya visto la BOM UTF-8, genere un mensaje de error si ve caracteres no ASCII (...) _ ¿No debería revertirse? –

+0

@PiotrDobrogost: Tratas '\ r' como un personaje válido de EOL. El punto es que si el carácter que sigue inmediatamente al '\ r' es un' \ n', entonces ese '\ n' debe ser 'comido' como parte de la línea que termina con' \ r' y _not_ tratado como parte del Proxima linea. Si el carácter que sigue inmediatamente a '\ r' es distinto de' \ n', entonces debería dejarse en la secuencia para ser el primer carácter de la siguiente línea. Es por eso que necesita la lógica descrita. –

+0

@PiotrDobrogost: "A menos que haya visto la BOM UTF-8, genere un mensaje de error si ve caracteres no ASCII". Creo que esto es correcto como está escrito. La regla es: si (a) ve un carácter que no es ASCII y (b) no ha visto una BOM UTF-8, entonces debe generar un error. (Si ve un carácter que no es ASCII pero vio una BOM UTF-8, entonces no hace un mensaje de error, y obviamente si todos los caracteres son ASCII no hace un mensaje de error.) Espero que esto aclare las cosas. Por favor, haga más preguntas si todavía no estoy claro y/o parece que estoy realmente equivocado aquí. –

1

A partir de Python 3.4 hay una función que le permite hacer lo que está pidiendo - importlib.util.decode_source

Según documentation:

importlib.util.decode_source(source_bytes)
Decodificar los bytes dado que representa el código fuente y lo devuelve como una cadena con Universal nuevas líneas (según lo requerido por importlib.abc.InspectLoader.get_source()).

Brett cañón talks sobre esta función en su charla Del origen al Código: How compilador Obras de CPython.

+0

Gracias! Es útil que esto haya aparecido. Mejor tarde que nunca :) –

Cuestiones relacionadas