Eliminarea caracterelor neimprimabile dintr-un șir de caractere în python (Programare, Python, String, Neimprimabil)

Vinko Vrsalovic a intrebat.
a intrebat.

Eu folosesc pentru a rula

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

pe Perl pentru a scăpa de caracterele neimprimabile.

În Python nu există clase de regex POSIX și nu pot scrie [:print:] având sensul pe care îl doresc. Nu cunosc nicio modalitate în Python de a detecta dacă un caracter este imprimabil sau nu.

Voi ce ați face?

EDIT: Trebuie să suporte și caractere Unicode. Modalitatea string.printable le va elimina cu plăcere din ieșire. curses.ascii.isprint va returna false pentru orice caracter Unicode.

13 răspunsuri
Ants Aasma

Iterarea peste șiruri de caractere este, din păcate, destul de lentă în Python. Expresiile regulate sunt peste un ordin de mărime mai rapide pentru acest tip de lucru. Trebuie doar să construiți singur clasa de caractere. Adresa unicodedata este destul de util în acest sens, în special modulul unicodedata.category() . A se vedea Baza de date de caractere Unicode pentru descrierea categoriilor.

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)

Pentru 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)

Pentru anumite cazuri de utilizare, se pot adăuga categorii suplimentare (de exemplu, toate categoriile de la control grup) ar putea fi preferabile, deși acest lucru ar putea încetini timpul de procesare și crește semnificativ utilizarea memoriei. Numărul de caractere pe categorie:

  • Cc (control): 65
  • Cf (format): 161
  • Cs (surogat): 2048
  • Co (utilizare privată): 137468
  • Cn (neatribuit): 836601

Editați Adăugarea sugestiilor din comentarii.

Comentarii

  • Este „Cc” suficient aici? Nu știu, doar întreb – mi se pare că și unele dintre celelalte categorii ‘C’ ar putea fi candidate pentru acest filtru. –  > Por Patrick Johnmeyer.
  • Această funcție, așa cum a fost publicată, elimină jumătate din caracterele ebraice. Obțin același efect pentru ambele metode prezentate. –  > Por dotancohen.
  • Din punct de vedere al performanței, nu ar funcționa mai rapid string.translate() în acest caz? Vedeți stackoverflow.com/questions/265960/…  > Por Kashyap.
  • Utilizați all_chars = (unichr(i) for i in xrange(sys.maxunicode)) pentru a evita eroarea de compilare îngustă. –  > Por danmichaelo.
  • Pentru mine control_chars == 'x00-x1fx7f-x9f' (testat pe Python 3.5.2) –  > Por AXO.
William Keller

Din câte știu eu, cea mai pythonică/eficientă metodă ar fi:

import string

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

Comentarii

  • Probabil că doriți ca șirul_filtrat = ”.join(filter(lambda x:x in string.printable, myStr) astfel încât să obțineți înapoi un șir. –  > Por Nathan Shively-Sanders.
  • 15

  • Din păcate, string.printable nu conține caractere unicode și, prin urmare, ü sau ó nu vor fi în ieșire… poate că mai este ceva? –  > Por Vinko Vrsalovic.
  • 17

  • Ar trebui să folosiți o înțelegere de listă sau expresii generatoare, nu filtru + lambda. Una dintre acestea va fi mai rapidă în 99,9% din cazuri. ”.join(s for s in myStr if s in string.printable) –  > Por habnabit.
  • @AaronGallagher: 99,9% mai rapid? De unde ai scos această cifră? Comparația de performanță nu este nici pe departe atât de rea. –  > Por Chris Morgan.
  • Bună, William. Această metodă pare să elimine toate caracterele non-ASCII. Există multe caractere non-ASCII imprimabile în Unicode! –  > Por dotancohen.
Ber

Ați putea încerca să configurați un filtru folosind unicodedata.category() funcție:

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

Consultați Tabelul 4-9 de la pagina 175 din Proprietățile caracterelor din baza de date Unicode pentru categoriile disponibile

Comentarii

  • ați început o înțelegere a listei care nu s-a încheiat în linia finală. Vă sugerez să eliminați complet paranteza de deschidere. –  > Por tzot.
  • Mulțumesc că ați semnalat acest lucru. Am editat postarea în consecință –  > Por Ber.
  • Aceasta pare a fi cea mai directă, cea mai simplă metodă. Mulțumesc. –  > Por dotancohen.
  • @CsabaToth Toate cele trei sunt valabile și dau același set. Metoda ta este poate cel mai frumos mod de a specifica un literal de set. –  > Por Ber.
  • @AnubhavJhalani Puteți adăuga mai multe categorii Unicode la filtru. Pentru a rezerva spații și cifre în plus față de litere, utilizați printable = {'Lu', 'Ll', Zs', 'Nd'} –  > Por Ber.
shawnrad

În Python 3,

def filter_nonprintable(text):
    import itertools
    # Use characters of control category
    nonprintable = itertools.chain(range(0x00,0x20),range(0x7f,0xa0))
    # Use translate to remove all non-printable characters
    return text.translate({character:None for character in nonprintable})

Consultați acest post de pe StackOverflow despre eliminarea punctuației pentru a vedea cum se compară .translate() cu regex & .replace()

Domeniile pot fi generate prin nonprintable = (ord(c) for c in (chr(i) for i in range(sys.maxunicode)) if unicodedata.category(c)=='Cc') utilizând categoriile din baza de date de caractere Unicode așa cum a arătat @Ants Aasma.

Comentarii

  • Ar fi mai bine să folosiți intervale Unicode (a se vedea răspunsul lui @Ants Aasma). Rezultatul ar fi text.translate({c:None for c in itertools.chain(range(0x00,0x20),range(0x7f,0xa0))}). –  > Por darkdragon.
ChrisP

Următorul va funcționa cu intrare Unicode și este destul de rapid…

import sys

# build a table mapping all non-printable characters to None
NOPRINT_TRANS_TABLE = {
    i: None for i in range(0, sys.maxunicode + 1) if not chr(i).isprintable()
}

def make_printable(s):
    """Replace non-printable characters in a string."""

    # the translate method on str removes characters
    # that map to None from the string
    return s.translate(NOPRINT_TRANS_TABLE)


assert make_printable('Café') == 'Café'
assert make_printable('x00x11Hello') == 'Hello'
assert make_printable('') == ''

Propriile mele teste sugerează că această abordare este mai rapidă decât funcțiile care iterați peste șirul de caractere și returnează un rezultat folosind str.join.

Comentarii

  • Acesta este singurul răspuns care funcționează pentru mine cu caractere Unicode. Minunat că ați furnizat cazuri de testare! –  > Por pir.
  • Dacă doriți să permiteți întreruperi de linie, adăugați LINE_BREAK_CHARACTERS = set(["
    ", "r"])
    și and not chr(i) in LINE_BREAK_CHARACTERS atunci când construiți tabelul. –  > Por pir.
Kirk Strauser

Această funcție utilizează înțelegerea listelor și str.join, astfel încât se execută în timp liniar în loc de O(n^2):

from curses.ascii import isprint

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

c6401

Încă o opțiune în python 3:

re.sub(f'[^{re.escape(string.printable)}]', '', my_string)

Comentarii

  • Acest lucru a funcționat super grozav pentru mine și are 1 linie. mulțumesc –  > Por Chop Labalagun.
  • dintr-un motiv oarecare acest lucru funcționează foarte bine pe Windows, dar nu-l pot folosi pe Linux, a trebuit să schimb f cu un r, dar nu sunt sigur că aceasta este soluția. –  > Por Chop Labalagun.
  • Se pare că Python-ul tău Linux a fost prea vechi pentru a suporta f-strings atunci. r-strings sunt destul de diferite, deși ai putea spune că r'[^' + re.escape(string.printable) + r']'. (Nu cred că re.escape() este în întregime corect aici, dar dacă funcționează…) –  > Por tripleee.
  • Din păcate, string.printable nu conține caractere Unicode și, prin urmare, ü sau ó nu se vor regăsi în rezultat… –  > Por the_economist.
Vinko Vrsalovic

Cel mai bun lucru pe care l-am găsit acum este (mulțumită python-izatorilor de mai sus)

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

Aceasta este singura modalitate pe care am găsit-o care funcționează cu caractere/șiruri Unicode

Vreo opțiune mai bună?

Comentarii

  • Cu excepția cazului în care nu sunteți pe python 2.3, []s interioare sunt redundante. „return ”.join(c for c …)” –  > Por habnabit.
  • Nu este chiar redundant – au semnificații diferite (și caracteristici de performanță), deși rezultatul final este același. –  > Por Miles.
  • Nu ar trebui ca și celălalt capăt al intervalului să fie protejat?..: „ord(c) <= 126” –  > Por Gearoid Murphy.
  • Dar există și caractere Unicode care nu sunt imprimabile. –  > Por tripleee.
Nilav Baran Ghosh

Cel de mai jos se comportă mai rapid decât celelalte de mai sus. Aruncați o privire

''.join([x if x in string.printable else '' for x in Str])

Comentarii

  • "".join([c if 0x21<=ord(c) and ord(c)<=0x7e else "" for c in ss]) –  > Por evandrix.
Risadinha

În Python nu există clase de regex POSIX

Există atunci când se utilizează regex bibliotecă: https://pypi.org/project/regex/

Este bine întreținută și suportă Unicode regex, Posix regex și multe altele. Utilizarea (semnăturile metodelor) este foarte bună similară cu cea din Python re.

Din documentație:

[[:alpha:]]; [[:^alpha:]]

Sunt acceptate clasele de caractere POSIX. Acestea sunt tratate în mod normal ca o formă alternativă de p{...}.

(Nu sunt afiliat, sunt doar un utilizator.)

darkdragon

Pe baza răspunsului lui @Ber, sugerez să eliminați doar caracterele de control așa cum sunt definite în categoriile din baza de date de caractere Unicode:

import unicodedata
def filter_non_printable(s):
    return ''.join(c for c in s if not unicodedata.category(c).startswith('C'))

Comentarii

  • Acesta este un răspuns excelent! –  > Por tdc.
  • S-ar putea să aveți dreptate cu startswith('C') dar, în testele mele, aceasta a fost mult mai puțin performantă decât orice altă soluție. –  > Por Big McLargeHuge.
  • big-mclargehuge: Scopul soluției mele a fost combinația de completitudine și simplitate/ lizibilitate. Ați putea încerca să folosiți if unicodedata.category(c)[0] != 'C' în schimb. Are o performanță mai bună? Dacă preferați viteza de execuție în detrimentul cerințelor de memorie, se poate precalcula tabelul, așa cum se arată în stackoverflow.com/a/93029/3779655 –  > Por darkdragon.
knowingpark

Pentru a elimina „spațiile albe”,

import re
t = """

t<p>&nbsp;</p>
t<p>&nbsp;</p>
t<p>&nbsp;</p>
t<p>&nbsp;</p>
t<p>
"""
pat = re.compile(r'[t
]')
print(pat.sub("", t))

Comentarii

  • De fapt, nu aveți nevoie nici de parantezele pătrate atunci. –  > Por tripleee.
Joe

Adaptat după răspunsurile lui Ants Aasma și shawnrad:

nonprintable = set(map(chr, list(range(0,32)) + list(range(127,160))))
ord_dict = {ord(character):None for character in nonprintable}
def filter_nonprintable(text):
    return text.translate(ord_dict)

#use
str = "this is my string"
str = filter_nonprintable(str)
print(str)

testat pe Python 3.7.7