2010-02-11 14 views
10

Tengo una cadena ëaúlt que quiero obtener la longitud de una manipulación en función de las posiciones de los caracteres, etc. El problema es que el primero ë está siendo contado dos veces, o supongo que ë está en la posición 0 y "está en la posición 1.Python que devuelve la longitud incorrecta de la cadena cuando se usan caracteres especiales

¿Hay alguna manera en Python de tener un carácter como ë representado como 1?

Estoy usando la codificación UTF-8 para el código real y la página web a la que se está enviando.

editar: Solo algunos antecedentes sobre por qué tengo que hacer esto. Estoy trabajando en un proyecto que traduce inglés a Seneca (una forma de lenguaje nativo americano) y "aparece bastante". Algunas reglas de reescritura para ciertas palabras requieren el conocimiento de la posición de la letra (sí mismo y las letras que lo rodean) y otras características, como acentos y otras marcas diacríticas.

+0

Esto es fácil de hacer en Perl con su 'Unicode :: GCString' módulo, cuyos métodos incluyen elementos estándar como 'length',' substr' e 'index', que funcionan en grafemas, no en puntos de código o unidades de código. Incluso incluye un método de "columnas", que es especialmente útil con caracteres amplia/completa de Asia oriental y con la combinación de caracteres y tal. Esto es realmente lo que quieres, y la forma en que estas cosas deberían funcionar. Sin embargo, no he encontrado el equivalente en Python. Perl tiene soporte Unicode más flexible y poderoso que Python, y es excelente en cadenas, por lo que puedes considerarlo. – tchrist

Respuesta

17

UTF-8 es una codificación Unicode que utiliza más de un byte para caracteres especiales. Si no desea la longitud de la cadena codificada, simple decodifíquela y use len() en el objeto unicode (¡y no en el objeto str!).

Éstos son algunos ejemplos:

>>> # creates a str literal (with utf-8 encoding, if this was 
>>> # specified on the beginning of the file): 
>>> len('ë́aúlt') 
9 
>>> # creates a unicode literal (you should generally use this 
>>> # version if you are dealing with special characters): 
>>> len(u'ë́aúlt') 
6 
>>> # the same str literal (written in an encoded notation): 
>>> len('\xc3\xab\xcc\x81a\xc3\xbalt') 
9 
>>> # you can convert any str to an unicode object by decoding() it: 
>>> len('\xc3\xab\xcc\x81a\xc3\xbalt'.decode('utf-8')) 
6 

Por supuesto, también se puede acceder a los caracteres individuales de un objeto unicode igual que lo haría en un objeto str (ambos están heredando de basestring y por lo tanto tienen los mismos métodos):

>>> test = u'ë́aúlt' 
>>> print test[0] 
ë 

Si desarrolla aplicaciones localizadas, por lo general es una buena idea utilizar sólo unicode -Objetos internamente, mediante la decodificación de todas las entradas que recibe. Una vez hecho el trabajo, puede codificar nuevamente el resultado como 'UTF-8'. Si se mantiene a este principio, que nunca se verá su servidor estrellarse debido a cualquier UnicodeDecodeError s internos que podría obtener de otra manera;)

PD: Por favor, tenga en cuenta, que el str y unicode tipo de datos han cambiado significativamente en Python 3. En Python 3 solo hay cadenas Unicode y cadenas de bytes simples que ya no se pueden mezclar. Eso debería ayudar a evitar los errores comunes con el manejo de Unicode ...

Saludos, Christoph

+0

+++ 1 :-) aus .at – Flavius

+0

Creo que esta respuesta resalta el problema: los acentos sobre el 'ea' son diferentes a los de la pregunta :) –

+0

Oh, tienes razón. Creo que perdí al personaje mientras lo copiaba ... lo siento por eso. Lamentablemente, parece que no hay un solo carácter Unicode que pueda representar los acentos. Nunca he visto algo así antes (al menos las diéresis alemanas que conozco se pueden escribir de las dos maneras, como carcter único y combinado) – tux21b

1

Lo mejor que puede hacer es utilizar unicodedata.normalize() para descomponer el personaje y luego filtrar los acentos.

No olvide utilizar unicode y literales Unicode en su código.

5

El problema es que el primer E está siendo contado dos veces, o supongo que e está en la posición 0 y 'está en la posición 1.

Sí. Así es como Unicode define los puntos de código. En general, puede pedir Python para convertir una carta y una marca diacrítica separado 'que combina' como U + 0301 COMBINA acento agudo mediante la normalización de Unicode:

>>> unicodedata.normalize('NFC', u'a\u0301') 
u'\xe1' # single character: á 

Sin embargo, no hay un solo carácter en Unicode para “E con diéresis y acento agudo "porque ningún idioma en el mundo ha usado la letra 'ë'.(La transliteración de Pinyin tiene "u con diéresis y acento agudo", pero no "e"). Por lo tanto, el soporte de fuentes es deficiente; se ve muy mal en muchos casos y es una mancha sucia en mi navegador web.

Determinar dónde están los "puntos editables" en una cadena de puntos de código Unicode es un trabajo complicado que requiere bastante conocimiento del dominio de los idiomas. Es parte del tema del "diseño de texto complejo", un área que también incluye cuestiones como texto bidireccional y creación y ligaduras contextuales de glpyh. Para hacer un diseño de texto complejo necesitarás una biblioteca como Uniscribe en Windows o Pango en general (para la cual hay una interfaz de Python).

Si, por el contrario, solamente desea ignorar por completo todos los caracteres de combinación cuando se hace un recuento, usted puede deshacerse de ellos con bastante facilidad:

def withoutcombining(s): 
    return ''.join(c for c in s if unicodedata.combining(c)==0) 

>>> withoutcombining(u'ë́aúlt') 
'\xeba\xfalt' # ëaúlt 
>>> len(_) 
5 
+0

+1 Esta respuesta funciona. Tenga en cuenta que ë en la sección del código se muestra incorrectamente, pero creo que es solo un problema de fuente/navegador. –

+0

Esto no es una solución generalizada.Necesita una forma de obtener grafemas, no solo puntos de código, y la conversión a NFC simplemente no es lo suficientemente buena para el caso general. Esto es fácil de hacer en Perl, donde la clase Unicode :: GCString admite operaciones básicas como substr(), index() y similares, que funcionan en grafemas. Entonces, combinar personajes no importa, y todo funciona bien. Sin embargo, según tengo entendido, Python no tiene un módulo disponible. – tchrist

-1

qué versión de Python está usando? Python 3.1 no tiene este problema.

>>> print(len("ë́aúlt")) 
6 

Saludos Djoudi

0

Dijiste: Tengo un ëaúlt cadena que quiero obtener la longitud de una manipulación basada en posiciones de carácter y así sucesivamente. El problema es que el primero ë está siendo contado dos veces, o supongo que ë está en la posición 0 y "está en la posición 1.

El primer paso para resolver cualquier problema Unicode es saber exactamente qué contienen sus datos; no adivine En este caso, tu conjetura es correcta; no siempre será

"Exactamente lo que está en sus datos": use la función incorporada repr() (para muchas más cosas además de unicode). Una ventaja útil de mostrar el resultado de repr() en su pregunta es que los respondedores tienen exactamente lo que usted tiene. Tenga en cuenta que su texto se muestra en CUATRO posiciones en lugar de 5 con algunos navegadores/fuentes: la 'e' y sus signos diacríticos y la 'a' se combinan en una posición.

Puede usar la función unicodedata.name() para indicarle qué es cada componente.

He aquí un ejemplo:

# coding: utf8 
import unicodedata 
x = u"ë́aúlt" 
print(repr(x)) 
for c in x: 
    try: 
     name = unicodedata.name(c) 
    except: 
     name = "<no name>" 
    print "U+%04X" % ord(c), repr(c), name 

Resultados:

u'\xeb\u0301a\xfalt' 
U+00EB u'\xeb' LATIN SMALL LETTER E WITH DIAERESIS 
U+0301 u'\u0301' COMBINING ACUTE ACCENT 
U+0061 u'a' LATIN SMALL LETTER A 
U+00FA u'\xfa' LATIN SMALL LETTER U WITH ACUTE 
U+006C u'l' LATIN SMALL LETTER L 
U+0074 u't' LATIN SMALL LETTER T 

Ahora lea la respuesta de @ bobince :-)

Cuestiones relacionadas