Python str vs types Unicode

101

En travaillant avec Python 2.7, je me demande quel avantage réel il y a à utiliser le type à la unicodeplace de str, car les deux semblent être capables de contenir des chaînes Unicode. Existe-t-il une raison particulière en dehors de la possibilité de définir des codes Unicode dans des unicodechaînes à l'aide du caractère d'échappement \?:

Exécuter un module avec:

# -*- coding: utf-8 -*-

a = 'á'
ua = u'á'
print a, ua

Résultats dans: á, á

ÉDITER:

Plus de tests avec le shell Python:

>>> a = 'á'
>>> a
'\xc3\xa1'
>>> ua = u'á'
>>> ua
u'\xe1'
>>> ua.encode('utf8')
'\xc3\xa1'
>>> ua.encode('latin1')
'\xe1'
>>> ua
u'\xe1'

Ainsi, la unicodechaîne semble être encodée en utilisant latin1au lieu de utf-8et la chaîne brute est encodée en utilisant utf-8? Je suis encore plus confus maintenant! : S

Caumons
la source
Il n'y a pas d' encodage pour unicode, c'est juste une abstraction de caractère unicode; unicodepeut être converti en stravec un certain codage (par exemple utf-8).
Bin

Réponses:

178

unicodeest destiné à gérer du texte . Le texte est une séquence de points de code qui peut être plus grand qu'un seul octet . Le texte peut être encodé dans un encodage spécifique pour représenter le texte sous forme d'octets bruts (par exemple utf-8, latin-1...).

Notez que ce unicode n'est pas encodé ! La représentation interne utilisée par python est un détail d'implémentation, et vous ne devriez pas vous en soucier tant qu'elle est capable de représenter les points de code que vous voulez.

Au contraire strdans Python 2 est une simple séquence d' octets . Cela ne représente pas du texte!

Vous pouvez penser unicodeà une représentation générale d'un texte, qui peut être encodé de nombreuses manières différentes dans une séquence de données binaires représentée via str.

Remarque: Dans Python 3, a unicodeété renommé en stret il existe un nouveau bytestype pour une séquence d'octets simple.

Quelques différences que vous pouvez voir:

>>> len(u'à')  # a single code point
1
>>> len('à')   # by default utf-8 -> takes two bytes
2
>>> len(u'à'.encode('utf-8'))
2
>>> len(u'à'.encode('latin1'))  # in latin1 it takes one byte
1
>>> print u'à'.encode('utf-8')  # terminal encoding is utf-8
à
>>> print u'à'.encode('latin1') # it cannot understand the latin1 byte

Notez que lors de l'utilisation, strvous disposez d'un contrôle de niveau inférieur sur les octets uniques d'une représentation de codage spécifique, tandis que lors de l'utilisation, unicodevous ne pouvez contrôler qu'au niveau du point de code. Par exemple, vous pouvez faire:

>>> 'àèìòù'
'\xc3\xa0\xc3\xa8\xc3\xac\xc3\xb2\xc3\xb9'
>>> print 'àèìòù'.replace('\xa8', '')
à�ìòù

Ce qui avant était valide UTF-8, ne l'est plus. En utilisant une chaîne Unicode, vous ne pouvez pas fonctionner de telle sorte que la chaîne résultante ne soit pas un texte Unicode valide. Vous pouvez supprimer un point de code, remplacer un point de code par un autre point de code, etc. mais vous ne pouvez pas jouer avec la représentation interne.

Bakuriu
la source
4
Merci beaucoup pour votre réponse, cela a beaucoup aidé! La partie la plus clarifiante pour moi est: "Unicode n'est pas encodé! La représentation interne utilisée par python est un détail d'implémentation, et vous ne devriez pas vous en soucier [...]". Ainsi, lors de la sérialisation d' unicodeobjets, je suppose que nous devons d'abord les utiliser explicitement encode()au format de codage approprié, car nous ne savons pas lequel est utilisé en interne pour représenter la unicodevaleur.
Caumons
10
Oui. Lorsque vous voulez enregistrer du texte (par exemple dans un fichier), vous devez le représenter avec des octets, c'est-à-dire que vous devez l' encoder . Lors de la récupération du contenu, vous devez connaître le codage utilisé, afin de pouvoir décoder les octets en un unicodeobjet.
Bakuriu
Je suis désolé, mais la déclaration qui unicoden'est pas encodée est tout simplement fausse. UTF-16 / UCS-2 et UTF-32 / UCS-4 sont également des encodages ... et à l'avenir, d'autres seront probablement créés. Le fait est que le fait que vous ne devriez pas vous soucier des détails de l'implémentation (et, en fait, vous ne devriez pas!), Ne signifie toujours pas que ce unicoden'est pas encodé. C'est bien sûr. Que ce soit possible .decode(), c'est une toute autre histoire.
0xC0000022L
1
@ 0xC0000022L Peut-être que la phrase telle qu'elle est n'est pas claire. Il devrait dire: la unicodereprésentation interne de l' objet peut être ce qu'elle veut, y compris une représentation non standard. En particulier dans python3 + n'utilise une représentation interne non standard qui change également en fonction des données contenues. En tant que tel, ce n'est pas un encodage standard . Unicode en tant que norme de texte définit uniquement les points de code qui sont une représentation abstraite du texte, il existe des tonnes de façons d'encoder unicode en mémoire, y compris le standard utf-X, etc. Python utilise sa propre méthode pour plus d'efficacité. unicode
Bakuriu le
1
@ 0xC0000022L De plus, le fait que UTF-16 soit un encodage n'a rien à voir avec l' unicodeobjet de CPython , puisqu'il n'utilise ni UTF-16, ni UTF-32. Il utilise une représentation ad hoc, et si vous souhaitez encoder les données en octets réels, vous devez utiliser encode. De plus: le langage ne précise pas comment unicodeest implémenté, donc différentes versions ou implémentations de python peuvent (et ont ) une représentation interne différente.
Bakuriu le
38

Unicode et les encodages sont des choses complètement différentes et indépendantes.

Unicode

Attribue un identifiant numérique à chaque caractère:

  • 0x41 → A
  • 0xE1 → á
  • 0x414 → Д

Ainsi, Unicode attribue le numéro 0x41 à A, 0xE1 à á et 0x414 à Д.

Même la petite flèche → que j'ai utilisée a son numéro Unicode, c'est 0x2192. Et même les emojis ont leurs numéros Unicode, 😂 est 0x1F602.

Vous pouvez rechercher les numéros Unicode de tous les caractères de ce tableau . En particulier, vous pouvez trouver les trois premiers caractères ci-dessus ici , la flèche ici et l'emoji ici .

Ces numéros attribués à tous les caractères par Unicode sont appelés points de code .

Le but de tout cela est de fournir un moyen de se référer sans ambiguïté à chaque caractère. Par exemple, si je parle de 😂, au lieu de dire "vous savez, cet emoji riant avec des larmes" , je peux simplement dire, point de code Unicode 0x1F602 . Plus facile, non?

Notez que les points de code Unicode sont généralement formatés avec un début U+, puis la valeur numérique hexadécimale complétée à au moins 4 chiffres. Ainsi, les exemples ci-dessus seraient U + 0041, U + 00E1, U + 0414, U + 2192, U + 1F602.

Les points de code Unicode vont de U + 0000 à U + 10FFFF. Soit 1 114 112 chiffres. 2048 de ces nombres sont utilisés pour les substituts , il en reste donc 1.112.064. Cela signifie qu'Unicode peut attribuer un identifiant unique (point de code) à 1 112 064 caractères distincts. Tous ces points de code ne sont pas encore affectés à un caractère, et Unicode est étendu en continu (par exemple, lorsque de nouveaux emojis sont introduits).

La chose importante à retenir est que tout ce que fait Unicode est d'attribuer un identifiant numérique, appelé point de code, à chaque caractère pour une référence simple et sans ambiguïté.

Encodages

Mappez les caractères sur les modèles de bits.

Ces modèles de bits sont utilisés pour représenter les caractères dans la mémoire de l'ordinateur ou sur le disque.

Il existe de nombreux encodages différents qui couvrent différents sous-ensembles de caractères. Dans le monde anglophone, les encodages les plus courants sont les suivants:

ASCII

Mappe 128 caractères (points de code U + 0000 à U + 007F) avec des modèles de bits de longueur 7.

Exemple:

  • a → 1100001 (0x61)

Vous pouvez voir tous les mappages dans ce tableau .

ISO 8859-1 (alias Latin-1)

Mappe 191 caractères (points de code U + 0020 à U + 007E et U + 00A0 à U + 00FF) à des modèles de bits de longueur 8.

Exemple:

  • a → 01100001 (0x61)
  • á → 11100001 (0xE1)

Vous pouvez voir tous les mappages dans ce tableau .

UTF-8

Cartes 1,112,064 caractères (tous les points de code Unicode existants) à des configurations de bits de longueur , soit 8, 16, 24 ou 32 bits (soit 1, 2, 3 ou 4 octets).

Exemple:

  • a → 01100001 (0x61)
  • á → 11000011 10100001 (0xC3 0xA1)
  • ≠ → 11100010 10001001 10100000 (0xE2 0x89 0xA0)
  • 😂 → 11110000 10011111 10011000 10000010 (0xF0 0x9F 0x98 0x82)

La façon dont UTF-8 encode les caractères en chaînes de bits est très bien décrite ici .

Unicode et encodages

En regardant les exemples ci-dessus, il devient clair à quel point Unicode est utile.

Par exemple, si je suis Latin-1 et que je veux expliquer mon encodage de á, je n'ai pas besoin de dire:

"J'encode ça avec un aigu (ou comme vous appelez cette barre montante) comme 11100001"

Mais je peux juste dire:

"J'encode U + 00E1 comme 11100001"

Et si je suis UTF-8 , je peux dire:

"Moi, à mon tour, j'encode U + 00E1 comme 11000011 10100001"

Et il est clair pour tout le monde de quel personnage nous parlons.

Maintenant à la confusion souvent apparue

Il est vrai que parfois le modèle binaire d'un encodage, si vous l'interprétez comme un nombre binaire, est le même que le point de code Unicode de ce caractère.

Par exemple:

  • ASCII code a comme 1100001, que vous pouvez interpréter comme le nombre hexadécimal 0x61 , et le point de code Unicode de a est U + 0061 .
  • Latin-1 code á comme 11100001, que vous pouvez interpréter comme le nombre hexadécimal 0xE1 , et le point de code Unicode de á est U + 00E1 .

Bien sûr, cela a été arrangé comme ceci à des fins de commodité. Mais vous devriez le considérer comme une pure coïncidence . Le modèle binaire utilisé pour représenter un caractère en mémoire n'est en aucun cas lié au point de code Unicode de ce caractère.

Personne ne dit même que vous devez interpréter une chaîne de bits comme 11100001 comme un nombre binaire. Regardez-le simplement comme la séquence de bits que Latin-1 utilise pour coder le caractère á .

Retour à votre question

L'encodage utilisé par votre interpréteur Python est UTF-8 .

Voici ce qui se passe dans vos exemples:

Exemple 1

Ce qui suit encode le caractère á en UTF-8. Il en résulte la chaîne de bits 11000011 10100001, qui est enregistrée dans la variable a.

>>> a = 'á'

Lorsque vous regardez la valeur de a, son contenu 11000011 10100001 est formaté en tant que numéro hexadécimal 0xC3 0xA1 et généré sous la forme '\xc3\xa1':

>>> a
'\xc3\xa1'

Exemple 2

Ce qui suit enregistre le point de code Unicode de á, qui est U + 00E1, dans la variable ua(nous ne savons pas quel format de données Python utilise en interne pour représenter le point de code U + 00E1 en mémoire, et cela n'a pas d'importance pour nous):

>>> ua = u'á'

Lorsque vous regardez la valeur de ua, Python vous indique qu'il contient le point de code U + 00E1:

>>> ua
u'\xe1'

Exemple 3

Ce qui suit encode le point de code Unicode U + 00E1 (représentant le caractère á) avec UTF-8, ce qui donne le modèle de bits 11000011 10100001. Encore une fois, pour la sortie, ce modèle de bits est représenté par le nombre hexadécimal 0xC3 0xA1:

>>> ua.encode('utf-8')
'\xc3\xa1'

Exemple 4

Ce qui suit encode le point de code Unicode U + 00E1 (représentant le caractère á) avec Latin-1, ce qui donne le modèle de bits 11100001. Pour la sortie, ce modèle de bits est représenté par le nombre hexadécimal 0xE1, qui, par coïncidence, est le même que l'initial point de code U + 00E1:

>>> ua.encode('latin1')
'\xe1'

Il n'y a aucune relation entre l'objet Unicode uaet l'encodage Latin-1. Le fait que le point de code de á soit U + 00E1 et que le codage Latin-1 de á soit 0xE1 (si vous interprétez le modèle binaire du codage comme un nombre binaire) est une pure coïncidence.

Weibeld
la source
31

Votre terminal est configuré sur UTF-8.

Le fait que l'imprimerie aest une coïncidence; vous écrivez des octets UTF-8 bruts sur le terminal. aest une valeur de longueur deux , contenant deux octets, les valeurs hexadécimales C3 et A1, tandis que uaest une valeur unicode de longueur un , contenant un point de code U + 00E1.

Cette différence de longueur est l'une des principales raisons d'utiliser les valeurs Unicode; vous ne pouvez pas facilement mesurer le nombre de caractères de texte dans une chaîne d'octets; le len()d'une chaîne d'octets vous indique combien d'octets ont été utilisés, pas combien de caractères ont été encodés.

Vous pouvez voir la différence lorsque vous encodez la valeur Unicode dans différents encodages de sortie:

>>> a = 'á'
>>> ua = u'á'
>>> ua.encode('utf8')
'\xc3\xa1'
>>> ua.encode('latin1')
'\xe1'
>>> a
'\xc3\xa1'

Notez que les 256 premiers points de code de la norme Unicode correspondent à la norme Latin 1, de sorte que le point de code U + 00E1 est codé en Latin 1 sous la forme d'un octet avec la valeur hexadécimale E1.

De plus, Python utilise des codes d'échappement dans les représentations de chaînes unicode et d'octets, et les points de code bas qui ne sont pas imprimables en ASCII sont également représentés à l'aide de \x..valeurs d'échappement. C'est pourquoi une chaîne Unicode avec un point de code entre 128 et 255 ressemble tout comme le 1 codage Latin. Si vous avez une chaîne Unicode avec des points de code au-delà de U + 00FF, une séquence d'échappement différente \u....est utilisée à la place, avec une valeur hexadécimale à quatre chiffres.

Il semble que vous ne compreniez pas encore complètement la différence entre Unicode et un encodage. Veuillez lire les articles suivants avant de continuer:

Martijn Pieters
la source
J'ai modifié ma question avec des tests supplémentaires. Je lis pour l'Unicode et les différents encodages depuis un certain temps et je pense comprendre la théorie, mais en testant le code Python, je ne
saisis
1
Le codage latin-1 correspond aux 256 premiers points de code du standard Unicode. C'est pourquoi U + 00E1 encode \xe1en latin 1.
Martijn Pieters
2
C'est l'aspect le plus important d'Unicode. Ce n'est pas un encodage . C'est du texte. Unicode est une norme qui inclut beaucoup, beaucoup plus, comme des informations sur ce que les points de code sont des nombres, ou des espaces ou d'autres catégories, devraient être affichés de gauche à droite ou de droite à gauche, etc. etc. etc.
Martijn Pieters
1
C'est comme dire qu'Unicode est comme une «interface» et que l'encodage est comme une véritable «implémentation».
Caumons
2
@Varun: vous devez utiliser une version étroite de Python 2, qui utilise UCS-2 en interne et dénature tout ce qui se trouve sur U + FFFF comme ayant une longueur deux. Python 3 et une version UCS-2 (large) vous montreront que la longueur est vraiment de 1.
Martijn Pieters
2

Lorsque vous définissez a comme unicode, les caractères a et á sont égaux. Sinon, á compte pour deux caractères. Essayez len (a) et len ​​(au). En plus de cela, vous devrez peut-être disposer du codage lorsque vous travaillez avec d'autres environnements. Par exemple, si vous utilisez md5, vous obtenez des valeurs différentes pour a et ua

Ali Rasim Kocal
la source