Como posso remover caracteres não ASCII, mas deixar pontos e espaços usando Python?

100

Estou trabalhando com um arquivo .txt. Quero uma seqüência de caracteres do texto do arquivo sem caracteres não ASCII. No entanto, quero deixar espaços e pontos. No momento, estou tirando isso também. Aqui está o código:

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

Como devo modificar onlyascii () para deixar espaços e pontos? Imagino que não seja muito complicado, mas não consigo descobrir.

alexwlchan
fonte
Obrigado (sinceramente) pelo esclarecimento John. Eu entendi que espaços e pontos são caracteres ASCII. No entanto, eu estava removendo ambos sem querer ao tentar remover apenas caracteres não ASCII. Vejo como minha pergunta pode ter sugerido o contrário.
@PoliticalEconomist: Seu problema ainda está muito subespecificado. Veja minha resposta.
John Machin

Respostas:

187

Você pode filtrar todos os caracteres da string que não podem ser impressos usando string.printable , assim:

>>> 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 em minha máquina contém:

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

EDIT: No Python 3, o filtro retornará um iterável. A maneira correta de obter uma string de volta seria:

''.join(filter(lambda x: x in printable, s))
Jterrace
fonte
2
o que há com esses caracteres imprimíveis que estão abaixo do ordinal 48?
joaquin
38
O único problema de usar filteré que ele retorna um iterável. Se você precisa de uma volta string (como eu fiz porque eu precisava disso ao fazer compressão lista), em seguida, faça o seguinte: ''.join(filter(lambda x: x in string.printable, s).
cjbarth
5
@cjbarth - o comentário é específico do python 3, mas muito útil. Obrigado!
tremor
7
Por que não usar expressão regular: re.sub(r'[^\x00-\x7f]',r'', your-non-ascii-string). Veja este tópico stackoverflow.com/a/20079244/658497
Noam Manos
1
@NoamManos isso foi 4-5 vezes mais rápido para mim do que a solução de junção ... filtro ... lambda, obrigado.
artfulrobot de
95

Uma maneira fácil de mudar para um codec diferente é usando encode () ou decode (). No seu caso, você deseja converter para ASCII e ignorar todos os símbolos que não são suportados. Por exemplo, a letra sueca å não é um caractere 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

Editar:

Python3: str -> bytes -> 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 (decodificar e codificar na ordem reversa)

>>> "hej d\xe5".decode("ascii", errors="ignore").encode()
'hej d'
Zweedeend
fonte
16
Eu entendoUnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 27
Xodarap777
2
Recebi esse erro quando coloquei o caractere Unicode real na string por meio de copiar e colar. Quando você especifica uma string como u'thestring ', a codificação funciona corretamente.
Ben Liyanage
2
Funciona apenas no Py3, mas é elegante.
gaborous
7
Para aqueles que estão recebendo o mesmo erro que @ Xodarap777: você deve primeiro .decode () a string, e somente depois disso codifique. Por exemplos.decode('utf-8').encode('ascii', errors='ignore')
Spc_555
6

Sua pergunta é ambígua; as duas primeiras sentenças juntas implicam que você acredita que espaço e "ponto" são caracteres não ASCII. Isso está incorreto. Todos os caracteres como ord (char) <= 127 são caracteres ASCII. Por exemplo, sua função exclui esses caracteres! "# $% & \ '() * +, -. / Mas inclui vários outros, por exemplo [] {}.

Por favor, dê um passo para trás, pense um pouco e edite sua pergunta para nos dizer o que você está tentando fazer, sem mencionar a palavra ASCII, e por que você acha que caracteres como ord (char)> = 128 são ignoráveis. Além disso: qual versão do Python? Qual é a codificação de seus dados de entrada?

Observe que seu código lê todo o arquivo de entrada como uma única string, e seu comentário ("ótima solução") para outra resposta implica que você não se preocupa com novas linhas em seus dados. Se o seu arquivo contém duas linhas como esta:

this is line 1
this is line 2

o resultado seria 'this is line 1this is line 2' ... é isso que você realmente quer?

Uma solução maior incluiria:

  1. um nome melhor para a função de filtro do que onlyascii
  2. reconhecimento de que uma função de filtro precisa apenas retornar um valor verdadeiro se o argumento for retido:

    def filter_func(char):
        return char == '\n' or 32 <= ord(char) <= 126
    # and later:
    filtered_data = filter(filter_func, data).lower()
John Machin
fonte
Esta resposta é muito útil para aqueles de nós que vêm perguntar algo semelhante ao OP, e sua resposta proposta é proveitosamente pítônica. No entanto, acho estranho que não haja uma solução mais eficiente para o problema da forma como você o interpretou (o que costumo encontrar) - caractere por caractere, isso leva muito tempo em um arquivo muito grande.
Xodarap777
5

Você pode usar o seguinte código para remover letras que não sejam do inglês:

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

Isso vai voltar

123456790 ABC #%? . ()

Noha Elprince
fonte
1

Se você quiser caracteres ascii imprimíveis, provavelmente deve corrigir seu código para:

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

isto é equivalente a string.printable(resposta de @jterrace), exceto pela ausência de retornos e tabulações ('\ t', '\ n', '\ x0b', '\ x0c' e '\ r'), mas não corresponde a o alcance da sua pergunta

joaquin
fonte
1
Um pouco mais simples: lambda x: 32 <= ord (x) <= 126
jterrace
não é o mesmo que string.printable porque deixa de fora string.whitespace, embora isso possa ser o que o OP deseja, depende de coisas como \ n e \ t.
jterrace
@jterrace à direita, inclui espaço (ord 32), mas não retorna e tabs
joaquin
sim, apenas comentando sobre "isso é equivalente a string.printable", mas não é verdade
jterrace
Eu editei a resposta, obrigado! a pergunta do OP é enganosa se você não a ler com atenção.
joaquin
1

Trabalhando meu caminho através do Fluent Python (Ramalho) - altamente recomendado. Liners de compreensão de lista inspirados no Capítulo 2:

onlyascii = ''.join([s for s in data if ord(s) < 127])
onlymatch = ''.join([s for s in data if s in
              'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'])
Matthew Dunn
fonte
Isso não permitiria símbolos ASCII padrão, como marcadores, símbolo de graus, símbolo de copyright, símbolo do iene, etc. Além disso, seu primeiro exemplo inclui símbolos não imprimíveis, como BELL, o que é indesejável.
SherylHohman