Comment vérifier si une chaîne en Python est en ASCII?

212

Je veux vérifier si une chaîne est en ASCII ou non.

Je suis au courant ord(), cependant quand j'essaye ord('é'), je l'ai TypeError: ord() expected a character, but string of length 2 found. J'ai compris que cela est dû à la façon dont j'ai construit Python (comme expliqué dans ord()la documentation de ).

Existe-t-il un autre moyen de vérifier?

Nico
la source
L'encodage de chaînes diffère un peu entre Python 2 et Python 3, il serait donc bon de savoir quelle version vous ciblez.
florisla

Réponses:

188
def is_ascii(s):
    return all(ord(c) < 128 for c in s)
Alexander Kojevnikov
la source
95
Inutilement inefficace. Il vaut mieux essayer le s.decode ('ascii') et attraper UnicodeDecodeError, comme suggéré par Vincent Marchetti.
ddaa
20
Ce n'est pas inefficace. all () court-circuite et renvoie False dès qu'il rencontre un octet invalide.
John Millikin
10
Inefficace ou non, la méthode la plus pythonique est le try / except.
Jeremy Cantrell
43
Il est inefficace par rapport au try / except. Ici, la boucle est dans l'interpréteur. Avec la forme try / except, la boucle est dans l'implémentation du codec C appelée par str.decode ('ascii'). Et je suis d'accord, la forme try / except est plus pythonique aussi.
ddaa
25
@JohnMachin ord(c) < 128est infiniment plus lisible et intuitif quec <= "\x7F"
Slater Victoroff
253

Je pense que vous ne posez pas la bonne question ...

Une chaîne en python n'a pas de propriété correspondant à 'ascii', utf-8 ou tout autre encodage. La source de votre chaîne (que vous la lisiez à partir d'un fichier, entrée à partir d'un clavier, etc.) peut avoir codé une chaîne unicode en ascii pour produire votre chaîne, mais c'est là que vous devez rechercher une réponse.

Peut-être la question que vous pouvez poser est: "Cette chaîne est-elle le résultat du codage d'une chaîne unicode en ascii?" - Vous pouvez y répondre en essayant:

try:
    mystring.decode('ascii')
except UnicodeDecodeError:
    print "it was not a ascii-encoded unicode string"
else:
    print "It may have been an ascii-encoded unicode string"
Vincent Marchetti
la source
28
utiliser l'encodage est préférable, car la chaîne ne décode pas en python 3, voyez quelle est la différence entre encoder / décoder? (python 2.x)
Jet Guo
@Sri: C'est parce que vous l'utilisez sur une chaîne non codée ( stren Python 2, bytesen Python 3).
dotancohen
En Python 2, cette solution ne fonctionne que pour une chaîne unicode . Un strdans n'importe quel encodage ISO devrait d'abord être encodé en Unicode. La réponse devrait aller là-dedans.
alexis
@JetGuo: vous devez utiliser les deux en fonction du type d'entrée: s.decode('ascii') if isinstance(s, bytes) else s.encode('ascii')dans Python 3. L'entrée d'OP est un bytestring 'é'(syntaxe Python 2, Python 3 n'avait pas été publié à l'époque) et .decode()est donc correcte.
jfs
2
@alexis: faux. strsur Python 2 est un bytestring. Il est correct d'utiliser .decode('ascii')pour savoir si tous les octets sont dans la plage ascii.
jfs
153

Python 3 voies:

isascii = lambda s: len(s) == len(s.encode())

Pour vérifier, passez la chaîne de test:

str1 = "♥O◘♦♥O◘♦"
str2 = "Python"

print(isascii(str1)) -> will return False
print(isascii(str2)) -> will return True
loin
la source
7
C'est une belle petite astuce pour détecter les caractères non-ascii dans les chaînes Unicode, qui en python3 est à peu près toutes les chaînes. Étant donné que les caractères ascii peuvent être codés en utilisant seulement 1 octet, toute longueur de caractères ascii sera fidèle à sa taille après avoir été codée en octets; tandis que les autres caractères non-ascii seront codés en 2 octets ou 3 octets en conséquence, ce qui augmentera leur taille.
Devy
Par @far la meilleure réponse, mais pas que certains caractères comme ... et - peuvent ressembler à ascii, donc au cas où vous voudriez utiliser ceci pour détecter le texte anglais, vous devez remplacer ces caractères avant de vérifier
Christophe Roussy
1
Mais en Python2, cela lancera un UnicodeEncodeError. Je dois trouver une solution pour Py2 et Py3
alvas
2
Pour ceux qui ne sont pas familiers avec l'utilisation de lambda (comme je l'étais lorsque j'ai rencontré cette réponse pour la première fois), isasciic'est maintenant une fonction que vous passez une chaîne: isascii('somestring')== Trueet isascii('àéç')==False
rabidang3ls
8
C'est tout simplement du gaspillage. Il code une chaîne en UTF-8, créant un tout autre bytestring. La vraie façon Python 3 est try: s.encode('ascii'); return True except UnicodeEncodeError: return False(comme ci-dessus, mais l'encodage, car les chaînes sont Unicode en Python 3). Cette réponse déclenche également une erreur dans Python 3 lorsque vous avez des substituts (par exemple, isascii('\uD800')déclenche une erreur au lieu de revenir False)
Artyer
74

Nouveau dans Python 3.7 ( bpo32677 )

Plus de contrôles ascii fastidieux / inefficaces sur les chaînes, nouvelle méthode / str/ intégrée - vérifiera si les chaînes sont ascii.bytesbytearray.isascii()

print("is this ascii?".isascii())
# True
abccd
la source
Celui-ci mérite d'être au top!
Salek
"\x03".isascii()est également vrai. La documentation indique que cela vérifie simplement que tous les caractères sont inférieurs au point de code 128 (0-127). Si vous voulez aussi éviter les caractères de contrôle, vous aurez besoin: text.isascii() and text.isprintable(). L'utilisation isprintableseule ne suffit pas non plus, car elle considérera qu'un caractère comme ¿est (correctement) imprimable, mais il ne fait pas partie de la section imprimable ascii, vous devez donc vérifier les deux si vous voulez les deux. Encore un autre problème: les espaces sont considérés comme imprimables, les tabulations et les nouvelles lignes ne le sont pas.
Luc
19

Ran dans quelque chose comme ça récemment - pour référence future

import chardet

encoding = chardet.detect(string)
if encoding['encoding'] == 'ascii':
    print 'string is in ascii'

que vous pourriez utiliser avec:

string_ascii = string.decode(encoding['encoding']).encode('ascii')
Alvin
la source
7
Bien sûr, cela nécessite la bibliothèque chardet .
StackExchange saddens dancek du
1
oui, bien que chardet soit disponible par défaut dans la plupart des installations
Alvin
7
chardet devine seulement l'encodage avec une certaine probabilité comme ceci: {'confidence': 0.99, 'encoding': 'EUC-JP'}(qui dans ce cas était complètement faux)
Suzana
19

Vincent Marchetti a la bonne idée, mais str.decodea été déprécié en Python 3. En Python 3, vous pouvez faire le même test avec str.encode:

try:
    mystring.encode('ascii')
except UnicodeEncodeError:
    pass  # string is not ascii
else:
    pass  # string is ascii

Notez que l'exception que vous souhaitez intercepter est également passée de UnicodeDecodeErrorà UnicodeEncodeError.

drs
la source
L'entrée de OP est un bytestring ( bytestapez en Python 3 qui n'a pas de .encode()méthode). .decode()dans la réponse de @Vincent Marchetti est correcte .
jfs
@JFSebastian L'OP demande "Comment vérifier si une chaîne en Python est en ASCII?" et ne spécifie pas d'octets par rapport aux chaînes unicode. Pourquoi dites-vous que sa contribution est un bytestring?
drs
1
regardez la date de la question: 'é'était un bytestring à l'époque.
jfs
1
@JFSebastian, ok, bien vu que cette réponse répond à cette question comme si elle avait été posée aujourd'hui, je pense qu'elle est toujours valable et utile. De moins en moins de personnes viendront ici chercher des réponses comme si elles utilisaient Python en 2008
drs
2
J'ai trouvé cette question lorsque je cherchais une solution pour python3 et lire rapidement la question ne m'a pas fait soupçonner qu'il s'agissait de python 2 specfic. Mais cette réponse a été vraiment utile - vote positif!
josch
17

Votre question est incorrecte; l'erreur que vous voyez n'est pas le résultat de la façon dont vous avez construit python, mais d'une confusion entre les chaînes d'octets et les chaînes unicode.

Les chaînes d'octets (par exemple "foo", ou "bar", en syntaxe python) sont des séquences d'octets; nombres de 0 à 255. Les chaînes Unicode (par exemple u "foo" ou u'bar ') sont des séquences de points de code Unicode; numéros de 0-1112064. Mais vous semblez intéressé par le caractère é, qui (dans votre terminal) est une séquence multi-octets qui représente un seul caractère.

Au lieu de cela ord(u'é'), essayez ceci:

>>> [ord(x) for x in u'é']

Cela vous indique quelle séquence de points de code "é" représente. Cela peut vous donner [233], ou cela peut vous donner [101, 770].

Au lieu d' chr()inverser cela, il y aunichr() :

>>> unichr(233)
u'\xe9'

Ce caractère peut en fait être représenté soit par un ou plusieurs "points de code" unicode, qui eux-mêmes représentent soit des graphèmes soit des caractères. Il s'agit soit de "e avec un accent aigu (c'est-à-dire, point de code 233)", soit de "e" (point de code 101), suivi de "un accent aigu sur le caractère précédent" (point de code 770). Donc, ce même caractère exact peut être présenté comme la structure de données Python u'e\u0301'ou u'\u00e9'.

La plupart du temps, vous ne devriez pas vous soucier de cela, mais cela peut devenir un problème si vous itérez sur une chaîne unicode, car l'itération fonctionne par point de code, et non par caractère décomposable. En d'autres termes, len(u'e\u0301') == 2et len(u'\u00e9') == 1. Si cela vous intéresse, vous pouvez convertir entre les formes composées et décomposées à l'aide de unicodedata.normalize.

Le glossaire Unicode peut être un guide utile pour comprendre certains de ces problèmes, en montrant comment chaque terme spécifique fait référence à une partie différente de la représentation du texte, ce qui est beaucoup plus compliqué que ne le pensent de nombreux programmeurs.

Glyphe
la source
3
'é' ne représente pas nécessairement un seul point de code. Il peut s'agir de deux points de code (U + 0065 + U + 0301).
jfs
2
Chaque caractère abstrait est toujours représenté par un seul point de code. Cependant, les points de code peuvent être codés sur plusieurs octets, selon le schéma de codage. c'est-à-dire, 'é' est deux octets en UTF-8 et UTF-16, et quatre octets en UTF-32, mais il s'agit dans chaque cas toujours d'un seul point de code - U + 00E9.
Ben Blank
5
@Ben Blank: U + 0065 et U + 0301 sont les points de code et ils ne représentent 'é' qui peut aussi être représenté par U + 00E9. Google "combinant un accent aigu".
jfs
JF a raison de combiner U + 0065 et U + 0301 pour former 'é' mais ce n'est pas un fonctino réversible. Vous obtiendrez U + 00E9. Selon wikipedia , ces points de code composites sont utiles pour la compatibilité descendante
Martin Konecny
1
@teehoo - C'est une fonction réversible dans le sens où vous pouvez normaliser à nouveau le point de code représentant le caractère composé en une séquence de points de code représentant le même caractère composé. En Python, vous pouvez le faire comme ceci: unicodedata.normalize ('NFD', u '\ xe9').
Glyph
10

Et si on faisait ça?

import string

def isAscii(s):
    for c in s:
        if c not in string.ascii_letters:
            return False
    return True
miya
la source
5
Cela échoue si votre chaîne contient des caractères ASCII qui ne sont pas des lettres. Pour vous des exemples de code, qui incluent la nouvelle ligne, l'espace, le point, la virgule, le trait de soulignement et les parenthèses.
florisla
9

J'ai trouvé cette question en essayant de déterminer comment utiliser / encoder / décoder une chaîne dont je n'étais pas sûr de l'encodage (et comment échapper / convertir des caractères spéciaux dans cette chaîne).

Ma première étape aurait dû être de vérifier le type de la chaîne - je ne savais pas que je pouvais obtenir de bonnes données sur sa mise en forme à partir des types. Cette réponse a été très utile et est arrivée à la véritable racine de mes problèmes.

Si vous obtenez un grossier et persistant

UnicodeDecodeError: le codec 'ascii' ne peut pas décoder l'octet 0xc3 en position 263: l'ordinal n'est pas dans la plage (128)

en particulier lorsque vous ENCODEZ, assurez-vous que vous n'essayez pas d'Unicode () une chaîne qui EST déjà UNICODE - pour une raison terrible, vous obtenez des erreurs de codec ascii. (Voir aussi la recette Python Kitchen et les documents Python didacticiels pour mieux comprendre à quel point cela peut être terrible.)

Finalement, j'ai déterminé que ce que je voulais faire était le suivant:

escaped_string = unicode(original_string.encode('ascii','xmlcharrefreplace'))

Le débogage a également été utile en définissant le codage par défaut de mon fichier sur utf-8 (mettez cela au début de votre fichier python):

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

Cela vous permet de tester des caractères spéciaux ('àéç') sans avoir à utiliser leurs échappements Unicode (u '\ xe0 \ xe9 \ xe7').

>>> specials='àéç'
>>> specials.decode('latin-1').encode('ascii','xmlcharrefreplace')
'&#224;&#233;&#231;'
Max P Magee
la source
4

Pour améliorer la solution d'Alexander à partir de Python 2.6 (et dans Python 3.x), vous pouvez utiliser le module d'assistance curses.ascii et utiliser la fonction curses.ascii.isascii () ou diverses autres: https://docs.python.org/2.6/ bibliothèque / curses.ascii.html

from curses import ascii

def isascii(s):
    return all(ascii.isascii(c) for c in s)
Sergey Nevmerzhitsky
la source
2

Vous pouvez utiliser la bibliothèque d'expressions régulières qui accepte la définition standard de Posix [[: ASCII:]].

Steve Moyer
la source
2

Une piqûre ( str-type) en Python est une série d'octets. Il n'y a aucun moyen de dire simplement en regardant la chaîne si cette série d'octets représente une chaîne ascii, une chaîne dans un jeu de caractères 8 bits comme ISO-8859-1 ou une chaîne codée avec UTF-8 ou UTF-16 ou autre .

Cependant, si vous connaissez l'encodage utilisé, vous pouvez decodetransformer la chaîne en chaîne unicode, puis utiliser une expression régulière (ou une boucle) pour vérifier si elle contient des caractères en dehors de la plage qui vous préoccupe.

JacquesB
la source
1

Comme la réponse de @ RogerDahl, mais il est plus efficace de court-circuiter en annulant la classe de caractères et en utilisant la recherche au lieu de find_alloumatch .

>>> import re
>>> re.search('[^\x00-\x7F]', 'Did you catch that \x00?') is not None
False
>>> re.search('[^\x00-\x7F]', 'Did you catch that \xFF?') is not None
True

J'imagine qu'une expression régulière est bien optimisée pour cela.

plaques de cuisson
la source
0
import re

def is_ascii(s):
    return bool(re.match(r'[\x00-\x7F]+$', s))

Pour inclure une chaîne vide au format ASCII, remplacez +par *.

Roger Dahl
la source
-1

Pour éviter que votre code ne plante, vous souhaiterez peut-être utiliser un try-exceptpour intercepterTypeErrors

>>> ord("¶")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: ord() expected a character, but string of length 2 found

Par exemple

def is_ascii(s):
    try:
        return all(ord(c) < 128 for c in s)
    except TypeError:
        return False

la source
Cet tryemballage est complètement inutile. Si "¶"est une chaîne Unicode, alors ord("¶")fonctionnera, et si ce n'est pas le cas (Python 2), la for c in sdécomposera en octets et ordcontinuera de fonctionner.
Ry-
-5

J'utilise ce qui suit pour déterminer si la chaîne est ascii ou unicode:

>> print 'test string'.__class__.__name__
str
>>> print u'test string'.__class__.__name__
unicode
>>> 

Utilisez ensuite un bloc conditionnel pour définir la fonction:

def is_ascii(input):
    if input.__class__.__name__ == "str":
        return True
    return False
mvknowles
la source
4
-1 AARRGGHH cela traite tous les caractères avec ord (c) dans la plage (128, 256) comme ASCII !!!
John Machin
Ça ne marche pas. Essayez d' appeler ce qui suit: is_ascii(u'i am ascii'). Même si les lettres et les espaces sont définitivement ASCII, cela revient quand même Falseparce que nous avons forcé la chaîne à l'être unicode.
jpmc26