การตัดอักขระที่ไม่สามารถพิมพ์ออกจากสตริงใน python


93

ฉันใช้ในการวิ่ง

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

บน Perl เพื่อกำจัดอักขระที่ไม่สามารถพิมพ์ได้

ใน Python ไม่มีคลาส regex POSIX และฉันไม่สามารถเขียน [: print:] หมายความว่าฉันต้องการได้ ฉันรู้ว่าไม่มีวิธีใดใน Python ในการตรวจสอบว่าอักขระนั้นพิมพ์ได้หรือไม่

คุณจะทำอะไร?

แก้ไข: ต้องรองรับอักขระ Unicode ด้วย วิธีที่ string.printable จะดึงออกจากเอาต์พุตอย่างมีความสุข curses.ascii.isprint จะส่งคืนเท็จสำหรับอักขระ Unicode ใด ๆ

คำตอบ:


86

การทำซ้ำในสตริงนั้นค่อนข้างช้าใน Python นิพจน์ทั่วไปอยู่ในลำดับความสำคัญเร็วกว่าสำหรับสิ่งประเภทนี้ คุณต้องสร้างคลาสตัวละครด้วยตัวคุณเอง unicodedataโมดูลเป็นประโยชน์มากสำหรับการนี้โดยเฉพาะอย่างยิ่งunicodedata.category ()ฟังก์ชั่น ดูฐานข้อมูลอักขระ Unicodeสำหรับคำอธิบายหมวดหมู่

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.0

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)

สำหรับบางกรณีการใช้งานหมวดหมู่เพิ่มเติม (เช่นทั้งหมดจากส่วนควบคุมกลุ่มอาจดีกว่าแม้ว่าอาจทำให้เวลาในการประมวลผลช้าลงและเพิ่มการใช้หน่วยความจำอย่างมากจำนวนอักขระต่อหมวดหมู่:

  • Cc (ควบคุม): 65
  • Cf (รูปแบบ): 161
  • Cs (ตัวแทน): 2048
  • Co (ใช้ส่วนตัว): 137468
  • Cn (ยังไม่ได้กำหนด): 836601

แก้ไขเพิ่มคำแนะนำจากความคิดเห็น


4
'Cc' เพียงพอหรือไม่ ฉันไม่รู้ฉันแค่ถาม - สำหรับฉันแล้วดูเหมือนว่าหมวดหมู่ 'C' อื่น ๆ บางหมวดอาจเป็นตัวเลือกสำหรับตัวกรองนี้เช่นกัน
Patrick Johnmeyer

1
ฟังก์ชันนี้ตามที่เผยแพร่แล้วจะลบอักขระฮีบรูครึ่งหนึ่งออก ฉันได้รับผลเหมือนกันสำหรับทั้งสองวิธีที่ให้มา
dotancohen

1
จากมุมมองด้านประสิทธิภาพ string.translate () จะไม่ทำงานเร็วขึ้นในกรณีนี้หรือไม่? ดูstackoverflow.com/questions/265960/…
Kashyap

3
ใช้all_chars = (unichr(i) for i in xrange(sys.maxunicode))เพื่อหลีกเลี่ยงข้อผิดพลาดในการสร้างที่แคบ
danmichaelo

4
สำหรับฉันcontrol_chars == '\x00-\x1f\x7f-\x9f'(ทดสอบบน Python 3.5.2)
AXO

76

เท่าที่ฉันรู้วิธี pythonic / มีประสิทธิภาพมากที่สุดคือ:

import string

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

10
คุณอาจต้องการ filtered_string = '' .join (ตัวกรอง (lambda x: x ใน string.printable, myStr) เพื่อให้คุณได้สตริงกลับมา
Nathan Shively-Sanders

13
น่าเศร้าที่ string.printable ไม่มีอักขระ Unicode ดังนั้นüหรือóจะไม่อยู่ในเอาต์พุต ... อาจมีอย่างอื่น?
Vinko Vrsalovic

17
คุณควรใช้นิพจน์ความเข้าใจรายการหรือตัวสร้างไม่ใช่ตัวกรอง + แลมด้า หนึ่งในนั้นจะเร็วขึ้น 99.9% '' .join (s for s in myStr if s in string.printable)
habnabit

3
@AaronGallagher: เร็วขึ้น 99.9%? คุณถอนร่างนั้นมาจากไหน? การเปรียบเทียบประสิทธิภาพไม่มีที่ไหนใกล้ความเลวร้าย
Chris Morgan

4
สวัสดีวิลเลียม วิธีนี้ดูเหมือนจะลบอักขระที่ไม่ใช่ ASCII ทั้งหมด มีอักขระที่ไม่ใช่ ASCII ที่สามารถพิมพ์ได้ใน Unicode!
dotancohen

18

คุณสามารถลองตั้งค่าตัวกรองโดยใช้unicodedata.category()ฟังก์ชัน:

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

ดูตารางที่ 4-9 ในหน้า 175 ในคุณสมบัติอักขระฐานข้อมูล Unicodeสำหรับหมวดหมู่ที่มี


คุณเริ่มทำความเข้าใจกับรายการซึ่งไม่ได้สิ้นสุดในบรรทัดสุดท้ายของคุณ ฉันขอแนะนำให้คุณถอดวงเล็บเปิดออกให้หมด
tzot

ขอขอบคุณที่ชี้ให้เห็น ฉันแก้ไขโพสต์ตามนั้น
Ber

1
นี่เป็นวิธีที่ตรงไปตรงมาที่สุด ขอบคุณ.
dotancohen

1
@CsabaToth ทั้งสามถูกต้องและให้ผลชุดเดียวกัน ของคุณอาจเป็นวิธีที่ดีที่สุดในการระบุชุดตามตัวอักษร
Ber

1
@AnubhavJhalani คุณสามารถเพิ่มหมวดหมู่ Unicode ลงในตัวกรองได้ หากต้องการจองช่องว่างและตัวเลขนอกเหนือจากตัวอักษรให้ใช้printable = {'Lu', 'Ll', Zs', 'Nd'}
Ber

11

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

ดูโพสต์ StackOverflow เกี่ยวกับการลบเครื่องหมายวรรคตอนสำหรับวิธีที่ .ranslate () เปรียบเทียบกับ regex & .replace ()

สามารถสร้างช่วงได้nonprintable = (ord(c) for c in (chr(i) for i in range(sys.maxunicode)) if unicodedata.category(c)=='Cc')โดยใช้หมวดหมู่ฐานข้อมูลอักขระ Unicodeดังที่แสดงโดย @Ants Aasma


จะดีกว่าถ้าใช้ช่วง Unicode (ดูคำตอบของ @Ants Aasma) ผลลัพธ์จะเป็นtext.translate({c:None for c in itertools.chain(range(0x00,0x20),range(0x7f,0xa0))})อย่างไร
darkdragon

9

สิ่งต่อไปนี้จะใช้ได้กับอินพุต Unicode และค่อนข้างเร็ว ...

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

การทดสอบของฉันแนะนำว่าวิธีนี้เร็วกว่าฟังก์ชันที่วนซ้ำบนสตริงและส่งคืนผลลัพธ์โดยใช้str.join.


นี่เป็นคำตอบเดียวที่ใช้ได้กับฉันด้วยอักขระ Unicode ยอดเยี่ยมที่คุณให้กรณีทดสอบ!
pir

1
หากคุณต้องการอนุญาตให้มีการแบ่งบรรทัดให้เพิ่มLINE_BREAK_CHARACTERS = set(["\n", "\r"])และand not chr(i) in LINE_BREAK_CHARACTERSเมื่อสร้างตาราง
pir

6

อีกทางเลือกหนึ่งใน python 3:

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

สิ่งนี้ใช้งานได้ดีมากสำหรับฉันและ 1 บรรทัด ขอบคุณ
Chop Labalagun

1
ด้วยเหตุผลบางอย่างสิ่งนี้ใช้งานได้ดีบน windows แต่ไม่สามารถใช้กับ linux ได้ฉันต้องเปลี่ยน f เป็น r แต่ฉันไม่แน่ใจว่านั่นเป็นวิธีแก้ปัญหา
Chop Labalagun

ดูเหมือนว่า Linux Python ของคุณเก่าเกินไปที่จะรองรับ f-strings แล้ว r-strings นั้นแตกต่างกันมากแม้ว่าคุณจะพูดr'[^' + re.escape(string.printable) + r']'ได้ (ฉันไม่คิดว่าre.escape()ที่นี่ถูกต้องทั้งหมด แต่ถ้ามันได้ผล ... )
tripleee

5

ฟังก์ชั่นนี้ใช้ความเข้าใจในรายการและ str.join ดังนั้นจึงทำงานในเวลาเชิงเส้นแทน O (n ^ 2):

from curses.ascii import isprint

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

2

สิ่งที่ดีที่สุดที่ฉันคิดขึ้นมาคือ (ขอบคุณ python-izers ด้านบน)

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

นี่เป็นวิธีเดียวที่ฉันพบว่าใช้ได้กับอักขระ / สตริง Unicode

ตัวเลือกที่ดีกว่านี้หรือไม่?


1
เว้นแต่คุณจะใช้ python 2.3 [] ภายในจะซ้ำซ้อน "return" .join (c for c ... ) "
habnabit

ไม่ซ้ำซ้อน - มีความหมายที่แตกต่างกัน (และลักษณะการทำงาน) แม้ว่าผลลัพธ์จะเหมือนกันก็ตาม
ไมล์

ควรป้องกันปลายอีกด้านหนึ่งของช่วงด้วยหรือไม่: "ord (c) <= 126"
Gearoid Murphy

7
แต่ก็มีอักขระ Unicode ที่ไม่สามารถพิมพ์ได้เช่นกัน
tripleee

2

ด้านล่างทำงานได้เร็วกว่าที่อื่น ๆ ด้านบน ลองดูสิ

''.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 ไม่มีคลาส regex POSIX

มีเมื่อใช้regexห้องสมุด: https://pypi.org/project/regex/

ได้รับการดูแลอย่างดีและรองรับ Unicode regex, Posix regex และอื่น ๆ อีกมากมาย การใช้งาน (วิธีลายเซ็น) เป็นมากreคล้ายกับงูใหญ่

จากเอกสารประกอบ:

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

รองรับคลาสอักขระ POSIX \p{...}เหล่านี้จะได้รับการรักษาตามปกติเป็นรูปแบบทางเลือกของการ

(ฉันไม่มีส่วนเกี่ยวข้องเป็นเพียงผู้ใช้)


2

จากคำตอบของ @ Ber ฉันขอแนะนำให้ลบเฉพาะอักขระควบคุมตามที่กำหนดไว้ในประเภทฐานข้อมูลอักขระ Unicode :

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

นี่ตอบโจทย์มาก!
tdc

คุณอาจกำลังทำอะไรบางอย่างอยู่startswith('C')แต่สิ่งนี้มีประสิทธิภาพในการทดสอบของฉันน้อยกว่าโซลูชันอื่น ๆ
Big McLargeHuge

big-mclargehuge: เป้าหมายของโซลูชันของฉันคือการผสมผสานระหว่างความสมบูรณ์และความเรียบง่าย / ความสามารถในการอ่าน คุณสามารถลองใช้if unicodedata.category(c)[0] != 'C'แทนได้ มันทำงานได้ดีขึ้นหรือไม่? หากคุณต้องการความเร็วในการประมวลผลมากกว่าความต้องการหน่วยความจำคุณสามารถคำนวณตารางล่วงหน้าดังที่แสดงในstackoverflow.com/a/93029/3779655
darkdragon

0

หากต้องการลบ "ช่องว่าง"

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

จริงๆแล้วคุณไม่จำเป็นต้องใช้วงเล็บเหลี่ยมเช่นกัน
tripleee

0

ดัดแปลงจากคำตอบของAnts Aasmaและ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)

ทดสอบกับ Python 3.7.7

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.