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


100

ฉันกำลังทำงานกับไฟล์. txt ฉันต้องการสตริงของข้อความจากไฟล์ที่ไม่มีอักขระที่ไม่ใช่ ASCII อย่างไรก็ตามฉันต้องการเว้นวรรคและจุด ตอนนี้ฉันกำลังลอกมันอยู่เหมือนกัน นี่คือรหัส:

def onlyascii(char):
    if ord(char) < 48 or ord(char) > 127: return ''
    else: return char

def get_my_string(file_path):
    f=open(file_path,'r')
    data=f.read()
    f.close()
    filtered_data=filter(onlyascii, data)
    filtered_data = filtered_data.lower()
    return filtered_data

ฉันจะแก้ไข onlyascii () ให้เว้นช่องว่างและจุดได้อย่างไร? ฉันคิดว่ามันไม่ซับซ้อนเกินไป แต่ฉันคิดไม่ออก


ขอบคุณ (ด้วยความจริงใจ) สำหรับคำชี้แจงของ John ฉันเข้าใจว่าช่องว่างและจุดเป็นอักขระ ASCII อย่างไรก็ตามฉันกำลังลบทั้งคู่โดยไม่ได้ตั้งใจในขณะที่พยายามลบเฉพาะอักขระที่ไม่ใช่ ASCII ฉันเห็นว่าคำถามของฉันอาจมีนัยเป็นอย่างอื่นได้อย่างไร

@PoliticalE economist: ปัญหาของคุณยังอยู่ในระดับต่ำมาก ดูคำตอบของฉัน
John Machin

คำตอบ:


187

คุณสามารถกรองอักขระทั้งหมดจากสตริงที่ไม่สามารถพิมพ์ได้โดยใช้string.printableเช่นนี้:

>>> s = "some\x00string. with\x15 funny characters"
>>> import string
>>> printable = set(string.printable)
>>> filter(lambda x: x in printable, s)
'somestring. with funny characters'

string.printable บนเครื่องของฉันประกอบด้วย:

0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c

แก้ไข: ใน Python 3 ตัวกรองจะส่งคืนการทำซ้ำ วิธีที่ถูกต้องในการรับสตริงคืนคือ:

''.join(filter(lambda x: x in printable, s))

2
เกิดอะไรขึ้นกับตัวอักษรที่พิมพ์ได้ที่ต่ำกว่าลำดับ 48?
joaquin

38
ปัญหาเดียวในการใช้filterคือมันส่งคืนได้ ถ้าคุณต้องการกลับสตริง (ที่ผมทำเพราะผมจำเป็นต้องทำแบบนี้เมื่อบีบอัดรายการ) ''.join(filter(lambda x: x in string.printable, s)แล้วทำเช่นนี้:
cjbarth

5
@cjbarth - ความคิดเห็นเป็น python 3 เฉพาะ แต่มีประโยชน์มาก ขอบคุณ!
อันเดอร์ช็อต

7
ทำไมไม่ใช้นิพจน์ทั่วไป: re.sub(r'[^\x00-\x7f]',r'', your-non-ascii-string). ดูกระทู้นี้stackoverflow.com/a/20079244/658497
Noam Manos

1
@NoamManos นี่เร็วขึ้น 4-5 เท่าสำหรับฉันที่เข้าร่วม ... กรอง ... โซลูชันแลมด้าขอบคุณ
artfulrobot

95

วิธีง่ายๆในการเปลี่ยนเป็น codec อื่นคือการใช้ encode () หรือ decode () ในกรณีของคุณคุณต้องการแปลงเป็น ASCII และละเว้นสัญลักษณ์ทั้งหมดที่ไม่รองรับ ตัวอย่างเช่นตัวอักษรสวีเดนåไม่ใช่อักขระ ASCII:

    >>>s = u'Good bye in Swedish is Hej d\xe5'
    >>>s = s.encode('ascii',errors='ignore')
    >>>print s
    Good bye in Swedish is Hej d

แก้ไข:

Python3: str -> ไบต์ -> str

>>>"Hej då".encode("ascii", errors="ignore").decode()
'hej d'

Python2: unicode -> str -> unicode

>>> u"hej då".encode("ascii", errors="ignore").decode()
u'hej d'

Python2: str -> unicode -> str (ถอดรหัสและเข้ารหัสในลำดับย้อนกลับ)

>>> "hej d\xe5".decode("ascii", errors="ignore").encode()
'hej d'

16
ฉันได้รับUnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 27
Xodarap777

2
ฉันได้รับข้อผิดพลาดนั้นเมื่อฉันใส่อักขระ Unicode จริงในสตริงผ่านการคัดลอกวาง เมื่อคุณระบุสตริงเป็นโค้ด u'thestring 'จะทำงานได้อย่างถูกต้อง
Ben Liyanage

2
ใช้งานได้กับ Py3 เท่านั้น แต่ก็สวยงาม
พูดไม่ออก

7
สำหรับผู้ที่ได้รับข้อผิดพลาดเดียวกันกับ @ Xodarap777: อันดับแรกคุณควร. decode () สตริงและหลังจากนั้นเข้ารหัสเท่านั้น ตัวอย่างเช่นs.decode('utf-8').encode('ascii', errors='ignore')
Spc_555

30

ตามที่ @artfulrobot สิ่งนี้ควรเร็วกว่าตัวกรองและแลมด้า:

re.sub(r'[^\x00-\x7f]',r'', your-non-ascii-string) 

ดูตัวอย่างเพิ่มเติมที่นี่ http://stackoverflow.com/questions/20078816/replace-non-ascii-characters-with-a-single-space/20079244#20079244


1
โซลูชันนี้ตอบคำถามที่ระบุไว้ของ OP แต่ระวังว่าจะไม่ลบอักขระที่ไม่สามารถพิมพ์ได้ซึ่งรวมอยู่ใน ASCII ซึ่งฉันคิดว่าเป็นสิ่งที่ OP ตั้งใจจะถาม
Danilo Souza Morães

6

คำถามของคุณไม่ชัดเจน สองประโยคแรกที่นำมารวมกันหมายความว่าคุณเชื่อว่าช่องว่างและ "จุด" ไม่ใช่อักขระ ASCII สิ่งนี้ไม่ถูกต้อง อักขระทั้งหมดเช่น ord (ถ่าน) <= 127 เป็นอักขระ ASCII ตัวอย่างเช่นฟังก์ชันของคุณไม่รวมอักขระเหล่านี้! "# $% & \ '() * +, -. / แต่รวมถึงอักขระอื่น ๆ เช่น [] {}

โปรดถอยกลับคิดสักนิดและแก้ไขคำถามของคุณเพื่อบอกเราว่าคุณกำลังพยายามทำอะไรโดยไม่ต้องพูดถึงคำว่า ASCII และทำไมคุณถึงคิดว่าตัวอักษรแบบนั้น ord (char)> = 128 นั้นไม่สนใจ นอกจากนี้: Python เวอร์ชันใด การเข้ารหัสข้อมูลอินพุตของคุณคืออะไร?

โปรดทราบว่ารหัสของคุณอ่านไฟล์อินพุตทั้งหมดเป็นสตริงเดียวและความคิดเห็นของคุณ ("วิธีแก้ปัญหาที่ยอดเยี่ยม") สำหรับคำตอบอื่นแสดงว่าคุณไม่สนใจการขึ้นบรรทัดใหม่ในข้อมูลของคุณ หากไฟล์ของคุณมีสองบรรทัดดังนี้:

this is line 1
this is line 2

ผลลัพธ์จะเป็น'this is line 1this is line 2'... นั่นคือสิ่งที่คุณต้องการจริงๆหรือ?

ทางออกที่ดีกว่า ได้แก่ :

  1. ชื่อที่ดีกว่าสำหรับฟังก์ชันตัวกรองมากกว่า onlyascii
  2. การรับรู้ว่าฟังก์ชันตัวกรองต้องการเพียงแค่ส่งคืนค่าที่แท้จริงหากยังคงอาร์กิวเมนต์ไว้:

    def filter_func(char):
        return char == '\n' or 32 <= ord(char) <= 126
    # and later:
    filtered_data = filter(filter_func, data).lower()

คำตอบนี้มีประโยชน์มากสำหรับพวกเราที่เข้ามาถามสิ่งที่คล้ายกับ OP และคำตอบที่คุณเสนอนั้นเป็นประโยชน์อย่างยิ่ง อย่างไรก็ตามฉันพบว่ามันแปลกที่ไม่มีวิธีแก้ปัญหาที่มีประสิทธิภาพมากกว่าอย่างที่คุณตีความ (ซึ่งฉันมักจะเจอ) - ทีละอักขระซึ่งใช้เวลานานมากในไฟล์ขนาดใหญ่มาก
Xodarap777

5

คุณสามารถใช้รหัสต่อไปนี้เพื่อลบตัวอักษรที่ไม่ใช่ภาษาอังกฤษ:

import re
str = "123456790 ABC#%? .(朱惠英)"
result = re.sub(r'[^\x00-\x7f]',r'', str)
print(result)

สิ่งนี้จะกลับมา

123456790 ABC #%? . ()


1

หากคุณต้องการอักขระ ascii ที่พิมพ์ได้คุณควรแก้ไขรหัสของคุณเป็น:

if ord(char) < 32 or ord(char) > 126: return ''

สิ่งนี้เทียบเท่ากับstring.printable(คำตอบจาก @jterrace) ยกเว้นกรณีที่ไม่มีผลตอบแทนและแท็บ ('\ t', '\ n', '\ x0b', '\ x0c' และ '\ r') แต่ไม่สอดคล้องกับ ช่วงคำถามของคุณ


1
ง่ายกว่าเล็กน้อย: lambda x: 32 <= ord (x) <= 126
jterrace

ซึ่งไม่เหมือนกับ string.printable เพราะมันจะออกจาก string.whitespace แม้ว่านั่นอาจเป็นสิ่งที่ OP ต้องการขึ้นอยู่กับสิ่งต่างๆเช่น \ n และ \ t
jterrace

@jterrace ขวารวมช่องว่าง (ord 32) แต่ไม่มีผลตอบแทนและแท็บ
joaquin

ใช่เพียงแสดงความคิดเห็นว่า "สิ่งนี้เทียบเท่ากับ string.printable" แต่ไม่เป็นความจริง
jterrace

ฉันแก้ไขคำตอบแล้วขอบคุณ! คำถาม OP จะทำให้เข้าใจผิดหากคุณไม่อ่านอย่างละเอียด
joaquin

1

หาทางผ่าน Fluent Python (Ramalho) - ขอแนะนำ รายการความเข้าใจ one-ish-liners ที่ได้รับแรงบันดาลใจจากบทที่ 2:

onlyascii = ''.join([s for s in data if ord(s) < 127])
onlymatch = ''.join([s for s in data if s in
              'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'])

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