Ersetzen Sie Nicht-ASCII-Zeichen durch ein Leerzeichen


244

Ich muss alle Nicht-ASCII-Zeichen (\ x00- \ x7F) durch ein Leerzeichen ersetzen. Ich bin überrascht, dass dies in Python nicht ganz einfach ist, es sei denn, ich vermisse etwas. Die folgende Funktion entfernt einfach alle Nicht-ASCII-Zeichen:

def remove_non_ascii_1(text):

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

Und dieses ersetzt Nicht-ASCII-Zeichen durch die Anzahl der Leerzeichen gemäß der Anzahl der Bytes im Zeichencodepunkt (dh das Zeichen wird durch 3 Leerzeichen ersetzt):

def remove_non_ascii_2(text):

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

Wie kann ich alle Nicht-ASCII-Zeichen durch ein Leerzeichen ersetzen?

Von der Vielzahl von ähnlichen SO Fragen , keine Adresse Zeichen Ersatz als Gegensatz zum Strippen , und zusätzlich alle Nicht-ASCII - Zeichen - Adresse eines bestimmtes Zeichen.


46
Wow, du hast wirklich gute Anstrengungen unternommen, um so viele Links zu zeigen. +1 sobald der Tag erneuert wird!
Shad0w_wa1k3r

3
Sie scheinen diesen einen stackoverflow.com/questions/1342000/…
Stuart

Ich bin daran interessiert, eine Beispieleingabe zu sehen, die Probleme hat.
Dstromberg

5
@Stuart: Danke, aber das ist das allererste, das ich erwähne.
Dotancohen

1
@dstromberg: Ich erwähne ein problematisches Beispielzeichen in der Frage : . Es ist dieser Typ .
Dotancohen

Antworten:


243

Ihr ''.join()Ausdruck filtert und entfernt alles, was nicht ASCII ist. Sie können stattdessen einen bedingten Ausdruck verwenden:

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

Dies behandelt Zeichen nacheinander und würde immer noch ein Leerzeichen pro ersetztem Zeichen verwenden.

Ihr regulärer Ausdruck sollte nur aufeinanderfolgende Nicht-ASCII-Zeichen durch ein Leerzeichen ersetzen :

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

Beachten Sie das +dort.


18
@dstromberg: langsamer; str.join() benötigt eine Liste (die Werte werden zweimal übergeben), und ein Generatorausdruck wird zuerst in einen konvertiert. Ein Listenverständnis zu vermitteln ist einfach schneller. Siehe diesen Beitrag .
Martijn Pieters

1
Der erste Code fügt mehrere Leerzeichen pro Zeichen ein, wenn Sie ihm eine UTF-8-Byte-Zeichenfolge zuführen.
Mark Ransom

@ MarkRansom: Ich nahm an, dass dies Python 3 ist.
Martijn Pieters

2
" Zeichen wird durch 3 Leerzeichen ersetzt" in der Frage impliziert, dass die Eingabe ein Bytestring (nicht Unicode) ist und daher Python 2 verwendet wird (andernfalls ''.joinwürde dies fehlschlagen). Wenn OP ein einzelnes Leerzeichen pro Unicode-Codepunkt möchte, sollte die Eingabe zuerst in Unicode dekodiert werden.
JFS

Das hat mir sehr geholfen!
Muhammad Haseeb

55

Für Sie, um die ähnlichste Darstellung Ihrer ursprünglichen Zeichenfolge zu erhalten, empfehle ich das Unidecode-Modul :

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

Dann können Sie es in einer Zeichenfolge verwenden:

remove_non_ascii("Ceñía")
Cenia

interessanter Vorschlag, aber es wird davon ausgegangen, dass der Benutzer wünscht, dass nicht ASCII die Regeln für Unidecode werden. Dies wirft jedoch eine Folgefrage an den Fragesteller auf, warum er auf Leerzeichen besteht, um sie möglicherweise durch einen anderen Charakter zu ersetzen.
Jxramos

Danke, das ist eine gute Antwort. Für diese Frage funktioniert dies nicht , da die meisten Daten, mit denen ich zu tun habe, keine ASCII-ähnliche Darstellung haben. Wie zum Beispiel דותן. Im Allgemeinen ist dies jedoch großartig, danke!
Dotancohen

1
Ja, ich weiß, dass dies bei dieser Frage nicht funktioniert , aber ich bin hier gelandet, um dieses Problem zu lösen, und dachte, ich würde nur meine Lösung für mein eigenes Problem teilen, was meiner Meinung nach bei Leuten wie @dotancohen, die sich damit befassen, sehr häufig ist mit Nicht-ASCII-Zeichen die ganze Zeit.
Alvaro Fuentes

In der Vergangenheit gab es einige Sicherheitslücken bei solchen Dingen. Seien Sie vorsichtig, wie Sie dies umsetzen!
Deweydb

Scheint nicht mit UTF-16-codierten Textzeichenfolgen zu funktionieren
user5359531

22

Verwenden Sie für die Zeichenverarbeitung Unicode-Zeichenfolgen:

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'

Beachten Sie jedoch, dass Sie immer noch ein Problem haben, wenn Ihre Zeichenfolge zerlegte Unicode-Zeichen enthält (z. B. separates Zeichen und kombinierte Akzentzeichen):

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

Vielen Dank, dies ist eine wichtige Beobachtung. Wenn Sie einen logischen Weg finden, um mit dem Fall des Kombinierens von Marken umzugehen, würde ich der Frage gerne ein Kopfgeld hinzufügen. Ich nehme an, dass es am besten wäre, einfach die Kombinationsmarke zu entfernen und dabei den nicht kombinierten Charakter in Ruhe zu lassen.
Dotancohen

1
Eine Teillösung besteht darin ud.normalize('NFC',s), Markierungen zu kombinieren, aber nicht alle Kombinationskombinationen werden durch einzelne Codepunkte dargestellt. Sie würden eine intelligentere Lösung benötigen, um ud.category()den Charakter zu betrachten.
Mark Tolonen

1
@dotancohen: In Unicode gibt es den Begriff "vom Benutzer wahrgenommenes Zeichen", der sich über mehrere Unicode-Codepunkte erstrecken kann. \X(eXtended Grapheme Cluster) Regex (vom regexModul unterstützt) ermöglicht das Durchlaufen solcher Zeichen (Hinweis: "Grapheme kombinieren nicht unbedingt Zeichenfolgen, und das Kombinieren von Zeichenfolgen ist nicht unbedingt ein Graphem" ).
JFS

10

Wenn das Ersatzzeichen '?' Anstelle eines Leerzeichens würde ich vorschlagen 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(),
))

Ergebnisse:

0.7208260721400134
0.009975979187503592

Ersetze das ? mit einem anderen Zeichen oder Leerzeichen danach, wenn nötig, und Sie wären immer noch schneller.
Moritz

7

Was ist mit diesem?

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
Obwohl dies ziemlich unelegant ist, ist es sehr gut lesbar. Danke dir.
Dotancohen

1
+1 für die Unicode-Behandlung ... @dotancohen IMNSHO "lesbar" impliziert "praktisch", was zu "elegant"
beiträgt

3

Als nativer und effizienter Ansatz müssen Sie keine ordZeichen oder Schleifen verwenden. Codieren Sie einfach mit asciiund ignorieren Sie die Fehler.

Im Folgenden werden nur die Nicht-ASCII-Zeichen entfernt:

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

Wenn Sie nun die gelöschten Zeichen ersetzen möchten, gehen Sie wie folgt vor:

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

In Python3 gibt dies encodeeinen Bytestring zurück. Denken Sie also daran. Außerdem werden bei dieser Methode keine Zeichen wie Zeilenumbrüche entfernt.
Kyle Gibson

-1

Möglicherweise für eine andere Frage, aber ich biete meine Version der Antwort von @ Alvero an (unter Verwendung von Unidecode). Ich möchte einen "regulären" Streifen für meine Zeichenfolgen erstellen, dh den Anfang und das Ende meiner Zeichenfolge für Leerzeichen, und dann nur andere Leerzeichen durch ein "reguläres" Leerzeichen ersetzen, d. H.

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

zu

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

Wir ersetzen zuerst alle Nicht-Unicode-Leerzeichen durch ein reguläres Leerzeichen (und verbinden es wieder).

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

Und dann teilen wir das noch einmal mit Pythons normalem Split und entfernen jedes "Bit".

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

Und zuletzt verbinden Sie diese wieder, aber nur, wenn die Zeichenfolge einen ifTest besteht,

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

Und damit safely_stripped('ㅤㅤㅤㅤCeñíaㅤmañanaㅤㅤㅤㅤ')richtig zurück 'Ceñía mañana'.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.