Comment faire en sorte que l'interpréteur python gère correctement les caractères non ASCII dans les opérations de chaîne?

104

J'ai une chaîne qui ressemble à ceci:

6 918 417 712

La manière claire de couper cette chaîne (si je comprends bien Python) est simplement de dire que la chaîne est dans une variable appelée s, nous obtenons:

s.replace('Â ', '')

Cela devrait faire l'affaire. Mais bien sûr, il se plaint que le caractère non ASCII '\xc2'du fichier blabla.py n'est pas codé.

Je n'ai jamais tout à fait pu comprendre comment basculer entre différents encodages.

Voici le code, c'est vraiment le même que ci-dessus, mais maintenant c'est dans son contexte. Le fichier est enregistré au format UTF-8 dans le bloc-notes et a l'en-tête suivant:

#!/usr/bin/python2.4
# -*- coding: utf-8 -*-

Le code:

f = urllib.urlopen(url)

soup = BeautifulSoup(f)

s = soup.find('div', {'id':'main_count'})

#making a print 's' here goes well. it shows 6Â 918Â 417Â 712

s.replace('Â ','')

save_main_count(s)

Cela ne va pas plus loin que s.replace...

Adergaard
la source
1
J'ai essayé toutes les 4 réponses jusqu'à présent. Ne pas aller. Obtention toujours de l'erreur UnicodeDecodeError: le codec 'ascii' ne peut pas décoder l'octet 0xc2 en position 1: l'ordinal n'est pas dans la plage (128)
adergaard
votre chaîne unicode doit être précédée deu
SilentGhost
@SilentGhost: comme vous pouvez le voir, il n'y a aucun moyen d'être sûr qu'il s'agit d'une chaîne unicode. J'obtiens une chaîne dont le contenu est indiqué ci-dessus, mais elle contient des chaînes non ascii. Voilà le vrai problème. Je suppose que c'est unicode car ce n'est pas dans les 128 premiers.
adergaard
L'erreur n'a rien à voir avec la chaîne entrante. C'est une chaîne de votre code qui déclenche cette erreur!
SilentGhost
2
Je parie que c'est pourquoi Python 3 est si strict sur la différence entre les chaînes et les séquences d'octets, juste pour éviter ce genre de confusion.
Mark Ransom

Réponses:

84

Python 2 utilise asciicomme encodage par défaut pour les fichiers source, ce qui signifie que vous devez spécifier un autre encodage en haut du fichier pour utiliser des caractères unicode non-ascii dans les littéraux. Python 3 utilise utf-8comme encodage par défaut pour les fichiers source, donc c'est moins un problème.

Voir: http://docs.python.org/tutorial/interpreter.html#source-code-encoding

Pour activer l'encodage source utf-8, cela irait dans l'une des deux premières lignes:

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

Ce qui précède est dans la documentation, mais cela fonctionne également:

# coding: utf-8

Considérations supplémentaires:

  • Le fichier source doit également être enregistré en utilisant le bon encodage dans votre éditeur de texte.

  • Dans Python 2, le littéral unicode doit avoir un uavant, comme dans s.replace(u"Â ", u"")Mais dans Python 3, utilisez simplement des guillemets. Dans Python 2, vous pouvez from __future__ import unicode_literalsobtenir le comportement Python 3, mais sachez que cela affecte tout le module actuel.

  • s.replace(u"Â ", u"")échouera également si ce sn'est pas une chaîne Unicode.

  • string.replace renvoie une nouvelle chaîne et ne modifie pas sur place, alors assurez-vous que vous utilisez également la valeur de retour

Jason S
la source
4
Vous n'avez en fait besoin que de # coding: utf-8. -*-n'est pas pour la décoration, mais il est peu probable que vous en ayez besoin. Je pense que c'était là pour les vieux coquillages.
fmalina
157
def removeNonAscii(s): return "".join(filter(lambda x: ord(x)<128, s))

edit: ma première impulsion est toujours d'utiliser un filtre, mais l'expression du générateur est plus efficace en mémoire (et plus courte) ...

def removeNonAscii(s): return "".join(i for i in s if ord(i)<128)

Gardez à l'esprit qu'il est garanti que cela fonctionne avec le codage UTF-8 (car tous les octets des caractères multi-octets ont le bit le plus élevé défini sur 1).

fortran
la source
1
J'obtiens: TypeError: ord () attendait un caractère, mais une chaîne de longueur 2 trouvée
Ivelin
@Ivelin c'est parce que le "caractère" n'est pas interprété comme un unicode approprié ... vérifiez que votre chaîne source est précédée du préfixe us'il s'agit d'un littéral.
fortran
35
>>> unicode_string = u"hello aåbäcö"
>>> unicode_string.encode("ascii", "ignore")
'hello abc'
truppo
la source
4
Je vois les votes que vous obtenez, mais quand je l'essaie, cela dit: Non. UnicodeDecodeError: le codec 'ascii' ne peut pas décoder l'octet 0xc2 en position 1: l'ordinal n'est pas dans la plage (128). Se pourrait-il que ma chaîne d'origine ne soit pas en Unicode? Eh bien en tout cas. il faut
adergaard
2
Bien, merci. Puis-je suggérer d'utiliser .decode () sur le résultat pour l'obtenir dans le codage d'origine?
AkiRoss
Si vous obtenez UnicodeDecodeError: 'ascii', essayez de convertir la chaîne au format '' UTF-8 'avant d'appliquer la fonction d'encodage.
Sateesh
16

Le code suivant remplacera tous les caractères non ASCII par des points d'interrogation.

"".join([x if ord(x) < 128 else '?' for x in s])
Vision
la source
Par curiosité, je voulais savoir cela, y a-t-il une raison particulière de le remplacer par le point d'interrogation?
Mohsin
6

Utilisation de Regex:

import re

strip_unicode = re.compile("([^-_a-zA-Z0-9!@#%&=,/'\";:~`\$\^\*\(\)\+\[\]\.\{\}\|\?\<\>\\]+|[^\s]+)")
print strip_unicode.sub('', u'6Â 918Â 417Â 712')
Akoi Meexx
la source
5

Bien trop tard pour une réponse, mais la chaîne d'origine était en UTF-8 et '\ xc2 \ xa0' est UTF-8 pour NO-BREAK SPACE. s.decode('utf-8')Décodez simplement la chaîne d'origine comme (\ xa0 s'affiche sous forme d'espace lorsqu'il est décodé incorrectement sous Windows-1252 ou latin-1:

Exemple (Python 3)

s = b'6\xc2\xa0918\xc2\xa0417\xc2\xa0712'
print(s.decode('latin-1')) # incorrectly decoded
u = s.decode('utf8') # correctly decoded
print(u)
print(u.replace('\N{NO-BREAK SPACE}','_'))
print(u.replace('\xa0','-')) # \xa0 is Unicode for NO-BREAK SPACE

Production

6 918 417 712
6 918 417 712
6_918_417_712
6-918-417-712
Mark Tolonen
la source
3
#!/usr/bin/env python
# -*- coding: utf-8 -*-

s = u"6Â 918Â 417Â 712"
s = s.replace(u"Â", "") 
print s

Cela imprimera 6 918 417 712

Ésaïe
la source
Nan. UnicodeDecodeError: le codec 'ascii' ne peut pas décoder l'octet 0xc2 en position 1: l'ordinal n'est pas dans la plage (128). Se pourrait-il que ma chaîne d'origine ne soit pas en Unicode? Eh bien en tout cas. Je fais probablement quelque chose de mal.
adergaard
@adergaard, avez-vous ajouté # - - codage: utf-8 - - en haut du fichier source?
Nadia Alramli
Oui, voir à nouveau le haut de cette page, j'ai édité la question et mis le code et les commentaires d'en-tête. Merci pour votre aide.
adergaard
Je pense que vous devrez trouver comment obtenir les chaînes du document html ou xml en Unicode. Plus d'informations à ce sujet ici: diveintopython.org/xml_processing/unicode.html
Isaiah
2

Je sais que c'est un vieux fil, mais je me suis senti obligé de mentionner la méthode translate, qui est toujours un bon moyen de remplacer tous les codes de caractères au-dessus de 128 (ou autre si nécessaire).

Utilisation : str. translate ( table [, deletechars] )

>>> trans_table = ''.join( [chr(i) for i in range(128)] + [' '] * 128 )

>>> 'Résultat'.translate(trans_table)
'R sultat'
>>> '6Â 918Â 417Â 712'.translate(trans_table)
'6  918  417  712'

À partir de Python 2.6 , vous pouvez également définir la table sur Aucun et utiliser deletechars pour supprimer les caractères que vous ne voulez pas, comme dans les exemples présentés dans la documentation standard à http://docs.python.org/library/stdtypes. html .

Avec les chaînes Unicode, la table de traduction n'est pas une chaîne de 256 caractères mais un dict avec l'ord () des caractères pertinents comme clés. Mais de toute façon, obtenir une chaîne ascii appropriée à partir d'une chaîne unicode est assez simple, en utilisant la méthode mentionnée par truppo ci-dessus, à savoir: unicode_string.encode ("ascii", "ignorer")

En résumé, si pour une raison quelconque vous avez absolument besoin d'obtenir une chaîne ascii (par exemple, lorsque vous déclenchez une exception standard avec raise Exception, ascii_message), vous pouvez utiliser la fonction suivante:

trans_table = ''.join( [chr(i) for i in range(128)] + ['?'] * 128 )
def ascii(s):
    if isinstance(s, unicode):
        return s.encode('ascii', 'replace')
    else:
        return s.translate(trans_table)

La bonne chose avec translate est que vous pouvez réellement convertir les caractères accentués en caractères ascii non accentués pertinents au lieu de simplement les supprimer ou de les remplacer par «?». Ceci est souvent utile, par exemple à des fins d'indexation.

Louis LC
la source
J'obtiens: TypeError: le mappage de caractères doit retourner un entier, aucun ou unicode
Ivelin
1
s.replace(u'Â ', '')              # u before string is important

et rendez votre .pyfichier unicode.

SilentGhost
la source
1

Ceci est un sale hack, mais peut fonctionner.

s2 = ""
for i in s:
    if ord(i) < 128:
        s2 += i
Corey D
la source
0

Pour ce que cela valait, mon jeu de caractères était utf-8et j'avais inclus la ligne classique " # -*- coding: utf-8 -*-".

Cependant, j'ai découvert que je n'avais pas de Newlines universelles lors de la lecture de ces données à partir d'une page Web.

Mon texte comportait deux mots, séparés par " \r\n". Je ne faisais que diviser \net remplacer le "\n".

Une fois que j'ai parcouru et vu le jeu de caractères en question, j'ai réalisé l'erreur.

Donc, il pourrait également être dans le jeu de caractères ASCII , mais un caractère auquel vous ne vous attendiez pas.

Glen
la source