Maison  >  Article  >  développement back-end  >  En savoir plus sur l'encodage Python et Unicode

En savoir plus sur l'encodage Python et Unicode

巴扎黑
巴扎黑original
2017-04-30 14:50:051296parcourir

Je suis sûr qu'il y a beaucoup d'explications sur Unicode et Python, mais afin de faciliter ma compréhension et mon utilisation, je prévois quand même d'écrire quelques choses supplémentaires à leur sujet.

Flux d'octets vs objet Unicode

Définissons d’abord une chaîne en Python. Lorsque vous utilisez le type chaîne, vous stockez en fait une chaîne d'octets.

[  a ][  b ][  c ] = "abc"
[ 97 ][ 98 ][ 99 ] = "abc"

Dans cet exemple, la chaîne abc est une chaîne d'octets. 97., 98 et 99 sont des codes ASCII. La définition dans Python 2.x est de traiter toutes les chaînes comme ASCII. Malheureusement, ASCII est la norme la moins courante parmi les jeux de caractères latins.

ASCII utilise les 127 premiers nombres pour le mappage des caractères. Les cartes de caractères comme Windows-1252 et UTF-8 ont les mêmes 127 premiers caractères. Il est possible de mélanger en toute sécurité les codages de chaînes lorsque la valeur de chaque octet de votre chaîne est inférieure à 127. Cependant, il y a un danger à faire cette hypothèse, comme nous le verrons ci-dessous.

Des problèmes surviendront lorsque votre chaîne contient des octets avec une valeur supérieure à 126. Regardons une chaîne codée dans Windows-1252. Le mappage de caractères dans Windows-1252 est un mappage de caractères sur 8 bits, il y aura donc un total de 256 caractères. Les 127 premiers sont identiques à ASCII et les 127 suivants sont d'autres caractères définis par Windows-1252.

A windows-1252 encoded string looks like this:
[ 97 ] [ 98 ] [ 99 ] [ 150 ] = "abc–"

Windows-1252 est toujours une chaîne d'octets, mais avez-vous vu que la valeur du dernier octet est supérieure à 126 ? Si Python tente de décoder ce flux d'octets en utilisant la norme ASCII par défaut, il signalera une erreur. Voyons ce qui se passe lorsque Python décode cette chaîne :

>>> x = "abc" + chr(150)
>>> print repr(x)
'abc\x96'
>>> u"Hello" + x
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
UnicodeDecodeError: &#39;ASCII&#39; codec can&#39;t decode byte 0x96 in position 3: ordinal not in range(128)

​ Utilisons UTF-8 pour encoder une autre chaîne :

A UTF-8 encoded string looks like this:
[ 97 ] [ 98 ] [ 99 ] [ 226 ] [ 128 ] [ 147 ] = "abc–"
[0x61] [0x62] [0x63] [0xe2]  [ 0x80] [ 0x93] = "abc-"

Si vous prenez la table de codage Unicode que vous connaissez, vous constaterez que le point de code Unicode correspondant au tiret anglais est 8211 (0×2013). Cette valeur est supérieure à la valeur maximale ASCII de 127. Une valeur supérieure à un octet peut être stockée. Étant donné que 8211 (0 × 2013) fait deux octets, UTF-8 doit utiliser quelques astuces pour indiquer au système qu'il faut trois octets pour stocker un caractère. Voyons quand Python va utiliser l'ASCII par défaut pour encoder une chaîne codée en UTF-8 avec une valeur de caractère supérieure à 126.

>>> x = "abc\xe2\x80\x93"
>>> print repr(x)
&#39;abc\xe2\x80\x93&#39;
>>> u"Hello" + x
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
UnicodeDecodeError: &#39;ASCII&#39; codec can&#39;t decode byte 0xe2 in position 3: ordinal not in range(128)

Comme vous pouvez le constater, Python a toujours utilisé le codage ASCII par défaut. Lorsqu'il traite le 4ème caractère, Python renvoie une erreur car sa valeur est 226, ce qui est supérieur à 126. C’est le problème du codage mixte.

Flux d'octets de décodage

Lors de l’apprentissage de Python Unicode pour la première fois, le terme décodage peut prêter à confusion. Vous pouvez décoder un flux d'octets en un objet Unicode et coder un objet Unicode en un flux d'octets.

Python doit savoir comment décoder un flux d'octets en un objet Unicode. Lorsque vous obtenez un flux d'octets, vous appelez sa méthode "decode" pour créer un objet Unicode à partir de celui-ci

. Vous feriez mieux de décoder le flux d'octets en Unicode le plus tôt possible.

>>> x = "abc\xe2\x80\x93"
>>> x = x.decode("utf-8")
>>> print type(x)
<type &#39;unicode&#39;>
>>> y = "abc" + chr(150)
>>> y = y.decode("windows-1252")
>>> print type(y)
>>> print x + y
abc–abc–

​Encoder Unicode dans un flux d'octets

Un objet Unicode est une représentation indépendante du codage d’un texte. Vous ne pouvez pas simplement générer un objet Unicode. Il doit être transformé en chaîne d'octets avant la sortie. Python serait bien adapté à ce type de travail, bien que Python utilise par défaut l'ASCII lors de l'encodage d'Unicode dans un flux d'octets. Ce comportement par défaut peut causer de nombreux problèmes.

>>> u = u"abc\u2013"
>>> print u
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: &#39;ascii&#39; codec can&#39;t encode character u&#39;\u2013&#39; in position 3: ordinal not in range(128)
>>> print u.encode("utf-8")
abc–

​Utiliser le module codecs

Le module codecs peut être d'une grande aide lors du traitement des flux d'octets. Vous pouvez ouvrir des fichiers avec l'encodage défini et le contenu que vous lisez dans le fichier sera automatiquement converti en objets Unicode.

​Essayez ceci :

>>> import codecs
>>> fh = codecs.open("/tmp/utf-8.txt", "w", "utf-8")
>>> fh.write(u"\u2013")
>>> fh.close()

Ce qu'il fait, c'est obtenir un objet Unicode et l'écrire dans le fichier en codage UTF-8. Vous pouvez également l'utiliser dans d'autres situations.

​Essayez ceci :

Lors de la lecture des données d'un fichier, codecs.open créera un objet fichier qui peut automatiquement convertir le fichier codé UTF-8 en un objet Unicode.

Continuons l'exemple ci-dessus, cette fois en utilisant les flux urllib.

>>> stream = urllib.urlopen("http://www.google.com")
>>> Reader = codecs.getreader("utf-8")
>>> fh = Reader(stream)
>>> type(fh.read(1))
<type &#39;unicode&#39;>
>>> Reader
<class encodings.utf_8.StreamReader at 0xa6f890>

Version monoligne :

>>> fh = codecs.getreader("utf-8")(urllib.urlopen("http://www.google.com"))
>>> type(fh.read(1))

Il faut être très prudent avec les modules codecs. Ce que vous transmettez doit être un objet Unicode, sinon il décodera automatiquement le flux d'octets en ASCII.

>>> x = "abc\xe2\x80\x93" # our "abc-" utf-8 string
>>> fh = codecs.open("/tmp/foo.txt", "w", "utf-8")
>>> fh.write(x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.5/codecs.py", line 638, in write
  return self.writer.write(data)
File "/usr/lib/python2.5/codecs.py", line 303, in write
  data, consumed = self.encode(object, self.errors)
UnicodeDecodeError: &#39;ascii&#39; codec can&#39;t decode byte 0xe2 in position 3: ordinal not in range(128)

Aïe, Python a recommencé à utiliser ASCII pour tout décoder.

​Le problème du découpage du flux d'octets UTF-8

Étant donné qu'une chaîne codée en UTF-8 est une liste d'octets, len() et les opérations de découpage ne fonctionnent pas correctement. Utilisez d’abord la chaîne que nous avons utilisée auparavant.

[ 97 ] [ 98 ] [ 99 ] [ 226 ] [ 128 ] [ 147 ] = "abc–"

Ensuite, procédez comme suit :

>>> my_utf8 = "abc–"
>>> print len(my_utf8)
6

Quel cheval ? Cela ressemble à 4 caractères, mais le résultat de len indique que c'est 6. Parce que len compte le nombre d'octets plutôt que le nombre de caractères.

>>> print repr(my_utf8)
&#39;abc\xe2\x80\x93&#39;

Maintenant, divisons cette chaîne.

>>> my_utf8[-1] # Get the last char
&#39;\x93&#39;

Laissez-moi partir, le résultat de la segmentation est le dernier octet, pas le dernier caractère.

Afin de segmenter correctement UTF-8, vous feriez mieux de décoder le flux d'octets pour créer un objet Unicode. Vous pourrez alors opérer et compter en toute sécurité.

>>> my_unicode = my_utf8.decode("utf-8")
>>> print repr(my_unicode)
u&#39;abc\u2013&#39;
>>> print len(my_unicode)
4
>>> print my_unicode[-1]
–

Quand Python encode/décode automatiquement

Dans certains cas, des erreurs seront générées lorsque Python encode/décode automatiquement en utilisant ASCII.

  第一个案例是当它试着将Unicode和字节串合并在一起的时候。

>>> u"" + u"\u2019".encode("utf-8")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: &#39;ascii&#39; codec can&#39;t decode byte 0xe2 in position 0:   ordinal not in range(128)

  在合并列表的时候会发生同样的情况。Python在列表里有string和Unicode对象的时候会自动地将字节串解码为Unicode。

>>> ",".join([u"This string\u2019s unicode", u"This string\u2019s utf-8".encode("utf-8")])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: &#39;ascii&#39; codec can&#39;t decode byte 0xe2 in position 11:  ordinal not in range(128)

  或者当试着格式化一个字节串的时候:

>>> "%s\n%s" % (u"This string\u2019s unicode", u"This string\u2019s  utf-8".encode("utf-8"),)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: &#39;ascii&#39; codec can&#39;t decode byte 0xe2 in position 11: ordinal not in range(128)

  基本上当你把Unicode和字节串混在一起用的时候,就会导致出错。

  在这个例子里面,你创建一个utf-8文件,然后往里面添加一些Unicode对象的文本。就会报UnicodeDecodeError错误。

>>> buffer = []
>>> fh = open("utf-8-sample.txt")
>>> buffer.append(fh.read())
>>> fh.close()
>>> buffer.append(u"This string\u2019s unicode")
>>> print repr(buffer)
[&#39;This file\xe2\x80\x99s got utf-8 in it\n&#39;, u&#39;This string\u2019s unicode&#39;]
>>> print "\n".join(buffer)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: &#39;ascii&#39; codec can&#39;t decode byte 0xe2 in position 9: ordinal not in range(128)

  你可以使用codecs模块把文件作为Unicode加载来解决这个问题。

>>> import codecs
>>> buffer = []
>>> fh = open("utf-8-sample.txt", "r", "utf-8")
>>> buffer.append(fh.read())
>>> fh.close()
>>> print repr(buffer)
[u&#39;This file\u2019s got utf-8 in it\n&#39;, u&#39;This string\u2019s unicode&#39;]
>>> buffer.append(u"This string\u2019s unicode")
>>> print "\n".join(buffer)
This file’s got utf-8 in it

This string’s unicode

  正如你看到的,由codecs.open 创建的流在当数据被读取的时候自动地将比特串转化为Unicode。

  最佳实践

  1.最先解码,最后编码

  2.默认使用utf-8编码

  3.使用codecs和Unicode对象来简化处理

  最先解码意味着无论何时有字节流输入,需要尽早将输入解码为Unicode。这会防止出现len( )和切分utf-8字节流发生问题。

  最后编码意味着只有在准备输入的时候才进行编码。这个输出可能是一个文件,一个数据库,一个socket等等。只有在处理完成之后才编码unicode对象。最后编码也意味着,不要让Python为你编码Unicode对象。Python将会使用ASCII编码,你的程序会崩溃。

  默认使用UTF-8编码意味着:因为UTF-8可以处理任何Unicode字符,所以你最好用它来替代windows-1252和ASCII。

  codecs模块能够让我们在处理诸如文件或socket这样的流的时候能少踩一些坑。如果没有codecs提供的这个工具,你就必须将文件内容读取为字节流,然后将这个字节流解码为Unicode对象。

  codecs模块能够让你快速的将字节流转化为Unicode对象,省去很多麻烦。

  解释UTF-8

  最后的部分是让你能入门UTF-8,如果你是个超级极客可以无视这一段。

  利用UTF-8,任何在127和255之间的字节是特别的。这些字节告诉系统这些字节是多字节序列的一部分。

Our UTF-8 encoded string looks like this:
[ 97 ] [ 98 ] [ 99 ] [ 226 ] [ 128 ] [ 147 ] = "abc–"

  最后3字节是一个UTF-8多字节序列。如果你把这三个字节里的第一个转化为2进制可以看到以下的结果:

11100010

  前3比特告诉系统它开始了一个3字节序列226,128,147。

  那么完整的字节序列。

11100010 10000000 10010011

  然后你运用三字节序列的下面的掩码。

1110xxxx 10xxxxxx 10xxxxxx
XXXX0010 XX000000 XX010011 Remove the X&#39;s
0010       000000   010011 Collapse the numbers
00100000 00010011          Get Unicode number 0x2013, 8211 The "–"

  这是基本的UTF-8入门,如果想知道更多的细节,可以去看UTF-8的维基页面。

  原文链接: ERIC MORITZ   翻译: 伯乐在线 - 贱圣OMG

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn