96 votes

Suppression des caractères non imprimables d'une chaîne de caractères en python

J'avais l'habitude de courir

$s =~ s/[^[:print:]]//g;

sur Perl pour se débarrasser des caractères non imprimables.

En Python, il n'y a pas de classes POSIX regex, et je ne peux pas écrire [:print :] pour que cela signifie ce que je veux. Je ne connais aucun moyen en Python de détecter si un caractère est imprimable ou non.

Que feriez-vous ?

EDIT : Il doit également supporter les caractères Unicode. La méthode string.printable les supprimera volontiers de la sortie. curses.ascii.isprint retournera false pour tout caractère unicode.

90voto

Ants Aasma Points 22921

L'itération sur les chaînes de caractères est malheureusement assez lente en Python. Les expressions régulières sont plus rapides d'un ordre de grandeur pour ce genre de choses. Il suffit de construire soi-même la classe de caractères. Le site unicode data est très utile pour cela, notamment le module unicodedata.catégorie() fonction. Voir Base de données des caractères Unicode pour la description des catégories.

import unicodedata, re, itertools, sys

all_chars = (chr(i) for i in range(sys.maxunicode))
categories = {'Cc'}
control_chars = ''.join(c for c in all_chars if unicodedata.category(c) in categories)
# or equivalently and much more efficiently
control_chars = ''.join(map(chr, itertools.chain(range(0x00,0x20), range(0x7f,0xa0))))

control_char_re = re.compile('[%s]' % re.escape(control_chars))

def remove_control_chars(s):
    return control_char_re.sub('', s)

Pour Python2

import unicodedata, re, sys

all_chars = (unichr(i) for i in xrange(sys.maxunicode))
categories = {'Cc'}
control_chars = ''.join(c for c in all_chars if unicodedata.category(c) in categories)
# or equivalently and much more efficiently
control_chars = ''.join(map(unichr, range(0x00,0x20) + range(0x7f,0xa0)))

control_char_re = re.compile('[%s]' % re.escape(control_chars))

def remove_control_chars(s):
    return control_char_re.sub('', s)

Pour certains cas d'utilisation, il est nécessaire d'ajouter des catégories supplémentaires (par exemple, toutes celles de la catégorie contrôle peut être préférable, bien que cela puisse ralentir le temps de traitement et augmenter considérablement l'utilisation de la mémoire. Nombre de caractères par catégorie :

  • Cc (contrôle) : 65
  • Cf (format) : 161
  • Cs (substitut) : 2048
  • Co (usage privé) : 137468
  • Cn (non attribué) : 836601

Modifier Ajout de suggestions à partir des commentaires.

4 votes

Est-ce que "Cc" suffit ici ? Je ne sais pas, je pose simplement la question. Il me semble que certaines des autres catégories "C" pourraient également être candidates à ce filtre.

0 votes

Ce code ne fonctionne pas en 2.6 ou 3.2, dans quelle version fonctionne-t-il ?

1 votes

Cette fonction, telle que publiée, supprime la moitié des caractères hébreux. J'obtiens le même effet avec les deux méthodes données.

78voto

William Keller Points 2845

Pour autant que je sache, la méthode la plus pythique/efficace serait :

import string

filtered_string = filter(lambda x: x in string.printable, myStr)

12 votes

Vous voulez probablement que la chaîne filtrée = ''.join(filter(lambda x:x in string.printable, myStr) de sorte que vous obteniez une chaîne.

15 votes

Malheureusement, string.printable ne contient pas de caractères unicode, et donc ü ou ó ne seront pas dans la sortie... peut-être y a-t-il autre chose ?

17 votes

Vous devriez utiliser une compréhension de liste ou des expressions génératrices, et non pas un filtre + lambda. L'une de ces méthodes sera plus rapide dans 99,9 % des cas. ''.join(s for s in myStr if s in string.printable)

20voto

Ber Points 10364

Vous pouvez essayer de mettre en place un filtre en utilisant la fonction unicodedata.category() fonction :

import unicodedata
printable = {'Lu', 'Ll'}
def filter_non_printable(str):
  return ''.join(c for c in str if unicodedata.category(c) in printable)

Voir le tableau 4-9 à la page 175 dans le Propriétés des caractères de la base de données Unicode pour les catégories disponibles

0 votes

Vous avez commencé une compréhension de la liste qui ne s'est pas terminée par votre dernière ligne. Je vous suggère de supprimer complètement la parenthèse ouvrante.

0 votes

Merci de l'avoir signalé. J'ai modifié le message en conséquence.

1 votes

Cette méthode semble la plus directe et la plus simple. Merci.

6voto

Kirk Strauser Points 12087

Cette fonction utilise les compréhensions de listes et str.join, elle s'exécute donc en temps linéaire au lieu de O(n^2) :

from curses.ascii import isprint

def printable(input):
    return ''.join(char for char in input if isprint(char))

2voto

Vinko Vrsalovic Points 116138

La meilleure solution que j'ai trouvée est (grâce aux pythonistes ci-dessus)

def filter_non_printable(str):
  return ''.join([c for c in str if ord(c) > 31 or ord(c) == 9])

C'est la seule méthode que j'ai trouvée qui fonctionne avec les caractères/chaînes Unicode.

De meilleures options ?

1 votes

À moins que vous n'utilisiez Python 2.3, les []s internes sont redondants. "return ''.join(c for c ...)"

0 votes

Pas tout à fait redondants - ils ont des significations (et des caractéristiques de performance) différentes, bien que le résultat final soit le même.

0 votes

L'autre extrémité de l'intervalle ne devrait-elle pas être protégée aussi? : "ord(c) <= 126"

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X