Loại bỏ các ký tự không in được khỏi một chuỗi trong python


88

Tôi sử dụng để chạy

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

trên Perl để loại bỏ các ký tự không in được.

Trong Python không có các lớp regex POSIX và tôi không thể viết [: print:] có nghĩa là tôi muốn. Tôi không biết cách nào trong Python để phát hiện xem một ký tự có thể in được hay không.

Bạn sẽ làm gì?

CHỈNH SỬA: Nó cũng phải hỗ trợ các ký tự Unicode. Cách string.printable sẽ loại bỏ chúng ra khỏi đầu ra một cách vui vẻ. curses.ascii.isprint sẽ trả về false cho bất kỳ ký tự unicode nào.

Câu trả lời:


83

Thật không may, việc lặp lại qua các chuỗi khá chậm trong Python. Biểu thức chính quy nhanh hơn một bậc lớn hơn đối với loại điều này. Bạn chỉ cần xây dựng lớp nhân vật cho mình. Các unicodedata Module này là khá hữu ích cho việc này, đặc biệt là unicodedata.category () chức năng. Xem Cơ sở dữ liệu ký tự Unicode để biết mô tả về các loại.

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)

Đối với 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)

Đối với một số trường hợp sử dụng, các danh mục bổ sung (ví dụ: tất cả từ nhóm điều khiển có thể thích hợp hơn, mặc dù điều này có thể làm chậm thời gian xử lý và tăng mức sử dụng bộ nhớ đáng kể. Số ký tự trên mỗi danh mục:

  • Cc (kiểm soát): 65
  • Cf (định dạng): 161
  • Cs (đại diện): 2048
  • Co (sử dụng riêng): 137468
  • Cn (chưa giao): 836601

Chỉnh sửa Thêm đề xuất từ ​​các nhận xét.


4
Ở đây có đủ 'Cc' không? Tôi không biết, tôi chỉ đang hỏi - đối với tôi dường như một số loại 'C' khác cũng có thể là ứng cử viên cho bộ lọc này.
Patrick Johnmeyer

1
Hàm này, như đã xuất bản, loại bỏ một nửa số ký tự tiếng Do Thái. Tôi nhận được hiệu quả như nhau cho cả hai phương pháp được đưa ra.
dotancohen

1
Từ góc độ hiệu suất, string.translate () sẽ không hoạt động nhanh hơn trong trường hợp này? Xem stackoverflow.com/questions/265960/…
Kashyap

3
Sử dụng all_chars = (unichr(i) for i in xrange(sys.maxunicode))để tránh lỗi xây dựng hẹp.
danmichaelo

4
Đối với tôi control_chars == '\x00-\x1f\x7f-\x9f'(đã thử nghiệm trên Python 3.5.2)
AXO

72

Theo như tôi biết, phương pháp hiệu quả nhất sẽ là:

import string

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

10
Bạn có thể muốn filter_string = '' .join (filter (lambda x: x in string.printable, myStr) để bạn lấy lại một chuỗi.
Nathan Shively-Sanders

12
Đáng buồn là string.printable không chứa các ký tự unicode, và do đó ü hoặc ó sẽ không có trong đầu ra ... có thể có thứ gì đó khác?
Vinko Vrsalovic

17
Bạn nên sử dụng biểu thức trình tạo hoặc hiểu danh sách, không phải bộ lọc + lambda. Một trong số này sẽ nhanh hơn 99,9% thời gian. '' .join (s cho s trong mystr nếu s trong string.printable)
habnabit

3
@AaronGallagher: Nhanh hơn 99,9%? Bạn lấy con số đó từ bao giờ? So sánh hiệu suất không đến nỗi tệ.
Chris Morgan

4
Chào William. Phương pháp này dường như loại bỏ tất cả các ký tự không phải ASCII. Có nhiều ký tự không phải ASCII có thể in được trong Unicode!
dotancohen

17

Bạn có thể thử thiết lập bộ lọc bằng unicodedata.category()chức năng:

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

Xem Bảng 4-9 trên trang 175 trong thuộc tính ký tự cơ sở dữ liệu Unicode để biết các danh mục có sẵn


bạn đã bắt đầu đọc hiểu danh sách mà không kết thúc ở dòng cuối cùng của bạn. Tôi đề nghị bạn loại bỏ hoàn toàn dấu ngoặc mở.
tzot 19/09/08

Cảm ơn bạn đã chỉ ra điều này. Tôi đã chỉnh sửa bài đăng cho phù hợp
Ber

1
Đây có vẻ là phương pháp trực tiếp, đơn giản nhất. Cảm ơn.
dotancohen

1
@CsabaToth Cả ba đều hợp lệ và mang lại cùng một bộ. Của bạn có lẽ là cách tốt nhất để chỉ định một bộ chữ.
Ber

1
@AnubhavJhalani Bạn có thể thêm nhiều danh mục Unicode vào bộ lọc. Để đặt trước khoảng trắng và chữ số ngoài việc sử dụng các chữ cáiprintable = {'Lu', 'Ll', Zs', 'Nd'}
Ber

10

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

Xem bài đăng StackOverflow này về cách xóa dấu câu để biết cách .translate () so sánh với regex & .replace ()

Các phạm vi có thể được tạo nonprintable = (ord(c) for c in (chr(i) for i in range(sys.maxunicode)) if unicodedata.category(c)=='Cc')bằng cách sử dụng các danh mục cơ sở dữ liệu ký tự Unicode như được hiển thị bởi @Ants Aasma.


Sẽ tốt hơn nếu sử dụng các dãy Unicode (xem câu trả lời của @Ants Aasma). Kết quả sẽ là text.translate({c:None for c in itertools.chain(range(0x00,0x20),range(0x7f,0xa0))}).
darkdragon

8

Phần sau sẽ hoạt động với đầu vào Unicode và khá nhanh ...

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

Thử nghiệm của riêng tôi cho thấy phương pháp này nhanh hơn các hàm lặp qua chuỗi và trả về kết quả bằng cách sử dụng str.join.


Đây là câu trả lời duy nhất phù hợp với tôi với các ký tự unicode. Thật tuyệt vì bạn đã cung cấp các trường hợp thử nghiệm!
pir

1
Nếu bạn muốn cho phép ngắt dòng, hãy thêm LINE_BREAK_CHARACTERS = set(["\n", "\r"])and not chr(i) in LINE_BREAK_CHARACTERSkhi tạo bảng.
pir

5

Hàm này sử dụng khả năng hiểu danh sách và str.join, vì vậy nó chạy trong thời gian tuyến tính thay vì O (n ^ 2):

from curses.ascii import isprint

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

2
filter(isprint,input)
yingted

5

Tuy nhiên, một tùy chọn khác trong python 3:

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

Điều này làm việc siêu tuyệt vời cho tôi và 1 dòng của nó. cảm ơn
Chop Labalagun

1
vì một số lý do điều này hoạt động tốt trên windows nhưng không thể sử dụng nó trên linux, tôi đã phải thay đổi f cho một r nhưng tôi không chắc đó là giải pháp.
Chop Labalagun

Có vẻ như Linux Python của bạn đã quá cũ để hỗ trợ f-string khi đó. R-string là khá khác nhau, mặc dù bạn có thể nói r'[^' + re.escape(string.printable) + r']'. (Tôi không nghĩ re.escape()là hoàn toàn chính xác ở đây, nhưng nếu nó hoạt động ...)
tripleee

2

Điều tốt nhất tôi nghĩ ra bây giờ là (nhờ python-izers ở trên)

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

Đây là cách duy nhất tôi phát hiện ra rằng hoạt động với các ký tự / chuỗi Unicode

Có lựa chọn nào tốt hơn không?


1
Trừ khi bạn đang sử dụng python 2.3, các [] bên trong là dư thừa. "return '' .join (c for c ...)"
habnabit 19/09/08

Không hoàn toàn thừa — chúng có ý nghĩa khác nhau (và đặc điểm hiệu suất), mặc dù kết quả cuối cùng là giống nhau.
Miles

Đầu bên kia của phạm vi cũng không được bảo vệ ?: "ord (c) <= 126"
Gearoid Murphy

7
Nhưng cũng có những ký tự Unicode không thể in được.
tripleee

2

Cái bên dưới hoạt động nhanh hơn cái khác ở trên. Hãy xem

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

Trong Python không có lớp regex POSIX

Có khi sử dụng regexthư viện: https://pypi.org/project/regex/

Nó được duy trì tốt và hỗ trợ Unicode regex, Posix regex và nhiều hơn nữa. Cách sử dụng (chữ ký phương thức) rất giống với Python re.

Từ tài liệu:

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

Các lớp ký tự POSIX được hỗ trợ. Chúng thường được coi là một dạng thay thế của\p{...} .

(Tôi không liên kết, chỉ là một người dùng.)


1

Dựa trên câu trả lời của @ Ber, tôi khuyên bạn chỉ nên xóa các ký tự điều khiển như được xác định trong các danh mục cơ sở dữ liệu ký tự Unicode :

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

Đây là một câu trả lời tuyệt vời!
tdc

0

Để loại bỏ 'khoảng trắng',

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

Thực ra bạn cũng không cần dấu ngoặc vuông.
tripleee

0

Phỏng theo câu trả lời của Ants Aasmashawnrad :

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)

đã thử nghiệm trên Python 3.7.7

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.