¿Cómo obtengo la longitud “visible” de una cadena Unicode combinada en Python?

Si tengo una cadena Python Unicode que contiene caracteres combinados, len notifica un valor que no corresponde al número de caracteres “vistos”.

Por ejemplo, si tengo una cadena con la combinación de u'A\u0332\u0305BC' y subrayados como u'A\u0332\u0305BC' , len(u'A\u0332\u0305BC') informa 5; pero la cadena mostrada tiene solo 3 caracteres.

¿Cómo obtengo lo “visible”, es decir, la cantidad de posiciones distintas ocupadas por la cadena que ve el usuario, la longitud de una cadena Unicode que contiene glifos combinados en Python?

El módulo unicodedata tiene una función de combining que se puede usar para determinar si un solo carácter es un carácter de combinación. Si devuelve 0 puede contar el carácter como no combinado.

 import unicodedata len(u''.join(ch for ch in u'A\u0332\u0305BC' if unicodedata.combining(ch) == 0)) 

o, un poco más simple:

 sum(1 for ch in u'A\u0332\u0305BC' if unicodedata.combining(ch) == 0) 

Si tiene un sabor de expresiones regulares que admita el grafema coincidente, puede usar \X

Manifestación

Mientras que el módulo de re de Python predeterminado no es compatible con \X , el módulo de expresiones regulares de Matthew Barnett:

 >>> len(regex.findall(r'\X', u'A\u0332\u0305BC')) 3 

En Python 2, necesitas usar u en el patrón:

 >>> regex.findall(u'\\X', u'A\u0332\u0305BC') [u'A\u0332\u0305', u'B', u'C'] >>> len(regex.findall(u'\\X', u'A\u0332\u0305BC')) 3 

La combinación de caracteres no son los únicos caracteres de ancho cero:

 >>> sum(1 for ch in u'\u200c' if unicodedata.combining(ch) == 0) 1 

( "\u200c" o "‌" es de no ancho de ancho cero; es un carácter que no se imprime).

En este caso el módulo regex tampoco funciona:

 >>> len(regex.findall(r'\X', u'\u200c')) 1 

Encontré wcwidth que maneja el caso anterior correctamente:

 >>> from wcwidth import wcswidth >>> wcswidth(u'A\u0332\u0305BC') 3 >>> wcswidth(u'\u200c') 0 

Pero todavía no parece funcionar con el ejemplo del usuario 596219:

 >>> wcswidth('각') 4