비 ASCII 문자를 단일 공백으로 교체


244

모든 비 ASCII (\ x00- \ x7F) 문자를 공백으로 바꿔야합니다. 내가 뭔가 빠진 것이 아니라면 파이썬에서 이것이 쉽지 않은 것에 놀랐습니다. 다음 함수는 단순히 비 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가 아닌 모든 문자를 단일 공백으로 바꾸려면 어떻게해야합니까?

무수한 유사한 SO의 질문에 , 없음 주소 문자 교체 반대 제거 , 그리고 추가로 모든 비 ASCII 문자가 아닌 특정 문자 해결합니다.


46
와우, 당신은 정말 많은 링크를 보여주기 위해 열심히 노력했습니다. 하루가 갱신 되 자마자 +1!
shad0w_wa1k3r

3
이 하나를 놓친 것 같습니다. stackoverflow.com/questions/1342000/…
Stuart

문제가있는 예제 입력을보고 싶습니다.
dstromberg

5
@ 스튜어트 : 감사합니다. 그러나 그것은 제가 언급 한 첫 번째 것입니다.
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() 필요 합니다 (값을 두 번 넘길 것입니다). 제너레이터 표현식은 먼저 1로 변환됩니다. 목록 이해력을주는 것이 더 빠릅니다. 이 게시물을 참조하십시오 .
Martijn Pieters

1
UTF-8 바이트 문자열을 제공하면 첫 번째 코드는 문자 당 여러 개의 공백을 삽입합니다.
Mark Ransom

@MarkRansom : 저는 이것을 파이썬 3이라고 가정했습니다.
Martijn Pieters

2
질문에서 " 문자는 3 개의 공백으로 대체됩니다" 는 입력이 바이트 문자열 (유니 코드 아님)이므로 Python 2가 사용됨을 나타 ''.join냅니다 (그렇지 않으면 실패합니다). OP가 유니 코드 코드 포인트 당 단일 공백을 원하면 입력을 유니 코드로 먼저 디코딩해야합니다.
jfs

이것은 나를 많이 도와주었습니다!
Muhammad 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

들어 문자 처리, 유니 코드 문자열을 사용합니다 :

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'

그러나 문자열에 분해 된 유니 코드 문자 (예 : 별도의 문자와 악센트 부호 결합)가 포함 된 경우 여전히 문제가 있습니다.

>>> 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 : 유니 코드에는 여러 유니 코드 코드 포인트에 걸쳐있을 수있는 "사용자 인식 문자"라는 개념이 있습니다. \X(eXtended grapheme cluster) 정규식 ( 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

교체 ? 필요한 경우 나중에 다른 문자 또는 공백을 사용하면 여전히 더 빠릅니다.
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 ... @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을 반환하므로 명심하십시오. 또한이 방법은 줄 바꿈과 같은 문자를 제거하지 않습니다.
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.