2015-06-17 4 views
8

In Python 3, le stringhe Unicode dovrebbero gentilmente fornire il numero di caratteri Unicode, ma non riesco a capire come ottenere la larghezza di visualizzazione finale di una stringa dato che alcuni caratteri si combinano.Come si ottiene la larghezza di visualizzazione dei caratteri Unicode combinati in Python 3?

Genesi 1: 1 - בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ

>>> len('בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ') 
60 

Ma la stringa è largo solo 37 caratteri. La normalizzazione non risolve il problema perché le vocali (i punti al di sotto dei caratteri più grandi) sono caratteri distinti.

>>> len(unicodedata.normalize('NFC', 'בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ')) 
60 

Come nota a margine: il modulo textwrap è completamente rotto a questo proposito, in modo aggressivo il confezionamento dove non dovrebbe. str.format sembra altrettanto rotto.

+0

Counting cluster grafema potrebbe non essere sufficiente ad esempio, [diversi tipi di carattere può portare a diverse dimensioni di testo] (http://stackoverflow.com/q/2922295/4279) – jfs

+0

Anche se stiamo garantito un font a spaziatura fissa ? –

+0

Segui il link, prova il codice e guarda tu stesso. – jfs

risposta

3

Un paio di soluzioni che utilizzano il terzo uniseg, come suggerito da @bobince:

>>> from uniseg.graphemecluster import grapheme_cluster_breakables 
>>> sum(grapheme_cluster_breakables('בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ')) 
37 
>>> 
>>> from uniseg.graphemecluster import grapheme_clusters 
>>> list(grapheme_clusters('בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְ הָאָרֶץ')) 
['בְּ', 'רֵ', 'א', 'שִׁ', 'י', 'ת', ',', ' ', 'בָּ', 'רָ', 'א', ' ', 'אֱ', 'לֹ', 'הִ', 'י', 'ם', ',', ' ', 'אֵ', 'ת', ' ', 'הַ', 'שָּׁ', 'מַ', 'יִ', 'ם', ',', ' ', 'וְ', 'אֵ', 'ת', ' ', 'הָ', 'אָ', 'רֶ', 'ץ'] 
>>> len(list(grapheme_clusters('בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַי , ואֵת הָאָרֶץ'))) 
37 

questo sembra il modo corretto di farlo.

Ecco un esempio che corregge textwrap. Le soluzioni per il patching di altri moduli dovrebbero essere simili.

>>> import textwrap 
>>> text = 'בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשּׁמַיִם, וְאֵת הָאָרֶץ' 
>>> print(textwrap.fill(text, width=40)) # bad, aggressive wrapping 
בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת 
הַשָּׁמַיִם, וְאֵת הָאָרֶץ 
>>> import uniseg.graphemecluster 
>>> def new_len(x): 
...  if isinstance(x, str): 
...   return sum(1 for _ in uniseg.graphemecluster.grapheme_clusters(x)) 
...  return len(x) 
>>> textwrap.len = new_len 
>>> print(textwrap.fill(text, width=40)) # Good wrapping 
בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ 
+1

Puoi anche usare il modulo 'regex':' count_user_perceived_characters = testo lambda: len (regex.findall (r '\ X', testo)) ' – jfs

+0

@ J.F.Sebastian Neat! Quel progetto dice che intende sostituire 're'. Hai qualche idea se effettivamente lo farà? –

+1

Non lo so. [Sembra improbabile] (http://bugs.python.org/issue2636). – jfs

3

Il problema è che i personaggi che conciliano, quale Python conta come distinto quando si calcola il __len__, ma si fondono in un singolo carattere stampato.

Per scoprire se un personaggio è un personaggio che unisce, possiamo utilizzare il unicodedata module:

unicodedata.combining(unichr)

restituisce la classe canonica combinata, assegnata al carattere Unicode unichr come intero. Restituisce 0 se non è definita alcuna classe di combinazione.

Una soluzione ingenua è semplicemente eliminare tutti i caratteri con una classe di combinazione diversa da zero. Questo lascia i personaggi che stanno da soli e dovrebbero darci una stringa con una mappatura 1-a-1 tra caratteri visibili e sottostanti. (Sono un novizio Unicode, ed è probabilmente più complicato di così: ci sono sottigliezze con la combinazione di caratteri e estensori grapheme che non capisco davvero, ma non sembrano avere importanza per questa particolare stringa.)

Così mi è venuta con questa funzione:

import unicodedata 

def visible_length(unistr): 
    '''Returns the number of printed characters in a Unicode string.''' 
    return len([char for char in unistr if unicodedata.combining(char) == 0]) 

che restituisce la lunghezza corretta per la stringa:

>>> visible_length('בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ') 
37 

questo non è probabilmente un solutio completa n per tutte le stringhe Unicode, ma a seconda del sottoinsieme di Unicode con cui stai lavorando, questo potrebbe essere sufficiente per le tue esigenze.

+3

Se è necessario l'intero algoritmo di segmentazione del grafo del grafo Unicode o la suddivisione in linea, allora è un po 'più complicato, vedere i moduli di terze parti come uniseg. – bobince

+0

+1. Questo mi era accaduto, ma quando ho giocato con unicodedata.combining e ho visto che restituiva una vasta gamma di valori, sono diventato piuttosto intimidito, ma forse è adatto ai miei scopi. Grazie. Speriamo che qualcuno possa proporre una soluzione ancora più robusta. –