Python'da bir dizeden yazdırılamayan karakterleri çıkarma


91

Koşmak için kullanırım

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

Yazdırılamayan karakterlerden kurtulmak için Perl'de.

Python'da POSIX regex sınıfı yoktur ve istediğim anlamına gelen [: print:] yazamıyorum. Python'da bir karakterin yazdırılabilir olup olmadığını algılamanın bir yolunu bilmiyorum.

Sen ne yapardın?

DÜZENLEME: Unicode karakterlerini de desteklemesi gerekir. String.printable yolu onları mutlu bir şekilde çıktıdan çıkaracaktır. curses.ascii.isprint, herhangi bir unicode karakter için yanlış döndürür.

Yanıtlar:


85

Python'da dizelerin üzerinde yineleme yapmak maalesef oldukça yavaştır. Düzenli ifadeler bu tür şeyler için çok daha hızlıdır. Sadece karakter sınıfını kendiniz oluşturmalısınız. Unicodedata modül, özellikle bunun için oldukça yararlıdır unicodedata.category () fonksiyonu. Kategorilerin açıklamaları için Unicode Karakter Veritabanı'na bakın .

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)

Python2 için

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)

Bazı kullanım durumları için, ek kategoriler (örneğin, kontrol grubundaki tümü tercih edilebilir, ancak bu, işlem süresini yavaşlatabilir ve bellek kullanımını önemli ölçüde artırabilir. Kategori başına karakter sayısı:

  • Cc (kontrol): 65
  • Cf (biçim): 161
  • Cs (vekil): 2048
  • Co (özel kullanım): 137468
  • Cn (belirtilmemiş): 836601

Düzenleme yorumlardan önerilerini ekleme.


4
Burada 'Cc' yeterli mi? Bilmiyorum, sadece soruyorum - bana öyle geliyor ki, diğer 'C' kategorilerinden bazıları da bu filtre için aday olabilir.
Patrick Johnmeyer

1
Bu işlev, yayınlandığı şekliyle, İbranice karakterlerin yarısını kaldırır. Verilen her iki yöntem için de aynı etkiyi elde ediyorum.
dotancohen

1
Performans açısından, bu durumda string.translate () daha hızlı çalışmaz mı? Bkz stackoverflow.com/questions/265960/...
Kashyap

3
all_chars = (unichr(i) for i in xrange(sys.maxunicode))Dar yapı hatasını önlemek için kullanın .
danmichaelo

4
Benim için control_chars == '\x00-\x1f\x7f-\x9f'(Python 3.5.2 üzerinde test edildi)
AXO

74

Bildiğim kadarıyla, en pitonik / verimli yöntem şöyle olurdu:

import string

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

10
Muhtemelen filtered_string = '' .join (filter (lambda x: x in string.printable, myStr) istiyorsunuz, böylece bir string geri döndünüz.
Nathan Shively-Sanders

12
Maalesef string.printable unicode karakterleri içermiyor ve bu nedenle çıktıda ü veya ó olmayacak ... belki başka bir şey olabilir?
Vinko Vrsalovic

17
Filter + lambda değil, bir liste anlama veya oluşturucu ifadeler kullanmalısınız. Bunlardan biri% 99,9 oranında daha hızlı olacaktır. '' .join (s for s for myStr if s in string.printable)
habnabit

3
@AaronGallagher:% 99.9 daha hızlı mı? Bu rakamı nereden alıyorsun? Performans karşılaştırması o kadar da kötü değil.
Chris Morgan

4
Merhaba William. Bu yöntem, ASCII olmayan tüm karakterleri kaldırıyor gibi görünüyor. Unicode'da birçok yazdırılabilir ASCII olmayan karakter vardır!
dotancohen

17

Şu unicodedata.category()işlevi kullanarak bir filtre oluşturmayı deneyebilirsiniz :

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

Kullanılabilir kategoriler için Unicode veritabanı karakter özelliklerinde Tablo 4-9 sayfa 175'e bakın.


son satırınızda bitmeyen bir liste anlayışına başladınız. Açma braketini tamamen çıkarmanızı öneririm.
tzot

Bunu işaret ettiğiniz için teşekkür ederim.
Gönderiyi

1
Bu, en doğrudan ve en basit yöntem gibi görünüyor. Teşekkürler.
dotancohen

1
@CsabaToth Üçü de geçerlidir ve aynı seti verir. Sizinki, bir set değişmezi belirlemenin belki de en güzel yoludur.
Ber

1
@AnubhavJhalani Filtreye daha fazla Unicode kategorisi ekleyebilirsiniz. Harf kullanımına ek olarak boşluk ve rakam ayırmak içinprintable = {'Lu', 'Ll', Zs', 'Nd'}
Ber

11

Python 3'te,

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

.Translate () işlevinin regex ve .replace () ile nasıl karşılaştırıldığını öğrenmek için noktalama işaretlerini kaldırmayla ilgili bu StackOverflow gönderisine bakın.

Aralıklar, @Ants Aasma tarafından gösterildiği gibi Unicode karakter veritabanı kategorilerinonprintable = (ord(c) for c in (chr(i) for i in range(sys.maxunicode)) if unicodedata.category(c)=='Cc') kullanılarak oluşturulabilir .


Unicode aralıklarını kullanmak daha iyi olur (bkz @Ants Aasma'nın cevabı). Sonuç olacaktır text.translate({c:None for c in itertools.chain(range(0x00,0x20),range(0x7f,0xa0))}).
darkdragon

9

Aşağıdakiler Unicode girişi ile çalışacaktır ve oldukça hızlıdır ...

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('\x00\x11Hello') == 'Hello'
assert make_printable('') == ''

Kendi testlerim, bu yaklaşımın dizi üzerinde yinelenen ve kullanarak bir sonuç döndüren işlevlerden daha hızlı olduğunu gösteriyor str.join.


Bu benim için unicode karakterlerle çalışan tek cevaptır. Test durumları sağlamış olmanız harika!
pir

1
Satır sonlarına izin vermek istiyorsanız , tabloyu oluştururken LINE_BREAK_CHARACTERS = set(["\n", "\r"])ve ekleyin and not chr(i) in LINE_BREAK_CHARACTERS.
pir

5

Bu işlev liste anlamalarını ve str.join'i kullanır, bu nedenle O (n ^ 2) yerine doğrusal zamanda çalışır:

from curses.ascii import isprint

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

2
filter(isprint,input)
2013

5

Yine python 3'te başka bir seçenek:

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

Bu benim için ve onun 1. satırı için süper harika çalıştı. teşekkürler
Chop Labalagun

1
Bazı nedenlerden dolayı bu, pencerelerde harika çalışıyor, ancak linux'ta kullanamıyorum, r için f'yi değiştirmek zorunda kaldım ancak çözümün bu olduğundan emin değilim.
Chop Labalagun

Linux Python'unuz o zamanlar f-dizelerini destekleyemeyecek kadar eski gibi görünüyor. r-dizeleri oldukça farklıdır, diyebilirsiniz r'[^' + re.escape(string.printable) + r']'. ( re.escape()Burada tamamen doğru olduğunu sanmıyorum , ama işe yararsa ...)
üçlü

2

Şu anda bulduğum en iyi şey (yukarıdaki piton düzenleyiciler sayesinde)

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

Unicode karakterleri / dizeleri ile çalıştığını bulduğum tek yol bu

Daha iyi seçenek var mı?


1
Python 2.3'te değilseniz, iç [] ler gereksizdir. "return" .join (c ... için) "
habnabit

Tamamen gereksiz değil — nihai sonuç aynı olsa da, farklı anlamları (ve performans özellikleri) vardır.
Mil

Aralığın diğer ucu da korunmasın mı ?: "ord (c) <= 126"
Gearoid Murphy

7
Ancak yazdırılamayan Unicode karakterler de var.
üçlü

2

Aşağıdaki, yukarıdakilerden daha hızlı performans gösterir. Bir göz at

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

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

2

Python'da POSIX normal ifade sınıfı yoktur

regexKitaplığı kullanırken : https://pypi.org/project/regex/

İyi korunur ve Unicode regex, Posix regex ve daha fazlasını destekler. Kullanım (yöntem imzaları) Python'unkine çok benzer re.

Belgelerden:

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

POSIX karakter sınıfları desteklenir. Bunlar normalde alternatif bir biçim olarak ele alınır \p{...}.

(Bağlı değilim, sadece bir kullanıcı.)


2

@ Ber'in cevabına dayanarak, yalnızca Unicode karakter veritabanı kategorilerinde tanımlanan kontrol karakterlerini kaldırmanızı öneririm :

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

Bu harika bir cevap!
tdc

Bununla ilgili bir şey olabilir, startswith('C')ancak bu benim testlerimde diğer çözümlerden çok daha az performans gösterdi.
Big McLargeHuge

big-mclargehuge: Çözümümün amacı, bütünlük ve basitlik / okunabilirlik kombinasyonuydu. Onun if unicodedata.category(c)[0] != 'C'yerine kullanmayı deneyebilirsin . Daha iyi performans gösteriyor mu? Yürütme hızını bellek gereksinimlerine göre tercih ediyorsanız, tabloyu stackoverflow.com/a/93029/3779655
darkdragon

0

'Beyaz boşluk'u kaldırmak için,

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

Aslında o zaman köşeli parantezlere de ihtiyacınız yok.
üçlü

0

Ants Aasma ve shawnrad'ın yanıtlarından uyarlanmıştır :

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)

Python 3.7.7'de test edildi

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.