แทนที่อักขระที่ไม่ใช่ ASCII ด้วยช่องว่างเดียว


244

ฉันต้องการแทนที่อักขระที่ไม่ใช่ ASCII (\ x00- \ x7F) ทั้งหมดด้วยช่องว่าง ฉันประหลาดใจที่นี่ไม่ได้เป็นเรื่องง่ายใน Python ถ้าฉันไม่ได้ทำอะไร ฟังก์ชันต่อไปนี้จะลบอักขระที่ไม่ใช่ ASCII ทั้งหมด:

def remove_non_ascii_1(text):

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

และอันนี้แทนที่อักขระที่ไม่ใช่ ASCII ด้วยจำนวนช่องว่างตามจำนวนไบต์ในจุดรหัสอักขระ (เช่นตัวละครจะถูกแทนที่ด้วย 3 ช่องว่าง):

def remove_non_ascii_2(text):

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

ฉันจะแทนที่อักขระที่ไม่ใช่ ASCII ทั้งหมดด้วยช่องว่างเดียวได้อย่างไร

ของ มากมายของที่คล้ายกันดังนั้นคำถาม , ไม่มีใครอยู่ตัวอักษรทดแทนเป็นตรงข้ามที่จะปอก , และนอกจากนี้ยังอยู่อักขระที่ไม่ใช่ ASCII ทั้งหมดที่ไม่ได้ตัวละครที่เฉพาะเจาะจง


46
ว้าวคุณพยายามอย่างดีในการแสดงลิงค์มากมาย +1 ทันทีที่วันต่ออายุ!
shad0w_wa1k3r

3
ดูเหมือนว่าคุณจะพลาดหนึ่งในstackoverflow.com/questions/1342000/
สจวร์ต

ฉันสนใจที่จะเห็นตัวอย่างอินพุตที่มีปัญหา
dstromberg

5
@ Stuart: ขอบคุณ แต่นั่นเป็นคนแรกที่ฉันพูดถึง
dotancohen

1
@dstromberg: ฉันพูดถึงตัวละครเช่นปัญหาในคำถาม: มันเป็นผู้ชายคนนี้
dotancohen

คำตอบ:


243

''.join()นิพจน์ของคุณกรองเอาสิ่งที่ไม่ใช่ ASCII ออก คุณสามารถใช้นิพจน์เงื่อนไขแทน:

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

วิธีนี้จะจัดการอักขระทีละตัวและจะยังคงใช้ช่องว่างหนึ่งตัวต่ออักขระที่ถูกแทนที่

นิพจน์ปกติของคุณควรแทนที่อักขระที่ไม่ใช่ ASCII ต่อเนื่องกันด้วยช่องว่าง:

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

สังเกตที่+นั่น


18
@dstromberg: ช้าลง; str.join() ต้องการรายการ (มันจะผ่านค่าสองครั้ง) และนิพจน์ตัวสร้างจะถูกแปลงเป็นรายการแรก การทำให้รายการเข้าใจง่ายขึ้นเร็วขึ้น ดูโพสต์นี้
Martijn Pieters

1
ส่วนแรกของโค้ดจะแทรกช่องว่างหลายช่องต่ออักขระถ้าคุณป้อนสตริง UTF-8
Mark Ransom

@ MarkRansom: ฉันสมมติว่านี่เป็น Python 3
Martijn Pieters

2
" อักขระถูกแทนที่ด้วย 3 ช่องว่าง"ในคำถามแสดงถึงว่าอินพุตเป็น bytestring (ไม่ใช่ Unicode) ดังนั้นจึงใช้ Python 2 (มิฉะนั้น''.joinจะล้มเหลว) ถ้า OP ต้องการช่องว่างเดียวต่อ Unicode codepoint ข้อมูลควรถูกถอดรหัสเป็น Unicode ก่อน
jfs

สิ่งนี้ช่วยฉันได้มาก!
มูฮัมหมัด Haseeb

55

เพื่อให้คุณได้รับความเหมือนกันมากที่สุดของสตริงเดิมของคุณฉันขอแนะนำโมดูล unidecode :

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

จากนั้นคุณสามารถใช้มันในสตริง:

remove_non_ascii("Ceñía")
Cenia

ข้อเสนอแนะที่น่าสนใจ แต่ผู้ใช้คาดหวังว่าผู้ใช้ที่ไม่ใช่ ASCII จะกลายเป็นกฎของ unidecode อย่างไรก็ตามเรื่องนี้ทำให้เกิดคำถามตามมาถามผู้ถามถึงสาเหตุที่พวกเขายืนยันในช่องว่างเพื่อแทนที่ด้วยอักขระอื่น?
jxramos

ขอบคุณนี่เป็นคำตอบที่ดี ใช้ไม่ได้กับจุดประสงค์ของคำถามนี้เพราะข้อมูลส่วนใหญ่ที่ฉันจัดการไม่มีการแทนค่าแบบ ASCII דותןเช่น อย่างไรก็ตามโดยทั่วไปแล้วนี่ยอดเยี่ยมมากขอบคุณ!
dotancohen

1
ใช่ฉันรู้ว่านี่ใช้ไม่ได้กับคำถามนี้แต่ฉันลงจอดที่นี่เพื่อพยายามแก้ปัญหานี้ดังนั้นฉันคิดว่าฉันจะแบ่งปันวิธีแก้ปัญหาของฉันเองซึ่งฉันคิดว่าเป็นเรื่องธรรมดามากสำหรับคนที่ @dotancohen ซึ่งเป็นคนจัดการ ที่มีอักขระที่ไม่ใช่ ASCII ตลอดเวลา
Alvaro Fuentes

ในอดีตมีช่องโหว่ด้านความปลอดภัยที่มีสิ่งต่างๆเช่นนี้ เพียงระวังวิธีที่คุณใช้งาน!
deweydb

ดูเหมือนจะไม่ทำงานกับสตริงข้อความที่เข้ารหัส UTF-16
user5359531

22

สำหรับการประมวลผลตัวอักษรให้ใช้สตริง 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'

แต่โปรดทราบว่าคุณยังคงมีปัญหาหากสตริงของคุณมีอักขระ Unicode ที่แยกย่อยแล้ว (ตัวอย่างเช่นอักขระแยกต่างหากและการรวมเครื่องหมายเน้นเสียง):

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

ขอบคุณนี่เป็นข้อสังเกตที่สำคัญ หากคุณพบวิธีที่เป็นตรรกะในการจัดการกรณีของการรวมเครื่องหมายฉันจะเพิ่มความสุขให้กับคำถามอย่างมีความสุข ฉันคิดว่าเพียงแค่ลบเครื่องหมายรวม แต่ออกจากตัวละครที่ไม่ได้รวมเพียงอย่างเดียวจะดีที่สุด
dotancohen

1
วิธีแก้ปัญหาบางส่วนคือการใช้ud.normalize('NFC',s)เพื่อรวมเครื่องหมาย แต่ไม่รวมชุดค่าผสมทั้งหมดจะถูกแสดงด้วยรหัสจุดเดียว คุณจะต้องมีวิธีแก้ปัญหาอย่างชาญฉลาดในการud.category()ดูตัวละคร
Mark Tolonen

1
@dotancohen: มีความคิดเกี่ยวกับ "ตัวอักษรที่ผู้ใช้รับรู้" ใน Unicode ที่อาจครอบคลุมหลายจุดรหัส Unicode \X(กลุ่มกราฟ eXtended) regex (สนับสนุนโดยregexโมดูล) อนุญาตให้วนซ้ำอักขระดังกล่าว (หมายเหตุ: "graphemes ไม่จำเป็นต้องรวมลำดับอักขระและการรวมลำดับอักขระไม่จำเป็นต้องเป็นกราฟ" )
jfs

10

หากอักขระทดแทนสามารถเป็น '?' แทนที่จะเว้นที่ว่างแล้วฉันจะแนะนำ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(),
))

ผล:

0.7208260721400134
0.009975979187503592

แทนที่ ? ด้วยอักขระหรือช่องว่างอื่นหลังจากนั้นหากจำเป็นและคุณยังจะเร็วขึ้น
Moritz

7

แล้วอันนี้หล่ะ?

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
แม้ว่านี่จะค่อนข้างไม่เหมาะสม แต่ก็สามารถอ่านได้มาก ขอบคุณ.
dotancohen

1
+1 สำหรับการจัดการ Unicode ... @Dotancohen IMNSHO "สามารถอ่านได้" หมายถึง "ใช้งานได้จริง" ซึ่งเพิ่มให้กับ "Elegant" ดังนั้นฉันจะพูดว่า "บิต
ไม่ค่อยเก่ง

3

ด้วยวิธีการเนทีฟและมีประสิทธิภาพคุณไม่จำเป็นต้องใช้ordหรือวนรอบอักขระใด ๆ เพียงแค่เข้ารหัสasciiและละเว้นข้อผิดพลาด

ต่อไปนี้จะเป็นการลบอักขระที่ไม่ใช่ ASCII:

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

ตอนนี้ถ้าคุณต้องการแทนที่ตัวละครที่ถูกลบเพียงทำต่อไปนี้:

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

ใน python3 สิ่งนี้encodeจะคืนค่าการทดสอบดังนั้นโปรดจำไว้ นอกจากนี้วิธีนี้จะไม่ตัดออกอักขระเช่นขึ้นบรรทัดใหม่
Kyle Gibson

-1

อาจเป็นไปได้สำหรับคำถามที่แตกต่างกัน แต่ฉันกำลังให้คำตอบของ @ Alvero เวอร์ชันของฉัน (ใช้ unidecode) ฉันต้องการที่จะทำแถบ "ปกติ" ในสตริงของฉันคือจุดเริ่มต้นและจุดสิ้นสุดของสตริงของฉันสำหรับตัวละครช่องว่างแล้วแทนที่เฉพาะตัวละครช่องว่างอื่น ๆ ที่มีช่องว่าง "ปกติ" เช่น

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

ถึง

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

ก่อนอื่นเราจะแทนที่ช่องว่างที่ไม่ใช่ยูนิโค้ดทั้งหมดด้วยช่องว่างปกติ (และเข้าร่วมอีกครั้ง)

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

จากนั้นเราก็แบ่งมันอีกครั้งด้วยการแบ่งปกติของงูใหญ่แล้วตัด "บิต" แต่ละอัน

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

และสุดท้ายเข้าร่วมเหล่านั้นกลับมาอีกครั้ง แต่ถ้าสายผ่านการifทดสอบ

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

และด้วยที่ถูกต้องส่งกลับsafely_stripped('ㅤㅤㅤㅤCeñíaㅤmañanaㅤㅤㅤㅤ')'Ceñía mañana'

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