Menghapus karakter yang tidak dapat dicetak dari string dengan python

91

Aku biasa lari

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

di Perl untuk menyingkirkan karakter yang tidak dapat dicetak.

Dalam Python tidak ada kelas regex POSIX, dan saya tidak bisa menulis [: print:] yang berarti apa yang saya inginkan. Saya tidak tahu cara menggunakan Python untuk mendeteksi apakah suatu karakter dapat dicetak atau tidak.

Apa yang akan kamu lakukan?

EDIT: Itu harus mendukung karakter Unicode juga. Cara string.printable dengan senang hati akan menghapusnya dari output. curses.ascii.isprint akan mengembalikan false untuk semua karakter unicode.

Vinko Vrsalovic
sumber

Jawaban:

85

Sayangnya, pengulangan string agak lambat di Python. Ekspresi reguler melebihi urutan besarnya lebih cepat untuk hal semacam ini. Anda hanya perlu membangun kelas karakter sendiri. The unicodedata modul cukup membantu untuk ini, terutama unicodedata.category () fungsi. Lihat Database Karakter Unicode untuk penjelasan tentang kategori.

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)

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

Untuk beberapa kasus penggunaan, kategori tambahan (mis. Semua dari grup kontrol mungkin lebih disukai, meskipun ini mungkin memperlambat waktu pemrosesan dan meningkatkan penggunaan memori secara signifikan. Jumlah karakter per kategori:

  • Cc (kontrol): 65
  • Cf (format): 161
  • Cs (pengganti): 2048
  • Co (penggunaan pribadi): 137468
  • Cn (belum digunakan): 836601

Edit Menambahkan saran dari komentar.

Semut Aasma
sumber
4
Apakah 'Cc' cukup di sini? Saya tidak tahu, saya hanya bertanya - menurut saya beberapa kategori 'C' lainnya mungkin juga menjadi kandidat untuk filter ini.
Patrick Johnmeyer
1
Fungsi ini, seperti yang diterbitkan, menghapus setengah dari karakter Ibrani. Saya mendapatkan efek yang sama untuk kedua metode yang diberikan.
dotancohen
1
Dari perspektif kinerja, bukankah string.translate () akan bekerja lebih cepat dalam kasus ini? Lihat stackoverflow.com/questions/265960/…
Kashyap
3
Gunakan all_chars = (unichr(i) for i in xrange(sys.maxunicode))untuk menghindari error build yang sempit.
danmichaelo
4
Bagi saya control_chars == '\x00-\x1f\x7f-\x9f'(diuji pada Python 3.5.2)
AXO
74

Sejauh yang saya tahu, metode paling pythonic / efisien adalah:

import string

filtered_string = filter(lambda x: x in string.printable, myStr)
William Keller
sumber
10
Anda mungkin ingin filtered_string = ".join (filter (lambda x: x in string.printable, myStr) sehingga Anda mendapatkan kembali sebuah string.
Nathan Shively-Sanders
12
Sayangnya string.printable tidak mengandung karakter unicode, dan dengan demikian ü atau ó tidak akan ada di output ... mungkin ada yang lain?
Vinko Vrsalovic
17
Anda harus menggunakan pemahaman daftar atau ekspresi generator, bukan filter + lambda. Salah satunya akan 99,9% waktunya lebih cepat. '' .join (s untuk di myStr jika dalam string.printable)
habnabit
3
@AaronGallagher: 99,9% lebih cepat? Dari mana Anda mengambil angka itu? Perbandingan kinerja tidak seburuk itu.
Chris Morgan
4
Hai William. Metode ini tampaknya menghapus semua karakter non-ASCII. Ada banyak karakter non-ASCII yang dapat dicetak di Unicode!
dotancohen
17

Anda dapat mencoba menyiapkan filter menggunakan unicodedata.category()fungsi:

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

Lihat Tabel 4-9 di halaman 175 di properti karakter database Unicode untuk kategori yang tersedia

Ber
sumber
Anda memulai pemahaman daftar yang tidak berakhir di baris terakhir Anda. Saya sarankan Anda melepas braket pembuka sepenuhnya.
tzot
Terima kasih telah menunjukkan hal ini. Saya mengedit postingan sesuai
Ber
1
Ini sepertinya metode yang paling langsung dan langsung. Terima kasih.
dotancohen
1
@CsabaToth Ketiganya valid dan menghasilkan set yang sama. Anda mungkin adalah cara terbaik untuk menentukan himpunan literal.
Ber
1
@AnubhavJhalani Anda dapat menambahkan lebih banyak kategori Unicode ke filter. Untuk memesan spasi dan angka selain penggunaan hurufprintable = {'Lu', 'Ll', Zs', 'Nd'}
Ber
11

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

Lihat postingan StackOverflow ini tentang menghapus tanda baca untuk mengetahui bagaimana .translate () dibandingkan dengan regex & .replace ()

Rentang dapat dibuat nonprintable = (ord(c) for c in (chr(i) for i in range(sys.maxunicode)) if unicodedata.category(c)=='Cc')menggunakan kategori basis data karakter Unicode seperti yang ditunjukkan oleh @Ants Aasma.

shawnrad.dll
sumber
Akan lebih baik menggunakan rentang Unicode (lihat jawaban @Ants Aasma). Hasilnya akan seperti itu text.translate({c:None for c in itertools.chain(range(0x00,0x20),range(0x7f,0xa0))}).
darkdragon
9

Berikut ini akan bekerja dengan masukan Unicode dan agak cepat ...

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('') == ''

Pengujian saya sendiri menunjukkan pendekatan ini lebih cepat daripada fungsi yang mengulang string dan mengembalikan hasil menggunakan str.join.

ChrisP
sumber
Ini adalah satu-satunya jawaban yang cocok untuk saya dengan karakter unicode. Luar biasa Anda memberikan kasus uji!
pir
1
Jika Anda ingin memperbolehkan jeda baris, tambahkan LINE_BREAK_CHARACTERS = set(["\n", "\r"])dan and not chr(i) in LINE_BREAK_CHARACTERSsaat menyusun tabel.
pir
5

Fungsi ini menggunakan pemahaman daftar dan str.join, sehingga berjalan dalam waktu linier alih-alih O (n ^ 2):

from curses.ascii import isprint

def printable(input):
    return ''.join(char for char in input if isprint(char))
Kirk Strauser
sumber
2
filter(isprint,input)
berbunyi
5

Namun opsi lain di python 3:

re.sub(f'[^{re.escape(string.printable)}]', '', my_string)
c6401
sumber
Ini bekerja sangat bagus untuk saya dan 1 barisnya. terima kasih
Chop Labalagun
1
untuk beberapa alasan ini berfungsi dengan baik di windows tetapi tidak dapat menggunakannya di linux, saya harus mengubah f untuk r tetapi saya tidak yakin itu solusinya.
Chop Labalagun
Kedengarannya seperti Linux Python Anda terlalu tua untuk mendukung f-string. r-string sangat berbeda, meskipun bisa dibilang r'[^' + re.escape(string.printable) + r']'. (Saya rasa tidak re.escape()sepenuhnya benar di sini, tetapi jika berhasil ...)
tripleee
2

Yang terbaik yang saya dapatkan sekarang adalah (terima kasih kepada python-izers di atas)

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

Ini adalah satu-satunya cara saya mengetahui yang berfungsi dengan karakter / string Unicode

Ada pilihan yang lebih baik?

Vinko Vrsalovic
sumber
1
Kecuali Anda menggunakan python 2.3, bagian dalam [] berlebihan. "return" .join (c untuk c ...) "
habnabit
Tidak terlalu berlebihan — mereka memiliki arti yang berbeda (dan karakteristik performa), meskipun hasil akhirnya sama.
Miles
Haruskah ujung lain dari rentang tidak dilindungi juga ?: "ord (c) <= 126"
Gearoid Murphy
7
Tetapi ada karakter Unicode yang tidak dapat dicetak juga.
tripleee
2

Yang di bawah ini bekerja lebih cepat dari yang lain di atas. Lihatlah

''.join([x if x in string.printable else '' for x in Str])
Nilav Baran Ghosh
sumber
"".join([c if 0x21<=ord(c) and ord(c)<=0x7e else "" for c in ss])
evandrix
2

Di Python tidak ada kelas regex POSIX

Ada saat menggunakan regexperpustakaan: https://pypi.org/project/regex/

Itu dipelihara dengan baik dan mendukung regex Unicode, Posix regex dan banyak lagi. Penggunaan (tanda tangan metode) sangat mirip dengan Python re.

Dari dokumentasi:

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

Kelas karakter POSIX didukung. Ini biasanya diperlakukan sebagai bentuk alternatif \p{...}.

(Saya tidak berafiliasi, hanya pengguna.)

Risadinha
sumber
2

Berdasarkan jawaban @ Ber, saya sarankan menghapus hanya karakter kontrol seperti yang didefinisikan dalam kategori database karakter Unicode :

import unicodedata
def filter_non_printable(s):
    return ''.join(c for c in s if not unicodedata.category(c).startswith('C'))
Naga gelap
sumber
Ini jawaban yang bagus!
tdc
Anda mungkin melakukan sesuatu dengan startswith('C')tetapi ini jauh kurang berkinerja dalam pengujian saya daripada solusi lainnya.
Big McLargeHuge
big-mclargehuge: Tujuan solusi saya adalah kombinasi dari kelengkapan dan kesederhanaan / keterbacaan. Anda bisa mencoba menggunakan if unicodedata.category(c)[0] != 'C'sebagai gantinya. Apakah kinerjanya lebih baik? Jika Anda lebih suka kecepatan eksekusi daripada persyaratan memori, tabel dapat dihitung sebelumnya seperti yang ditunjukkan di stackoverflow.com/a/93029/3779655
darkdragon
0

Untuk menghapus 'spasi',

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))
taman pengetahuan
sumber
Sebenarnya, Anda juga tidak membutuhkan tanda kurung siku.
tripleee
0

Diadaptasi dari jawaban oleh Ants Aasma dan 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)

diuji pada Python 3.7.7

Joe
sumber