Python et Unicode

C’est peut-être évident pour certains, mais je viens de réaliser que :

>>> 'Pâte à crêpe'.count("e")
2
>>> 'Pâte à crêpe'.count("e")
3

(ça doit être aisément reproductible en copiant-collant le code).

Le premier qui trouve gagne une crêpe à la prochaine PyConFR !

1 « J'aime »

Ligatures?

In [4]: for c in 'Pâte à crêpe':
   ...:     print(name(c))
   ...: 

LATIN CAPITAL LETTER P
LATIN SMALL LETTER A
COMBINING CIRCUMFLEX ACCENT
LATIN SMALL LETTER T
LATIN SMALL LETTER E
SPACE
LATIN SMALL LETTER A
COMBINING GRAVE ACCENT
SPACE
LATIN SMALL LETTER C
LATIN SMALL LETTER R
LATIN SMALL LETTER E WITH CIRCUMFLEX
LATIN SMALL LETTER P
LATIN SMALL LETTER E

In [5]: for c in 'Pâte à crêpe':
   ...:     print(name(c))
   ...: 

LATIN CAPITAL LETTER P
LATIN SMALL LETTER A
COMBINING CIRCUMFLEX ACCENT
LATIN SMALL LETTER T
LATIN SMALL LETTER E
SPACE
LATIN SMALL LETTER A
COMBINING GRAVE ACCENT
SPACE
LATIN SMALL LETTER C
LATIN SMALL LETTER R
LATIN SMALL LETTER E
COMBINING CIRCUMFLEX ACCENT
LATIN SMALL LETTER P
LATIN SMALL LETTER E

Dans le premier cas, le “ê” est un caractère à lui tout seul et dans le deuxième cas, le “ê” est une combinaison de “e” + “^” (et du coup il est compté) ?

Dans le premier cas, le “ê” est un caractère à lui tout seul et dans le deuxième cas, le “ê” est une combinaison de “e” + “^” (et du coup il est compté) ?

Bravo ! Pour générer le cas j’ai utilisé unicodedata.normalize() pour passer de la « forme normale composée » (NFC) à la « forme normale décomposée » (NFD) :

>>> unicodedata.normalize("NFC", "crêpe").count("e")
1
>>> unicodedata.normalize("NFD", "crêpe").count("e")
2

Le caractère utilisé s’appelle un “caractère de combinaison” :

>>> unicodedata.name(unicodedata.normalize("NFD", "crêpe")[3])
'COMBINING CIRCUMFLEX ACCENT'

On peut faire plein de choses avec les caractères de combinaison :

>>> combining_circumflex = unicodedata.normalize("NFD", "crêpe")[3]
>>> # ou
>>> combining_circumflex = unicodedata.lookup("COMBINING CIRCUMFLEX ACCENT")
>>> "y" + combining_circumflex
'ŷ'
>>> "y" + combining_circumflex * 2
'ŷ̂'

Évidement votre navigateur ne sait pas forcément afficher sa, ou la police qu’il utilise, ou une combinaison des deux, j’imagine l’enfer que ça doit être derrière le rendu de ce genre de chose. Mais gnome-console y arrive :

Capture d’écran du 2023-06-15 21-20-30

Pour finir, c’est mon moyen préféré pour retirer les accents d’une chaîne :

>>> "".join(c for c in unicodedata.normalize("NFD", "pâte à crêpe") if not unicodedata.combining(c))
'pate a crepe'

[sans vergogne]Très utile pour résoudre HackInScience — Playing with anagrams[/sans vergogne].

Ah et pour finir, GG @grewn0uille, je te dois une crêpe à la prochaine PyConFR :slight_smile:

yeay \o/ !

Pour les passionnés d’histoire, la première publication d’Unicode date d’octobre 1991, 8 mois après la première publication de Python !

1 « J'aime »

Excellente remarque de @vertenote sur mastodon, « ça dépend du nombre d’œufs » :

>>> 'Pâte à crêpe 2 œufs'.count("e")
2
>>> 'Pâte à crêpe 3 œufs'.count("e")
3

j’adore !

3 « J'aime »

Moi aussi je voulais une crêpe. :frowning:

2 « J'aime »

Pour celles et ceux qui connaissent LaTeX, c’est un classique : typiquement, on veut copier-coller du texte depuis un PDF produit par LaTeX, on voit é et l’on récupère 'e.

Ouais ça ne m’étonne pas vraiment, j’ai tout de suite pensé à la normalisation unicode.
J’ai découvert ça quand je cherchais une manière propre de retirer les accents d’une chaîne, et oui ça fait plutôt bien le boulot.

Si vous voulez vous amuser un peu :

"👩‍🚀👩‍👦‍👦👨‍👩👨👨‍👩‍👧👨‍👨‍👦".count("👩")

>>> 4 
3 « J'aime »

Pour mes listings Python sous LaTeX en ISO-8859-1, je les passe en UTF8 à l’aide de la commande Linux iconv.

iconv -f ISO-8859-1 -t UTF8 mon_fichier_latin1 -o mon_fichier_UTF8

On peut sur ce même principe passer de l’UTF8 à l’ISO-8859-1:

iconv -f UTF8 -t ISO-8859-1 mon_fichier_UTF8 -o mon_fichier_latin1
1 « J'aime »

HAHA tu es Breton ? Repéré !

Y’a que les Bretons pour parler de pâte à crêpe en ISO-8859-1 :

>>> "Pâte à crêpe Bretonne".encode("ISO-8859-1")
b'P\xe2te \xe0 cr\xeape Bretonne'
>>> "Pâte à crêpe avec 2 œufs".encode("ISO-8859-1")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'latin-1' codec can't encode character '\u0153' in position 20: ordinal not in range(256)

Le reste de la France a migré très vite en UTF-8 pour pouvoir mettre des :egg: dans la pâte !

Pendant qu’on y est j’aime bien cette syntaxe :

>>> "\N{EGG}"
'🥚'

Le reste de la France a migré très vite en UTF-8

Sauf qu’il existe @mdk des documents à mettre à jour et qui ont été initialement rédigés en ISO-8859-1

Bien sûr, (je faisais de l’humour (malheureusement)). Attention, il existe aussi beaucoup de documents en CP1252, c’est presque pareil, mais c’est pas pareil. Attention aussi au Latin-15, encore différent :

>>> "Pâte à crêpe avec 2 œufs".encode("CP1252") == "Pâte à crêpe avec 2 œufs".encode("ISO-8859-15")
False

Vive UTF-8 !

CP1252 ( :face_vomiting:), ça me fait toujours penser au CPC 1512 :heart:

3 « J'aime »