Supprimer tout sauf les caractères alphanumériques d'une chaîne en Python

339

Quelle est la meilleure façon de supprimer tous les caractères non alphanumériques d'une chaîne en utilisant Python?

Les solutions présentées dans la variante PHP de cette question fonctionneront probablement avec quelques ajustements mineurs, mais ne me semblent pas très «pythoniques».

Pour mémoire, je ne veux pas seulement supprimer les points et les virgules (et autres signes de ponctuation), mais aussi les guillemets, les crochets, etc.

Mark van Lent
la source
8
Vous vous souciez des caractères alphanumériques internationaux, comme «æøå», «مرحبا», «สวัสดี», «こ ん に ち は»?
Pimin Konstantin Kefaloukos
4
@PiminKonstantinKefaloukos Oui, je me soucie des caractères internationaux, d'où mon commentaire sur la réponse acceptée pour utiliser re.UNICODE.
Mark van Lent

Réponses:

337

Je viens de chronométrer certaines fonctions par curiosité. Dans ces tests, je supprime les caractères non alphanumériques de la chaîne string.printable(partie du stringmodule intégré). L'utilisation de compilé '[\W_]+'et pattern.sub('', str)s'est avérée être la plus rapide.

$ python -m timeit -s \
     "import string" \
     "''.join(ch for ch in string.printable if ch.isalnum())" 
10000 loops, best of 3: 57.6 usec per loop

$ python -m timeit -s \
    "import string" \
    "filter(str.isalnum, string.printable)"                 
10000 loops, best of 3: 37.9 usec per loop

$ python -m timeit -s \
    "import re, string" \
    "re.sub('[\W_]', '', string.printable)"
10000 loops, best of 3: 27.5 usec per loop

$ python -m timeit -s \
    "import re, string" \
    "re.sub('[\W_]+', '', string.printable)"                
100000 loops, best of 3: 15 usec per loop

$ python -m timeit -s \
    "import re, string; pattern = re.compile('[\W_]+')" \
    "pattern.sub('', string.printable)" 
100000 loops, best of 3: 11.2 usec per loop
Otto Allmendinger
la source
2
Résultats très intéressants: je m'attendais à ce que les expressions régulières soient plus lentes. Fait intéressant, j'ai essayé cela avec une autre option ( valid_characters = string.ascii_letters + string.digitssuivie join(ch for ch in string.printable if ch in valid_characters)et c'était 6 microsecondes plus rapide que l' isalnum()option. Toujours bien plus lent que l'expression
régulière
+1, mesurer le temps, c'est bien! (mais dans l'avant-dernier, faites à la pattern.sub('', string.printable)place - idiot d'appeler re.sub lorsque vous avez un objet RE! -).
Alex Martelli
46
Pour mémoire: utilisez re.compile('[\W_]+', re.UNICODE)pour le rendre unicode sûr.
Mark van Lent
3
comment faites-vous sans supprimer l'espace blanc?
maudulus
6
faites-le sans supprimer l'espace blanc: re.sub ('[\ W _] +', '', phrase, flags = re.UNICODE)
PALEN
269

Expressions régulières à la rescousse:

import re
re.sub(r'\W+', '', your_string)

Par définition Python '\W== [^a-zA-Z0-9_], qui exclut tout numbers, letterset_

Fourmis Aasma
la source
2
Que fait le signe plus dans l'expression rationnelle? (Je sais ce que cela signifie, juste curieux de savoir pourquoi il est nécessaire pour le re.)
Mark van Lent
7
@Mark: J'imagine que cela accélérerait la substitution car le remplacement supprimera tous les caractères non-mots d'un bloc en une seule fois, plutôt que de les supprimer un par un.
DrAl
2
Oui, j'ai mis cela au banc d'essai tout en ajustant du code critique de performance il y a quelque temps. S'il y a des plages de caractères importantes à remplacer, l'accélération est énorme.
Ants Aasma
20
Cela peut ne pas être pertinent dans ce cas, mais \Wgardera également les soulignements.
Blixt
12
En suivant le conseil de @Blixt, si vous ne voulez que des lettres et des chiffres, vous pouvez faire re.sub (r '[^ a-zA-Z0-9]', '', your_string)
Nigini
69

Utilisez la méthode str.translate () .

En supposant que vous le ferez souvent:

(1) Une fois, créez une chaîne contenant tous les caractères que vous souhaitez supprimer:

delchars = ''.join(c for c in map(chr, range(256)) if not c.isalnum())

(2) Chaque fois que vous voulez froncer une chaîne:

scrunched = s.translate(None, delchars)

Le coût d'installation se compare probablement favorablement à re.compile; le coût marginal est bien inférieur:

C:\junk>\python26\python -mtimeit -s"import string;d=''.join(c for c in map(chr,range(256)) if not c.isalnum());s=string.printable" "s.translate(None,d)"
100000 loops, best of 3: 2.04 usec per loop

C:\junk>\python26\python -mtimeit -s"import re,string;s=string.printable;r=re.compile(r'[\W_]+')" "r.sub('',s)"
100000 loops, best of 3: 7.34 usec per loop

Remarque: L' utilisation de string.printable comme données de référence confère au modèle «[\ W _] +» un avantage injuste ; tous les caractères non alphanumériques sont dans un seul paquet ... dans les données typiques, il y aurait plus d'une substitution à faire:

C:\junk>\python26\python -c "import string; s = string.printable; print len(s),repr(s)"
100 '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'

Voici ce qui se passe si vous donnez un peu plus de travail à re.sub:

C:\junk>\python26\python -mtimeit -s"d=''.join(c for c in map(chr,range(256)) if not c.isalnum());s='foo-'*25" "s.translate(None,d)"
1000000 loops, best of 3: 1.97 usec per loop

C:\junk>\python26\python -mtimeit -s"import re;s='foo-'*25;r=re.compile(r'[\W_]+')" "r.sub('',s)"
10000 loops, best of 3: 26.4 usec per loop
John Machin
la source
1
Utiliser translate est en effet un peu plus rapide. Même lorsque vous ajoutez une boucle for juste avant de faire la substitution / traduction (pour que les coûts d'installation pèsent moins), la traduction est encore 17 fois plus rapide que l'expression régulière sur ma machine. Bon à savoir.
Mark van Lent
3
C'est certainement la solution la plus pythonique.
codygman
1
Cela m'a presque convaincu, mais je suggérerais d'utiliser string.punctuationAu lieu de''.join(c for c in map(chr, range(256)) if not c.isalnum())
ArnauOrriols
1
Notez que cela fonctionne pour les strobjets mais pas pour les unicodeobjets.
Yavar
@John Machin Est-ce essentiellement une compréhension de liste qui est passée en argument .join()?
AdjunctProfessorFalcon
42

Tu pourrais essayer:

print ''.join(ch for ch in some_string if ch.isalnum())
ars
la source
16
>>> import re
>>> string = "Kl13@£$%[};'\""
>>> pattern = re.compile('\W')
>>> string = re.sub(pattern, '', string)
>>> print string
Kl13
DéplacéAustralie
la source
1
J'ai adoré votre réponse, mais elle supprime également les caractères arabes. Pouvez-vous me dire comment les conserver?
Charif DZ
14

Que diriez-vous:

def ExtractAlphanumeric(InputString):
    from string import ascii_letters, digits
    return "".join([ch for ch in InputString if ch in (ascii_letters + digits)])

Cela fonctionne en utilisant la compréhension de liste pour produire une liste des caractères InputStrings'ils sont présents dans les chaînes combinées ascii_letterset digits. Il joint ensuite la liste en une chaîne.

DrAl
la source
Il semble que string.ascii_letters ne contienne que des lettres (duh) et non des chiffres. J'ai aussi besoin des chiffres ...
Mark van Lent
L'ajout de string.digits résoudrait en effet le problème que je viens de mentionner. :)
Mark van Lent
Oui, je m'en suis rendu compte lorsque je suis revenu lire votre question. Note à soi: apprends à lire!
DrAl
5

Comme dérivé de quelques autres réponses ici, je propose un moyen très simple et flexible de définir un ensemble de caractères auquel vous souhaitez limiter le contenu d'une chaîne. Dans ce cas, j'autorise le tiret et le soulignement alphanumériques PLUS. Ajoutez ou supprimez simplement des caractères de mon PERMITTED_CHARSselon votre cas d'utilisation.

PERMITTED_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-" 
someString = "".join(c for c in someString if c in PERMITTED_CHARS)
BuvinJ
la source
3
Au lieu de coder en dur les caractères autorisés, qui sont sujets à des erreurs subtiles, utilisez string.digits + string.ascii_letters + '_-'.
Reti43
Votre suggestion n'est pas fausse, mais elle n'enregistre pas non plus beaucoup de caractères de "saisie" si tel est votre objectif. Si vous copiez mon article, vous n'aurez pas non plus de faute de frappe! Le vrai point, cependant, de ma réponse est de permettre un moyen explicite, ouvert et simple de définir exactement les caractères que vous souhaitez autoriser.
BuvinJ
En tant que terrain d'entente, vous pouvez combiner ces suggestions dans SPECIAL_CHARS = '_-'puis utiliserstring.digits + string.ascii_letters + SPECIAL_CHARS
BuvinJ
C'était une suggestion en termes de ce qui est raisonnable, sauf si nous faisons du golf de code. "Marcher" autour du clavier pour taper 52 lettres de l'alphabet dans l'ordre prend beaucoup plus de temps que d'importer un package pour utiliser un ou deux objets. Et cela n'inclut pas le temps de vérifier que vous avez tout tapé correctement. Il s'agit de bonnes pratiques, c'est tout.
Reti43
Je t'entends! Mon vrai point ici est une flexibilité extrême, au cas où vous voudriez être plus précis avec votre jeu de caractères.
BuvinJ
5
sent = "".join(e for e in sent if e.isalpha())
Tom Kalvijn
la source
Je vais essayer d'expliquer: il passe par tous les caractères de chaîne dans e for e in sentet vérifie via l' if e.isalpha()instruction si le caractère actuel est un symbole alphabétique, si c'est le cas - le joint à la sentvariable via sent = "".join()et tous les symboles non alphabétiques seront remplacés par ""(chaîne vide) car de joinfonction.
Sysanin
puisque cela fait une boucle par caractère plutôt que de compter sur C regex, n'est-ce pas extrêmement lent?
dcsan
3
for char in my_string:
    if not char.isalnum():
        my_string = my_string.replace(char,"")
Junior Ogun
la source
2

Synchronisation avec des chaînes aléatoires d'imprimables ASCII:

from inspect import getsource
from random import sample
import re
from string import printable
from timeit import timeit

pattern_single = re.compile(r'[\W]')
pattern_repeat = re.compile(r'[\W]+')
translation_tb = str.maketrans('', '', ''.join(c for c in map(chr, range(256)) if not c.isalnum()))


def generate_test_string(length):
    return ''.join(sample(printable, length))


def main():
    for i in range(0, 60, 10):
        for test in [
            lambda: ''.join(c for c in generate_test_string(i) if c.isalnum()),
            lambda: ''.join(filter(str.isalnum, generate_test_string(i))),
            lambda: re.sub(r'[\W]', '', generate_test_string(i)),
            lambda: re.sub(r'[\W]+', '', generate_test_string(i)),
            lambda: pattern_single.sub('', generate_test_string(i)),
            lambda: pattern_repeat.sub('', generate_test_string(i)),
            lambda: generate_test_string(i).translate(translation_tb),

        ]:
            print(timeit(test), i, getsource(test).lstrip('            lambda: ').rstrip(',\n'), sep='\t')


if __name__ == '__main__':
    main()

Résultat (Python 3.7):

       Time       Length                           Code                           
6.3716264850008880  00  ''.join(c for c in generate_test_string(i) if c.isalnum())
5.7285426190064750  00  ''.join(filter(str.isalnum, generate_test_string(i)))
8.1875841680011940  00  re.sub(r'[\W]', '', generate_test_string(i))
8.0002205439959650  00  re.sub(r'[\W]+', '', generate_test_string(i))
5.5290945199958510  00  pattern_single.sub('', generate_test_string(i))
5.4417179649972240  00  pattern_repeat.sub('', generate_test_string(i))
4.6772285089973590  00  generate_test_string(i).translate(translation_tb)
23.574712151996210  10  ''.join(c for c in generate_test_string(i) if c.isalnum())
22.829975890002970  10  ''.join(filter(str.isalnum, generate_test_string(i)))
27.210196289997840  10  re.sub(r'[\W]', '', generate_test_string(i))
27.203713296003116  10  re.sub(r'[\W]+', '', generate_test_string(i))
24.008979928999906  10  pattern_single.sub('', generate_test_string(i))
23.945240008994006  10  pattern_repeat.sub('', generate_test_string(i))
21.830899796994345  10  generate_test_string(i).translate(translation_tb)
38.731336012999236  20  ''.join(c for c in generate_test_string(i) if c.isalnum())
37.942474347000825  20  ''.join(filter(str.isalnum, generate_test_string(i)))
42.169366310001350  20  re.sub(r'[\W]', '', generate_test_string(i))
41.933375883003464  20  re.sub(r'[\W]+', '', generate_test_string(i))
38.899814646996674  20  pattern_single.sub('', generate_test_string(i))
38.636144253003295  20  pattern_repeat.sub('', generate_test_string(i))
36.201238164998360  20  generate_test_string(i).translate(translation_tb)
49.377356811004574  30  ''.join(c for c in generate_test_string(i) if c.isalnum())
48.408927293996385  30  ''.join(filter(str.isalnum, generate_test_string(i)))
53.901889764994850  30  re.sub(r'[\W]', '', generate_test_string(i))
52.130339455994545  30  re.sub(r'[\W]+', '', generate_test_string(i))
50.061149017004940  30  pattern_single.sub('', generate_test_string(i))
49.366573111998150  30  pattern_repeat.sub('', generate_test_string(i))
46.649754120997386  30  generate_test_string(i).translate(translation_tb)
63.107938601999194  40  ''.join(c for c in generate_test_string(i) if c.isalnum())
65.116287978999030  40  ''.join(filter(str.isalnum, generate_test_string(i)))
71.477421126997800  40  re.sub(r'[\W]', '', generate_test_string(i))
66.027950693998720  40  re.sub(r'[\W]+', '', generate_test_string(i))
63.315361931003280  40  pattern_single.sub('', generate_test_string(i))
62.342320287003530  40  pattern_repeat.sub('', generate_test_string(i))
58.249303059004890  40  generate_test_string(i).translate(translation_tb)
73.810345625002810  50  ''.join(c for c in generate_test_string(i) if c.isalnum())
72.593953348005020  50  ''.join(filter(str.isalnum, generate_test_string(i)))
76.048324580995540  50  re.sub(r'[\W]', '', generate_test_string(i))
75.106637657001560  50  re.sub(r'[\W]+', '', generate_test_string(i))
74.681338128997600  50  pattern_single.sub('', generate_test_string(i))
72.430461594005460  50  pattern_repeat.sub('', generate_test_string(i))
69.394243567003290  50  generate_test_string(i).translate(translation_tb)

str.maketrans& str.translateest le plus rapide, mais inclut tous les caractères non ASCII. re.compile& pattern.subest plus lent, mais est en quelque sorte plus rapide que ''.join& filter.

Solomon Ucko
la source
-1

Si j'ai bien compris, la façon la plus simple est d'utiliser l'expression régulière car elle vous offre beaucoup de flexibilité, mais l'autre méthode simple consiste à utiliser pour le suivi de boucle est le code avec l'exemple, j'ai également compté l'occurrence de mot et stocké dans le dictionnaire ..

s = """An... essay is, generally, a piece of writing that gives the author's own 
argument — but the definition is vague, 
overlapping with those of a paper, an article, a pamphlet, and a short story. Essays 
have traditionally been 
sub-classified as formal and informal. Formal essays are characterized by "serious 
purpose, dignity, logical 
organization, length," whereas the informal essay is characterized by "the personal 
element (self-revelation, 
individual tastes and experiences, confidential manner), humor, graceful style, 
rambling structure, unconventionality 
or novelty of theme," etc.[1]"""

d = {}      # creating empty dic      
words = s.split() # spliting string and stroing in list
for word in words:
    new_word = ''
    for c in word:
        if c.isalnum(): # checking if indiviual chr is alphanumeric or not
            new_word = new_word + c
    print(new_word, end=' ')
    # if new_word not in d:
    #     d[new_word] = 1
    # else:
    #     d[new_word] = d[new_word] +1
print(d)

s'il vous plaît noter cela si cette réponse est utile!

Abhishek Pratap Singh
la source