Como verificar se uma string em Python está em ASCII?

211

Quero verificar se uma string está em ASCII ou não.

Estou ciente ord(), no entanto, quando tento ord('é'), tenho TypeError: ord() expected a character, but string of length 2 found. Eu entendi que isso é causado pela maneira como construí o Python (como explicado na ord()documentação de ).

Existe outra maneira de verificar?

Nico
fonte
A codificação de strings difere bastante entre Python 2 e Python 3, portanto, seria bom saber qual versão você está segmentando.
Florisla

Respostas:

188
def is_ascii(s):
    return all(ord(c) < 128 for c in s)
Alexander Kojevnikov
fonte
95
Ineficientemente ineficiente. Muito melhor tentar s.decode ('ascii') e capturar UnicodeDecodeError, conforme sugerido por Vincent Marchetti.
Ddaa 13/10/08
20
Não é ineficiente. all () entrará em curto-circuito e retornará False assim que encontrar um byte inválido.
John Millikin
10
Ineficiente ou não, o método mais pitônico é o try / except.
Jeremy Cantrell
43
É ineficiente em comparação com a tentativa / exceção. Aqui o loop está no intérprete. Com o formulário try / except, o loop está na implementação do codec C chamado por str.decode ('ascii'). E eu concordo, a forma try / except também é mais pitônica.
Ddaa 16/10/08
25
@JohnMachin ord(c) < 128é infinitamente mais legível e intuitivo do quec <= "\x7F"
Slater Victoroff
252

Eu acho que você não está fazendo a pergunta certa--

Uma cadeia de caracteres em python não possui propriedades correspondentes a 'ascii', utf-8 ou qualquer outra codificação. A fonte da sua string (se você a lê em um arquivo, entrada em um teclado etc.) pode ter codificado uma string unicode em ascii para produzir sua string, mas é aí que você precisa obter uma resposta.

Talvez a pergunta que você possa fazer seja: "Essa string é o resultado da codificação de uma string unicode em ascii?" - Isso você pode responder tentando:

try:
    mystring.decode('ascii')
except UnicodeDecodeError:
    print "it was not a ascii-encoded unicode string"
else:
    print "It may have been an ascii-encoded unicode string"
Vincent Marchetti
fonte
28
use encode é melhor, porque string no decode method em python 3, veja qual é a diferença entre encode / decode? (python 2.x)
Jet Guo
@ Sri: Isso é porque você está usando em uma string não codificada ( strno Python 2, bytesno Python 3).
dotancohen
No Python 2, essa solução funciona apenas para uma cadeia unicode . A strem qualquer codificação ISO precisaria ser codificada para Unicode primeiro. A resposta deve entrar nisso.
Alexis
@ JetGuo: você deve usar ambos, dependendo do tipo de entrada: s.decode('ascii') if isinstance(s, bytes) else s.encode('ascii')no Python 3. A entrada do OP é um bytestring 'é'(sintaxe do Python 2, o Python 3 não havia sido lançado no momento) e, portanto, .decode()está correto.
jfs
2
@exex: errado. strno Python 2 é um bytestring. É correto usar .decode('ascii')para descobrir se todos os bytes estão no intervalo ascii.
jfs
153

Maneira Python 3:

isascii = lambda s: len(s) == len(s.encode())

Para verificar, passe na sequência de teste:

str1 = "♥O◘♦♥O◘♦"
str2 = "Python"

print(isascii(str1)) -> will return False
print(isascii(str2)) -> will return True
longe
fonte
7
Este é um pequeno truque para detectar caracteres não-ascii em strings Unicode, que no python3 são praticamente todas as strings. Como os caracteres ascii podem ser codificados usando apenas 1 byte, o comprimento de qualquer caractere ascii será fiel ao seu tamanho após a codificação em bytes; enquanto outros caracteres não-ascii serão codificados para 2 bytes ou 3 bytes de acordo, o que aumentará seu tamanho.
Devy
Por @far a melhor resposta, mas não que alguns caracteres como ... e - possam parecer ascii, caso você queira usá-lo para detectar texto em inglês, substitua esses caracteres antes de verificar
Christophe Roussy
1
Mas no Python2 lançará um UnicodeEncodeError. Preciso encontrar uma solução para Py2 e Py3
alvas
2
Para aqueles que não estão familiarizados com o uso do lambda (como eu era quando me deparei com essa resposta) isasciiagora é uma função que você passa uma string: isascii('somestring')== Truee isascii('àéç')==False
rabidang3ls
8
Isso é simplesmente um desperdício. Ele codifica uma string em UTF-8, criando uma outra bytestring. A maneira verdadeira de Python 3 é try: s.encode('ascii'); return True except UnicodeEncodeError: return False(como acima, mas codificando, como as strings são Unicode no Python 3). Essa resposta também levanta um erro no Python 3 quando você tem substitutos (por exemplo, isascii('\uD800')gera um erro em vez de voltar False)
Artyer
71

Novo no Python 3.7 ( bpo32677 )

Chega de verificações ascii cansativas / ineficientes nas strings, novo método str/ bytes/ bytearrayinterno - .isascii()verificará se as strings são ascii.

print("is this ascii?".isascii())
# True
abccd
fonte
Este merece estar no topo!
Salek
"\x03".isascii()também é verdadeiro. A documentação diz que isso apenas verifica se todos os caracteres estão abaixo do ponto de código 128 (0-127). Se você também quer evitar caracteres de controle, você vai precisar de: text.isascii() and text.isprintable(). Apenas usar isprintablepor si só também não é suficiente, pois considerará um caractere como ¿imprimível (corretamente), mas não está dentro da seção imprimível ascii, portanto, você deve verificar os dois se desejar os dois. Ainda outro problema: os espaços são considerados imprimíveis, as guias e as novas linhas não.
Luc
19

Encontrei algo assim recentemente - para referência futura

import chardet

encoding = chardet.detect(string)
if encoding['encoding'] == 'ascii':
    print 'string is in ascii'

com o qual você pode usar:

string_ascii = string.decode(encoding['encoding']).encode('ascii')
Alvin
fonte
7
Obviamente, isso requer a biblioteca chardet .
StackExchange entristece dancek
1
sim, embora chardet está disponível por padrão na maioria das instalações
Alvin
7
chardet única adivinhar a codificação com uma certa probabilidade assim: {'confidence': 0.99, 'encoding': 'EUC-JP'}(que neste caso era completamente errado)
Suzana
19

Vincent Marchetti tem a idéia certa, mas str.decodefoi preterido no Python 3. No Python 3, você pode fazer o mesmo teste com str.encode:

try:
    mystring.encode('ascii')
except UnicodeEncodeError:
    pass  # string is not ascii
else:
    pass  # string is ascii

Observe que a exceção que você deseja capturar também mudou de UnicodeDecodeErrorpara UnicodeEncodeError.

drs
fonte
A entrada do OP é uma bytestring ( bytesdigite no Python 3 que não possui .encode()método). .decode()na resposta de @Vincent Marchetti está correta .
jfs
@JFSebastian O OP pergunta "Como verificar se uma string em Python está em ASCII?" e não especifica bytes vs cadeias unicode. Por que você diz que a entrada dele é uma bytestring?
drs
1
veja a data da pergunta: 'é'houve um desvio na época.
jfs
1
@JFSebastian, ok, bem, considerando que esta resposta responde a esta pergunta como se fosse perguntada hoje, acho que ainda é válida e útil. Menos e menos pessoas virão aqui procurando respostas como se estivessem executando o Python em 2008
drs
2
Encontrei essa pergunta quando eu estava procurando uma solução para python3 e a leitura rápida da pergunta não me fez suspeitar que isso era python 2 specfic. Mas essa resposta foi realmente útil - votação positiva!
Josch
17

Sua pergunta está incorreta; o erro que você vê não é resultado de como você criou python, mas de uma confusão entre cadeias de bytes e cadeias unicode.

Cadeias de bytes (por exemplo, "foo", ou 'bar', na sintaxe python) são sequências de octetos; números de 0 a 255. Strings Unicode (por exemplo, u "foo" ou u'bar ') são sequências de pontos de código unicode; números de 0 a 11202064. Mas você parece estar interessado no caractere é, que (em seu terminal) é uma sequência de vários bytes que representa um único caractere.

Em vez de ord(u'é'), tente o seguinte:

>>> [ord(x) for x in u'é']

Isso indica qual sequência de pontos de código "é" representa. Pode dar a você [233], ou pode dar a você [101, 770].

Em vez de chr()reverter isso, existe unichr():

>>> unichr(233)
u'\xe9'

Na verdade, esse caractere pode ser representado como um ou vários "pontos de código" unicode, que representam grafos ou caracteres. É "e com um sotaque agudo (isto é, ponto de código 233)" ou "e" (ponto de código 101), seguido por "um sotaque agudo do caractere anterior" (ponto de código 770). Portanto, esse mesmo caractere exato pode ser apresentado como a estrutura de dados Python u'e\u0301'ouu'\u00e9' .

Na maioria das vezes, você não precisa se preocupar com isso, mas isso pode se tornar um problema se você estiver iterando sobre uma cadeia unicode, pois a iteração funciona por ponto de código, não por caractere decomponível. Em outras palavras, len(u'e\u0301') == 2e len(u'\u00e9') == 1. Se isso lhe interessa, você pode converter entre formulários compostos e decompostos usando unicodedata.normalize.

O Glossário Unicode pode ser um guia útil para entender alguns desses problemas, apontando como cada termo específico se refere a uma parte diferente da representação do texto, o que é muito mais complicado do que muitos programadores imaginam.

Glifo
fonte
3
'é' não representa necessariamente um único ponto de código. Podem ser dois pontos de código (U + 0065 + U + 0301).
JFS
2
Cada caractere abstrato é sempre representado por um único ponto de código. No entanto, os pontos de código podem ser codificados para vários bytes, dependendo do esquema de codificação. ou seja, 'é' tem dois bytes em UTF-8 e UTF-16 e quatro bytes em UTF-32, mas, em cada caso, ainda é um único ponto de código - U + 00E9.
Ben Blank
5
Em branco @ Ben: U + 0065 e U + 0301 são pontos de código e eles fazem representam 'é', que pode também ser representado por U + 00E9. Google "combinando sotaque agudo".
JFS
JF está certo ao combinar U + 0065 e U + 0301 para formar 'é', mas este não é um functino reversível. Você receberá U + 00E9. De acordo com a wikipedia , esses pontos de código composto são úteis para compatibilidade com versões anteriores
Martin Konecny
1
@teehoo - É uma função reversível no sentido em que você pode normalizar novamente o ponto de código que representa o caractere composto em uma sequência de pontos de código que representam o mesmo caractere composto. No Python, você pode fazer o seguinte: unicodedata.normalize ('NFD', u '\ xe9').
Glyph
10

Que tal fazer isso?

import string

def isAscii(s):
    for c in s:
        if c not in string.ascii_letters:
            return False
    return True
miya
fonte
5
Isso falhará se a string contiver caracteres ASCII que não são letras. Para você codificar exemplos, isso inclui nova linha, espaço, ponto, vírgula, sublinhado e parênteses.
Florisla
9

Encontrei essa pergunta ao tentar determinar como usar / codificar / decodificar uma string cuja codificação eu não tinha certeza (e como escapar / converter caracteres especiais nessa string).

Meu primeiro passo deveria ter sido verificar o tipo da string - eu não sabia que podia obter bons dados sobre a formatação dos tipos. Esta resposta foi muito útil e chegou à raiz real dos meus problemas.

Se você está sendo grosseiro e persistente

UnicodeDecodeError: o codec 'ascii' não pode decodificar o byte 0xc3 na posição 263: ordinal fora do intervalo (128)

particularmente quando você está codificando, verifique se não está tentando unicode () uma string que já é unicode - por algum motivo terrível, você recebe erros de codec ascii. (Veja também a receita do Python Kitchen e os documentos do Python tutoriais de para entender melhor o quão terrível isso pode ser.)

Eventualmente, eu determinei que o que eu queria fazer era o seguinte:

escaped_string = unicode(original_string.encode('ascii','xmlcharrefreplace'))

Também útil na depuração foi definir a codificação padrão no meu arquivo para utf-8 (coloque isso no início do seu arquivo python):

# -*- coding: utf-8 -*-

Isso permite que você teste caracteres especiais ('àéç') sem precisar usar seus escapes unicode (u '\ xe0 \ xe9 \ xe7').

>>> specials='àéç'
>>> specials.decode('latin-1').encode('ascii','xmlcharrefreplace')
'&#224;&#233;&#231;'
Max P Magee
fonte
4

Para melhorar a solução de Alexander do Python 2.6 (e no Python 3.x), você pode usar o módulo auxiliar curses.ascii e a função curses.ascii.isascii () ou várias outras funções: https://docs.python.org/2.6/ library / curses.ascii.html

from curses import ascii

def isascii(s):
    return all(ascii.isascii(c) for c in s)
Sergey Nevmerzhitsky
fonte
2

Você pode usar a biblioteca de expressões regulares que aceita a definição padrão [[: ASCII:]] do Posix.

Steve Moyer
fonte
2

Uma picada ( str-tipo) no Python é uma série de bytes. Não tem como dizer, apenas olhando para a string, se essa série de bytes representa uma string ascii, uma string em um conjunto de caracteres de 8 bits como ISO-8859-1 ou uma string codificada com UTF-8 ou UTF-16 ou qualquer outra coisa. .

No entanto, se você souber a codificação usada, poderá decodecolocar a string em uma string unicode e, em seguida, usar uma expressão regular (ou um loop) para verificar se ela contém caracteres fora do intervalo que você está preocupado.

JacquesB
fonte
1

Como a resposta de @ RogerDahl, mas é mais eficiente dar um curto-circuito negando a classe de personagem e usando a pesquisa em vez de find_allou match.

>>> import re
>>> re.search('[^\x00-\x7F]', 'Did you catch that \x00?') is not None
False
>>> re.search('[^\x00-\x7F]', 'Did you catch that \xFF?') is not None
True

Imagino que uma expressão regular seja bem otimizada para isso.

fogão
fonte
0
import re

def is_ascii(s):
    return bool(re.match(r'[\x00-\x7F]+$', s))

Para incluir uma sequência vazia como ASCII, altere +para *.

Roger Dahl
fonte
-1

Para evitar falhas no código, talvez você queira usar um try-exceptpara capturarTypeErrors

>>> ord("¶")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: ord() expected a character, but string of length 2 found

Por exemplo

def is_ascii(s):
    try:
        return all(ord(c) < 128 for c in s)
    except TypeError:
        return False

fonte
Este tryinvólucro é completamente inútil. Se "¶"for uma string Unicode, ord("¶")funcionará e, se não for (Python 2), for c in sa decomporá em bytes ord, continuando a funcionar.
Ry-
-5

Eu uso o seguinte para determinar se a seqüência de caracteres é ascii ou unicode:

>> print 'test string'.__class__.__name__
str
>>> print u'test string'.__class__.__name__
unicode
>>> 

Em seguida, basta usar um bloco condicional para definir a função:

def is_ascii(input):
    if input.__class__.__name__ == "str":
        return True
    return False
mvknowles
fonte
4
-1 AARRGGHH trata todos os caracteres com ord (c) no intervalo (128, 256) como ASCII !!!
John Knin
Não funciona Tente chamar o seguinte: is_ascii(u'i am ascii'). Mesmo que as letras e os espaços sejam definitivamente ASCII, isso ainda retorna Falseporque forçamos a string a ser unicode.
Jpmc26