非ASCII文字を1つのスペースに置き換えます


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以外のすべての文字を1つのスペースに置き換えるにはどうすればよいですか?

無数同様のSOの質問なしのアドレス文字代替として反対するストリッピングそしてさらにすべての非ASCII文字ではない、特定の文字取り組みます。


46
うわー、あなたは本当に多くのリンクを表示するために本当に良い努力をしました。日が更新されるとすぐに+1します!
shad0w_wa1k3r 2013年

3
あなたはこの1見逃しているように見えるstackoverflow.com/questions/1342000/...
スチュアート

問題のある入力例を見たいです。
dstromberg 2013年

5
@スチュアート:ありがとうございます。しかし、これは私が言及する最初の問題です。
dotancohen 2013年

1
@dstromberg:問題のある文字の例について質問で述べました。それはだ、この男
dotancohen 2013年

回答:


243

あなたの''.join()表現はフィルタリングであり、非ASCIIを削除します。代わりに条件式を使用できます。

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

これは文字を1つずつ処理し、置換された文字ごとに1つのスペースを使用します。

正規表現は、ASCII以外の連続する文字をスペースに置き換えるだけです。

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

+そこに注意してください。


18
@dstromberg:遅い; リストstr.join() が必要です(値が2回渡されます)。ジェネレータ式は最初に1つに変換されます。リスト内包表記を指定する方が簡単です。この投稿を参照してください。
Martijn Pieters

1
最初のコードでは、UTF-8バイト文字列をフィードすると、文字ごとに複数の空白が挿入されます。
Mark Ransom

@MarkRansom:これはPython 3であると想定していました。–
Martijn Pieters

2
質問の文字が3つのスペースで置換される」は、入力がバイト文字列(Unicodeではない)であるため、Python 2が使用されることを意味します(それ以外の場合''.joinは失敗します)。OPがUnicodeコードポイントごとに単一のスペースを必要とする場合、入力を最初にUnicodeにデコードする必要があります。
jfs

これは私をたくさん助けました!
ムハンマドハジーブ

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 2016

ありがとう、これは良い答えです。私が扱っているデータのほとんどはASCIIのような表現を持っていないので、この質問の目的には適していません。などדותן。しかし、一般的な意味でこれは素晴らしいです、ありがとう!
dotancohen 2016

1
はい、これはこの質問ではうまくいかないことはわかっていますが、私はその問題を解決するためにここに着陸したので、自分の問題の解決策を共有するだけだと思いました。常に非ASCII文字で。
Alvaro Fuentes

過去に、このようなセキュリティ上の脆弱性がいくつかありました。これを実装する方法に注意してください!
deweydb 2016年

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 2013年

1
部分的な解決策はud.normalize('NFC',s)、マークの組み合わせに使用することですが、すべての組み合わせの組み合わせが単一のコードポイントで表されるわけではありません。あなたud.category()はキャラクターを見るよりスマートなソリューションが必要です。
Mark Tolonen 2013年

1
@dotancohen:Unicodeには「ユーザーが認識する文字」という概念があり、複数のUnicodeコードポイントにまたがることがあります。\X(拡張書記素クラスタ)正規表現(regexモジュールでサポート)では、このような文字を反復処理できます(注:「書記素は必ずしも文字列を結合しているわけではなく、文字列を結合しているとは必ずしも書記素ではない」)。
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

置き換えますか?その後、必要に応じて別の文字またはスペースを追加します。
モリッツ

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 ... @dotancohen IMNSHO「読み取り可能」は「実用的」であることを意味し、「エレガント」に追加されるため、「少しエレガントではない」と言います
qneill

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はバイト文字列を返すので、それを覚えておいてください。また、このメソッドは改行などの文字を削除しません。
カイルギブソン

-1

別の質問の可能性がありますが、@ Alveroの回答のバージョンを提供しています(ユニデコードを使用)。文字列、つまり空白文字の文字列の最初と最後に「通常の」ストリップを実行し、他の空白文字のみを「通常の」スペースに置き換えたい、つまり

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

最初に、非Unicodeスペースをすべて通常のスペースに置き換えます(そして再び結合します)。

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

そして、Pythonの通常の分割でそれを再び分割し、各「ビット」を取り除きます。

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