2009-07-20 17 views
5

Tengo un script de Python 2.6 que está amordazando en caracteres especiales, codificados en Latin-1, que estoy recuperando de una base de datos de SQL Server. Me gustaría imprimir estos caracteres, pero estoy algo limitado porque estoy usando una biblioteca que llama a la fábrica unicode, y no sé cómo hacer que Python use un códec que no sea ascii.Latin-1 y la fábrica de Unicode en Python

El script es una herramienta simple para devolver datos de búsqueda desde una base de datos sin tener que ejecutar el SQL directamente en un editor de SQL. Uso la biblioteca PrettyTable 0.5 para mostrar los resultados.

El núcleo del script es este código. Las tuplas que obtengo del cursor contienen datos enteros y de cadena, y no hay datos Unicode. (Que haría uso de adodbapi en lugar de pyodbc, lo que me Unicode, pero me da adodbapi otros problemas.)

x = pyodbc.connect(cxnstring) 
r = x.cursor() 
r.execute(sql) 

t = PrettyTable(columns) 
for rec in r: 
    t.add_row(rec) 
r.close() 
x.close() 

t.set_field_align("ID", 'r') 
t.set_field_align("Name", 'l') 
print t 

Pero la columna de la Name puede contener caracteres que quedan fuera del rango ASCII. A veces voy a conseguir un mensaje de error como este, en la línea 222 de prettytable.pyc, cuando se llega a la llamada t.add_row:

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

Ésta es la línea 222 en prettytable.py. Utiliza unicode, que es la fuente de mis problemas, y no solo en este script, sino en otros scripts de Python que he escrito.

for i in range(0,len(row)): 
    if len(unicode(row[i])) > self.widths[i]: # This is line 222 
     self.widths[i] = len(unicode(row[i])) 

Por favor dígame qué estoy haciendo mal aquí. ¿Cómo puedo hacer que unicode funcione sin hackear prettytable.py o cualquiera de las otras bibliotecas que uso? ¿Hay incluso una manera de hacer esto?

EDITAR: El error no se produce en la instrucción print, sino en la llamada t.add_row.

EDITAR: Con la ayuda de Bastien Léonard, se me ocurrió la siguiente solución. No es una panacea, pero funciona.

x = pyodbc.connect(cxnstring) 
r = x.cursor() 
r.execute(sql) 

t = PrettyTable(columns) 
for rec in r: 
    urec = [s.decode('latin-1') if isinstance(s, str) else s for s in rec] 
    t.add_row(urec) 
r.close() 
x.close() 

t.set_field_align("ID", 'r') 
t.set_field_align("Name", 'l') 
print t.get_string().encode('latin-1') 

Terminé descodificando en el camino y codificando en el camino de salida. ¡Todo esto me da la esperanza de que todo el mundo conecte sus bibliotecas a Python 3.x más temprano que tarde!

Respuesta

5

Añadir esto al comienzo del módulo:

# coding: latin1 

o descodificar la cadena a Unicode mismo.

[Editar]

Ha sido un tiempo desde que jugaba con Unicode, pero espero que este ejemplo le mostrará cómo convertir de Latin1 a Unicode:

>>> s = u'ééé'.encode('latin1') # a string you may get from the database 
>>> s.decode('latin1') 
u'\xe9\xe9\xe9' 

[Editar]

Documentación :
http://docs.python.org/howto/unicode.html
http://docs.python.org/library/codecs.html

+0

He intentado poner la codificación en la parte superior de mis scripts, pero todavía no funciona. Probaré la decodificación explícita, pero espero que haya una solución más general. – eksortso

+1

Probablemente no desee establecer la codificación: latin1. Eso cambia la codificación de la fuente del script, no sus datos. –

+0

@Glenn: Sugerí eso porque pensé que 'print t' podría imprimir cadenas crudas Latin1. –

2

¿Tal vez intente decodificar las cadenas codificadas en latin1 en unicode?

t.add_row((value.decode('latin1') for value in rec)) 
+1

t.add_row ([s.decode ('latin-1') si esinstance (s, str) else s para s in rec]) # Creo que quiso decir esto (o algo así). – eksortso

+0

Probablemente, dependiendo de lo que necesite esa cosa bonita. – liori

0

Después de un rápido vistazo a la fuente de PrettyTable, parece que funciona en objetos Unicode internamente (ver _stringify_row, add_row y add_column, por ejemplo). Como no sabe qué codificación están usando las cadenas de entrada, usa la codificación predeterminada, usually ascii.

Ahora ascii es un subconjunto de latin-1, lo que significa que si está convirtiendo de ascii a latin-1, no debería tener ningún problema. Lo contrario, sin embargo, no es verdad; no todos los caracteres latin-1 se asignan a caracteres ascii. Para demostrar esto:

>>> s = u'\xed\x31\x32\x33' 
>>> print s 
# FAILS: Python calls "s.decode('ascii')", but ascii codec can't decode '\xed' 
>>> print s.decode('ascii') 
# FAILS: Same as above 
>>> print s.decode('latin-1') 
í123 

explícitamente la conversión de las cadenas a Unicode (al igual que lo hizo con el tiempo) fija las cosas, y tiene más sentido, la OMI - lo más probable es saber qué charset sus datos está utilizando, que la autor de PrettyTable :). Por cierto, puede omitir el control de cadenas en su lista de comprensión mediante el reemplazo de s.decode('latin-1') con unicode(s, 'latin-1') ya que todos los objetos se pueden forzar a las cadenas.

Una última cosa: no olvide verificar el conjunto de caracteres de su base de datos y tablas: no desea suponer 'latin-1' en el código, cuando los datos se almacenan como algo diferente ('utf-8'?) en la base de datos. En MySQL, puede usar el comando SHOW CREATE TABLE <table_name> para averiguar qué conjunto de caracteres está usando una tabla, y SHOW CREATE DATABASE <db_name> para hacer lo mismo para una base de datos.

+0

Si está usando objetos Unicode internamente, debería haber una forma de recuperar los objetos Unicode y evitar convertirlos sin sentido de un lado a otro. Siempre que siempre uses objetos Unicode evitarás la mayor parte de este desastre (así es como funciona Python 3). –

+0

Creo que sí: "get_string". – elo80ka

+0

@ elo80ka, los datos realmente se almacenan como Latin-1. Lo verifiqué antes de escribir el guión. Además (en Python 2.6 al menos), las entradas no se pueden forzar usando 'unicode (int_value, 'latin-1')', aunque 'unicode (int_value)' funcione. @Glenn Maynard, imprimir los resultados implica una decodificación, explícitamente definida o no. Tuve que usar 't.get_string(). Encode ('latin-1')' Sí, estoy esperando la adopción generalizada de py3k, de modo que todas las cadenas sean Unicode. Ahorraría muchas molestias. – eksortso