Thay thế các ký tự không phải ASCII bằng một khoảng trắng


244

Tôi cần thay thế tất cả các ký tự không phải ASCII (\ x00- \ x7F) bằng một khoảng trắng. Tôi ngạc nhiên rằng điều này không dễ dàng trong Python, trừ khi tôi thiếu một cái gì đó. Hàm sau chỉ đơn giản loại bỏ tất cả các ký tự không phải ASCII:

def remove_non_ascii_1(text):

    return ''.join(i for i in text if ord(i)<128)

Và cái này thay thế các ký tự không phải ASCII bằng số lượng khoảng trắng theo số lượng byte trong điểm mã ký tự (nghĩa là ký tự được thay thế bằng 3 khoảng trắng):

def remove_non_ascii_2(text):

    return re.sub(r'[^\x00-\x7F]',' ', text)

Làm cách nào tôi có thể thay thế tất cả các ký tự không phải ASCII bằng một khoảng trắng?

Của các vô số các giống SO câu hỏi , không có địa chỉ nhân vật thay thế như phản đối để tước , giải quyết bổ sung tất cả các ký tự khác ASCII không phải là một nhân vật cụ thể.


46
wow, bạn thực sự đã nỗ lực tốt để hiển thị rất nhiều liên kết. +1 ngay khi ngày đổi mới!
shad0w_wa1k3r

3
Có vẻ như bạn đã bỏ lỡ một stackoverflow.com/questions/1342000/ trộm
Stuart

Tôi quan tâm đến việc xem một ví dụ đầu vào có vấn đề.
dstromberg

5
@Stuart: Cảm ơn, nhưng đó là cái đầu tiên tôi nhắc đến.
dotancohen

1
@dstromberg: Tôi đề cập đến một nhân vật ví dụ có vấn đề trong câu hỏi : . Đó là anh chàng này .
dotancohen

Câu trả lời:


243

''.join()Biểu thức của bạn là lọc , loại bỏ mọi thứ không phải ASCII; thay vào đó, bạn có thể sử dụng biểu thức điều kiện:

return ''.join([i if ord(i) < 128 else ' ' for i in text])

Điều này xử lý từng ký tự một và vẫn sẽ sử dụng một khoảng trắng cho mỗi ký tự được thay thế.

Biểu thức thông thường của bạn chỉ nên thay thế các ký tự không phải ASCII liên tiếp bằng khoảng trắng:

re.sub(r'[^\x00-\x7F]+',' ', text)

Lưu ý +ở đó.


18
@dstromberg: chậm hơn; str.join() cần một danh sách (nó sẽ chuyển qua các giá trị hai lần) và biểu thức trình tạo trước tiên sẽ được chuyển đổi thành một. Cung cấp cho nó một sự hiểu biết danh sách đơn giản là nhanh hơn. Xem bài này .
Martijn Pieters

1
Đoạn mã đầu tiên sẽ chèn nhiều khoảng trống cho mỗi ký tự nếu bạn cung cấp cho nó chuỗi byte UTF-8.
Đánh dấu tiền chuộc

@MarkRansom: Tôi đã giả sử đây là Python 3.
Martijn Pieters

2
" ký tự được thay thế bằng 3 dấu cách" trong câu hỏi ngụ ý rằng đầu vào là một dấu phụ (không phải Unicode) và do đó Python 2 được sử dụng (nếu không ''.joinsẽ thất bại). Nếu OP muốn một không gian duy nhất cho mỗi điểm mã Unicode thì đầu vào phải được giải mã thành Unicode trước.
jfs

Điều này đã giúp tôi rất nhiều!
Muhammad Haseeb

55

Đối với bạn, việc có được đại diện giống nhau nhất của chuỗi ban đầu của bạn, tôi khuyên bạn nên mô-đun unidecode :

from unidecode import unidecode
def remove_non_ascii(text):
    return unidecode(unicode(text, encoding = "utf-8"))

Sau đó, bạn có thể sử dụng nó trong một chuỗi:

remove_non_ascii("Ceñía")
Cenia

đề xuất thú vị, nhưng nó giả định rằng người dùng muốn non ascii trở thành những quy tắc cho unidecode. Tuy nhiên, điều này đặt ra một câu hỏi tiếp theo cho người hỏi về lý do tại sao họ nhấn mạnh vào không gian, để có thể thay thế bằng một nhân vật khác?
jxramos

Cảm ơn bạn, đây là một câu trả lời tốt. Nó không hoạt động cho mục đích của câu hỏi này bởi vì hầu hết các dữ liệu mà tôi đang xử lý không có đại diện giống như ASCII. Chẳng hạn như דותן. Tuy nhiên, trong ý nghĩa chung này là tuyệt vời, cảm ơn bạn!
dotancohen

1
Vâng, tôi biết điều này không hiệu quả cho câu hỏi này , nhưng tôi đã đến đây để cố gắng giải quyết vấn đề đó, vì vậy tôi nghĩ tôi chỉ chia sẻ giải pháp của mình cho vấn đề của riêng tôi, điều mà tôi nghĩ là rất phổ biến đối với những người như @dotancohen, người giải quyết với các nhân vật không phải ascii mọi lúc.
Alvaro Fuentes

Đã có một số lỗ hổng bảo mật với những thứ như thế này trong quá khứ. Chỉ cần cẩn thận làm thế nào bạn thực hiện điều này!
deweydb

Dường như không hoạt động với các chuỗi văn bản được mã hóa UTF-16
user5359531

22

Để xử lý ký tự , sử dụng chuỗi Unicode:

PythonWin 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32.
>>> s='ABC马克def'
>>> import re
>>> re.sub(r'[^\x00-\x7f]',r' ',s)   # Each char is a Unicode codepoint.
'ABC  def'
>>> b = s.encode('utf8')
>>> re.sub(rb'[^\x00-\x7f]',rb' ',b) # Each char is a 3-byte UTF-8 sequence.
b'ABC      def'

Nhưng lưu ý rằng bạn vẫn sẽ gặp sự cố nếu chuỗi của bạn chứa các ký tự Unicode bị phân tách (ví dụ: ký tự riêng biệt và kết hợp các dấu trọng âm):

>>> s = 'mañana'
>>> len(s)
6
>>> import unicodedata as ud
>>> n=ud.normalize('NFD',s)
>>> n
'mañana'
>>> len(n)
7
>>> re.sub(r'[^\x00-\x7f]',r' ',s) # single codepoint
'ma ana'
>>> re.sub(r'[^\x00-\x7f]',r' ',n) # only combining mark replaced
'man ana'

Cảm ơn bạn, đây là một quan sát quan trọng. Nếu bạn tìm thấy một cách hợp lý để xử lý trường hợp kết hợp các dấu, tôi sẽ vui vẻ thêm tiền thưởng cho câu hỏi. Tôi cho rằng chỉ cần loại bỏ dấu kết hợp mà chỉ để lại ký tự không có tổ chức là tốt nhất.
dotancohen

1
Một giải pháp một phần là sử dụng ud.normalize('NFC',s)để kết hợp các nhãn hiệu, nhưng không phải tất cả các kết hợp kết hợp được thể hiện bằng các điểm mã duy nhất. Bạn sẽ cần một giải pháp thông minh hơn khi nhìn vào ud.category()nhân vật.
Đánh dấu Tolonen

1
@dotancohen: có một khái niệm về "ký tự cảm nhận của người dùng" trong Unicode có thể trải rộng trên một số điểm mã Unicode. \Xregex (cụm biểu đồ eXtends) (được hỗ trợ bởi regexmô-đun) cho phép lặp lại các ký tự đó (lưu ý: "biểu đồ không nhất thiết phải kết hợp các chuỗi ký tự và kết hợp các chuỗi ký tự không nhất thiết phải là biểu đồ" ).
jfs

10

Nếu ký tự thay thế có thể là '?' thay vì một khoảng trắng, sau đó tôi đề nghị result = text.encode('ascii', 'replace').decode():

"""Test the performance of different non-ASCII replacement methods."""


import re
from timeit import timeit


# 10_000 is typical in the project that I'm working on and most of the text
# is going to be non-ASCII.
text = 'Æ' * 10_000


print(timeit(
    """
result = ''.join([c if ord(c) < 128 else '?' for c in text])
    """,
    number=1000,
    globals=globals(),
))

print(timeit(
    """
result = text.encode('ascii', 'replace').decode()
    """,
    number=1000,
    globals=globals(),
))

Các kết quả:

0.7208260721400134
0.009975979187503592

Thay thế cái ? với một nhân vật hoặc không gian khác sau đó nếu cần, và bạn vẫn sẽ nhanh hơn.
Moritz

7

Cái này thì sao?

def replace_trash(unicode_string):
     for i in range(0, len(unicode_string)):
         try:
             unicode_string[i].encode("ascii")
         except:
              #means it's non-ASCII
              unicode_string=unicode_string[i].replace(" ") #replacing it with a single space
     return unicode_string

1
Mặc dù điều này là không phù hợp, nhưng nó rất dễ đọc. Cảm ơn bạn.
dotancohen

1
+1 để xử lý unicode ... @dotancohen IMNSHO "có thể đọc được" ngụ ý "thực tế" làm tăng thêm "thanh lịch", vì vậy tôi sẽ nói "một chút không phù hợp"
qneill

3

Là một cách tiếp cận bản địa và hiệu quả, bạn không cần phải sử dụng ordhoặc bất kỳ vòng lặp nào trên các ký tự. Chỉ cần mã hóa asciivà bỏ qua các lỗi.

Sau đây sẽ chỉ loại bỏ các ký tự không phải ascii:

new_string = old_string.encode('ascii',errors='ignore')

Bây giờ nếu bạn muốn thay thế các ký tự đã xóa, chỉ cần làm như sau:

final_string = new_string + b' ' * (len(old_string) - len(new_string))

Trong python3, điều này encodesẽ trả về một bytestring, vì vậy hãy ghi nhớ điều đó. Ngoài ra, phương pháp này sẽ không loại bỏ các ký tự như dòng mới.
Kyle Gibson

-1

Có khả năng cho một câu hỏi khác, nhưng tôi đang cung cấp phiên bản trả lời @ Alvero của tôi (sử dụng unidecode). Tôi muốn thực hiện một dải "thông thường" trên các chuỗi của mình, tức là đầu và cuối chuỗi của tôi cho các ký tự khoảng trắng, sau đó chỉ thay thế các ký tự khoảng trắng khác bằng khoảng trắng "thông thường", nghĩa là

"Ceñíaㅤmañanaㅤㅤㅤㅤ"

đến

"Ceñía mañana"

,

def safely_stripped(s: str):
    return ' '.join(
        stripped for stripped in
        (bit.strip() for bit in
         ''.join((c if unidecode(c) else ' ') for c in s).strip().split())
        if stripped)

Trước tiên, chúng tôi thay thế tất cả các không gian unicode bằng một không gian thông thường (và nối lại nó một lần nữa),

''.join((c if unidecode(c) else ' ') for c in s)

Và sau đó chúng tôi chia lại lần nữa, với sự phân chia bình thường của python và tước từng "bit",

(bit.strip() for bit in s.split())

Và cuối cùng tham gia lại những lần đó, nhưng chỉ khi chuỗi vượt qua một ifbài kiểm tra,

' '.join(stripped for stripped in s if stripped)

Và với điều đó, safely_stripped('ㅤㅤㅤㅤCeñíaㅤmañanaㅤㅤㅤㅤ')trả lại chính xác 'Ceñía mañana'.

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.