ASCII olmayan karakterleri tek bir boşlukla değiştirme


244

ASCII olmayan (\ x00- \ x7F) karakterleri bir boşlukla değiştirmem gerekiyor. Bir şey eksik olmadıkça Python'da bunun kolay kolay olmadığına şaşırdım. Aşağıdaki işlev ASCII olmayan tüm karakterleri kaldırır:

def remove_non_ascii_1(text):

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

Ve bu ASCII olmayan karakterleri karakter kodu noktasındaki bayt miktarına göre boşluk miktarıyla değiştirir (yani, karakter 3 boşlukla değiştirilir):

def remove_non_ascii_2(text):

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

ASCII olmayan tüm karakterleri tek bir boşlukla nasıl değiştirebilirim?

Of sayısız ait benzer SO sorular , hiçbiri adresi karakter yedek olarak karşıt için sıyırma , ve ayrıca tüm ASCII olmayan karakterler değil, belirli bir karakter yöneliktir.


46
vay, gerçekten çok fazla bağlantı göstermek için iyi çaba harcadınız. Gün yenilendiğinde +1!
shad0w_wa1k3r

3
Bunu kaçırmış görünüyorsun stackoverflow.com/questions/1342000/…
Stuart

Sorunları olan bir örnek girdi görmek istiyorum.
dstromberg

5
@Stuart: Teşekkürler, ama ilk bahsettiğim bu.
dotancohen

1
@dstromberg: Bahse konu sorunlu bir örnek karakteri söz: . Bu var bu adam .
dotancohen

Yanıtlar:


243

Sizin ''.join()ifade edilir filtreleme şey ASCII olmayan kaldırarak; bunun yerine koşullu bir ifade kullanabilirsiniz:

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

Bu, karakterleri tek tek işler ve değiştirilen her karakter için bir boşluk kullanır.

Normal ifadeniz yalnızca ardışık ASCII olmayan karakterlerin yerine bir boşluk koymalıdır :

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

Not +orada.


18
@ dstromberg: daha yavaş; bir listeye str.join() ihtiyaç duyar (değerleri iki kez iletir) ve bir jeneratör ifadesi önce bire dönüştürülür. Bir liste kavrayışı vermek daha hızlıdır. Bu gönderiye bakın .
Martijn Pieters

1
UTF-8 bayt dizesini beslerseniz, ilk kod parçası karakter başına birden çok boşluk ekler.
Mark Ransom

@MarkRansom: Bunun Python 3 olduğunu varsayıyordum.
Martijn Pieters

2
Soruda " karakter 3 boşlukla değiştirildi" ifadesi, girdinin bir bit test (Unicode değil) olduğunu ve bu nedenle Python 2'nin kullanıldığını (aksi takdirde ''.joinbaşarısız olacağını ) belirtir . OP, Unicode kod noktası başına tek bir boşluk istiyorsa, girişin önce Unicode'a kodu çözülmesi gerekir.
jfs

Bu bana çok yardımcı oldu!
Muhammad Haseeb

55

Sizin için orijinal dizenizin en benzer temsilini almak için unidecode modülünü tavsiye ederim :

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

Sonra bir dizede kullanabilirsiniz:

remove_non_ascii("Ceñía")
Cenia

ilginç bir öneri, ancak kullanıcı uncii olmayan unidecode için kurallar olmak istediğini varsayar. Ancak bu, askere neden boşluklarda ısrar ettikleri, belki de başka bir karakterle yer değiştirmeleri hakkında bir soru soruyor?
jxramos

Teşekkür ederim, bu iyi bir cevap. Bu sorunun amacı için işe yaramaz çünkü uğraştığım verilerin çoğunun ASCII benzeri bir temsili yoktur. Gibi דותן. Ancak, genel anlamda bu harika, teşekkürler!
dotancohen

1
Evet, bunun bu soru için işe yaramadığını biliyorum , ama buraya bu sorunu çözmeye çalışırken indim, bu yüzden çözümümü sadece kendi sorunumla paylaşacağımı düşündüm, ki bu da @dotancohen olarak uğraşan insanlar için çok yaygın olduğunu düşünüyorum her zaman ascii olmayan karakterlerle.
Alvaro Fuentes

Geçmişte bunun gibi şeylerle ilgili bazı güvenlik açıkları vardı. Bunu nasıl uyguladığınıza dikkat edin!
deweydb

UTF-16 kodlu metin dizeleriyle çalışmıyor gibi görünüyor
user5359531 14:16

22

İçin karakter işleme, Unicode dizeleri kullanın:

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'

Ancak dizenizde ayrıştırılmış Unicode karakterler varsa (örneğin ayrı karakter ve aksan işaretlerini birleştirme) sorun yaşamaya devam edeceğinizi unutmayın:

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

Teşekkür ederim, bu önemli bir gözlem. Birleştirme işaretlerini ele almanın mantıklı bir yolunu bulursanız, soruya mutlu bir lütuf eklerim. Sanırım birleştirici işaretin kaldırılması, ancak birleştirilmemiş karakteri yalnız bırakmanın en iyisi olacağını düşünüyorum.
dotancohen

1
Kısmi bir çözüm, ud.normalize('NFC',s)işaretleri birleştirmek için kullanmaktır , ancak tüm birleştirme kombinasyonları tek kod noktaları ile temsil edilmez. ud.category()Karakteri incelemek için daha akıllı bir çözüme ihtiyacınız var .
Mark Tolonen

1
@dotancohen: Unicode'da birkaç Unicode kod noktasına yayılabilen "kullanıcı tarafından algılanan karakter" kavramı vardır. \X(eXtended grapheme cluster) regex ( regexmodül tarafından desteklenir ) bu karakterler üzerinde yineleme yapılmasına izin verir (not: "graphemes karakter dizilerini mutlaka birleştirmez ve karakter dizilerini birleştirmeye gerek yoktur" ).
jfs

10

Değiştirme karakteri '?' bir boşluk yerine, şunu öneririm 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(),
))

Sonuçlar:

0.7208260721400134
0.009975979187503592

Değiştirilsin mi? sonradan başka bir karakter ya da boşluk eklerseniz, yine de daha hızlı olursunuz.
Moritz

7

Peki ya bu?

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
Bu oldukça yetersiz olmasına rağmen, çok okunabilir. Teşekkür ederim.
dotancohen

1
Unicode kullanımı için +1 ... @dotancohen IMNSHO "okunabilir", "zarif" i ekleyen "pratik" anlamına gelir, bu yüzden "biraz
beceriksiz

3

Yerel ve etkili bir yaklaşım olarak, ordkarakterler üzerinde veya herhangi bir döngü kullanmanıza gerek yoktur . Sadece kodlayın asciive hataları görmezden gelin.

Aşağıdaki sadece ascii olmayan karakterleri kaldıracaktır:

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

Şimdi silinen karakterleri değiştirmek istiyorsanız aşağıdakileri yapın:

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

Python3'te bu encodebir test sonucunu döndürür, bu yüzden bunu aklınızda bulundurun. Ayrıca, bu yöntem satırsonu gibi karakterleri çıkarmaz.
Kyle Gibson

-1

Potansiyel olarak farklı bir soru için, ama @ Alvero'nun yanıtı (unidecode kullanarak) sürümümü veriyorum. Dizelerimde "normal" bir şerit yapmak, yani boşluk karakterleri için dizgimin başlangıcını ve sonunu yapmak ve sonra sadece diğer boşluk karakterlerini "normal" boşlukla değiştirmek istiyorum.

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

için

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

Önce unicode olmayan tüm alanları normal bir alanla değiştiririz (ve tekrar birleştiririz),

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

Ve sonra tekrar python'un normal bölünmesiyle böldük ve her bir "bit" i soyuyoruz,

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

Ve son olarak bunları tekrar birleştirin, ancak sadece dize bir iftesti geçerse ,

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

Ve bununla, safely_stripped('ㅤㅤㅤㅤCeñíaㅤmañanaㅤㅤㅤㅤ')doğru bir şekilde geri döner 'Ceñía mañana'.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.