Jak sprawdzić, czy ciąg znaków w Pythonie jest w ASCII?

211

Chcę sprawdzić, czy ciąg znaków jest w ASCII, czy nie.

Jestem tego świadomy ord(), ale kiedy próbuję ord('é'), mam TypeError: ord() expected a character, but string of length 2 found. Zrozumiałem, że jest to spowodowane sposobem, w jaki zbudowałem Pythona (jak wyjaśniono w ord()dokumentacji ).

Czy istnieje inny sposób sprawdzenia?

Nico
źródło
Kodowanie łańcuchów znaków różni się nieco w Pythonie 2 i Pythonie 3, więc dobrze byłoby wiedzieć, na którą wersję kierujesz swój cel.
florisla

Odpowiedzi:

188
def is_ascii(s):
    return all(ord(c) < 128 for c in s)
Alexander Kojevnikov
źródło
95
Bezcelowo nieefektywne. Znacznie lepiej wypróbować s.decode ('ascii') i złapać UnicodeDecodeError, jak sugeruje Vincent Marchetti.
ddaa
20
To nie jest nieefektywne. all () spowoduje zwarcie i zwróci False, gdy tylko napotka nieprawidłowy bajt.
John Millikin,
10
Nieefektywna lub nie, tym bardziej pythoniczną metodą jest try / wyjątkiem.
Jeremy Cantrell
43
Jest nieefektywny w porównaniu do try / wyjątkiem. Tutaj pętla znajduje się w tłumaczu. W postaci try / try pętla znajduje się w implementacji kodeka C wywoływanej przez str.decode („ascii”). I zgadzam się, forma try / try jest również bardziej pytoniczna.
ddaa
25
@JohnMachin ord(c) < 128jest nieskończenie bardziej czytelny i intuicyjny niżc <= "\x7F"
Slater Victoroff
252

Myślę, że nie zadajesz właściwego pytania ...

Łańcuch w pythonie nie ma właściwości odpowiadającej „ascii”, utf-8 lub innemu kodowaniu. Źródło łańcucha (niezależnie od tego, czy czytasz go z pliku, dane wejściowe z klawiatury itp.) Mogło zakodować łańcuch znaków Unicode w ascii, aby wygenerować łańcuch, ale tam musisz znaleźć odpowiedź.

Być może pytanie, które możesz zadać, brzmi: „Czy ten ciąg jest wynikiem kodowania ciągu Unicode w ascii?” - Na to możesz odpowiedzieć, próbując:

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
źródło
28
użycie kodowania jest lepsze, ponieważ łańcuch bez metody dekodowania w pythonie 3, widzisz jaka jest różnica między kodowaniem / dekodowaniem? (python 2.x)
Jet Guo
@Sri: To dlatego, że używasz go na niekodowanym ciągu ( strw Python 2, bytesw Python 3).
dotancohen
W Pythonie 2 to rozwiązanie działa tylko dla ciągu znaków Unicode . A strw dowolnym kodowaniu ISO musiałoby najpierw zostać zakodowane w Unicode. Odpowiedź powinna się w tym znaleźć.
Alexis
@JetGuo: powinieneś używać obu w zależności od typu danych wejściowych: s.decode('ascii') if isinstance(s, bytes) else s.encode('ascii')w Pythonie 3. Dane wejściowe OP to testowanie 'é'(składnia Pythona 2, Python 3 nie został w tym czasie wydany) i dlatego .decode()jest poprawna.
jfs
2
@alexis: źle. strw Pythonie 2 to testowanie. Prawidłowe jest użycie, .decode('ascii')aby dowiedzieć się, czy wszystkie bajty są w zakresie ascii.
jfs
153

Python 3 sposób:

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

Aby to sprawdzić, przekaż ciąg testowy:

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

print(isascii(str1)) -> will return False
print(isascii(str2)) -> will return True
daleko
źródło
7
Jest to niezła sztuczka do wykrywania znaków innych niż ascii w ciągach znaków Unicode, które w python3 to właściwie wszystkie ciągi znaków. Ponieważ znaki ascii mogą być kodowane przy użyciu tylko 1 bajtu, więc dowolna długość znaków ascii będzie zgodna z ich rozmiarem po zakodowaniu w bajtach; podczas gdy inne znaki nie-ascii będą kodowane odpowiednio do 2 lub 3 bajtów, co zwiększy ich rozmiary.
Devy
Przez @far najlepsza odpowiedź, ale nie to, że niektóre znaki takie jak ... i - mogą wyglądać jak ascii, więc jeśli chcesz użyć tego do wykrywania tekstu w języku angielskim, wymień takie znaki przed sprawdzeniem
Christophe Roussy
1
Ale w Python2 wyrzuci błąd UnicodeEncodeError. Muszę znaleźć rozwiązanie dla Py2 i Py3
alvas
2
Dla tych, którzy nie są zaznajomieni z używaniem lambda (tak jak ja, kiedy pierwszy raz natknąłem się na tę odpowiedź), isasciijest teraz funkcja, która przekazuje ciąg: isascii('somestring')== Truei isascii('àéç')==False
rabidang3ls
8
To po prostu marnotrawstwo. Koduje ciąg znaków w UTF-8, tworząc zupełnie inne bajtowanie. Prawdziwy sposób na Python 3 jest try: s.encode('ascii'); return True except UnicodeEncodeError: return False(jak wyżej, ale kodowanie, ponieważ w Pythonie 3 ciągi znaków są Unicode). Ta odpowiedź powoduje także błąd w Pythonie 3, gdy masz zastępcze dane (np. isascii('\uD800')Podnosi błąd zamiast wracać False)
Artyer
71

Nowy w Python 3.7 ( bpo32677 )

Nigdy więcej męczących / nieefektywnych kontroli ascii na łańcuchach, nowa wbudowana metoda str/ bytes/ bytearray- .isascii()sprawdzi, czy łańcuchy są ascii.

print("is this ascii?".isascii())
# True
abccd
źródło
Ten zasługuje na to, aby być na szczycie!
Salek
"\x03".isascii()jest również Prawdą. Dokumentacja mówi, że to tylko sprawdza, czy wszystkie znaki znajdują się poniżej punktu kodowego 128 (0-127). Jeśli chcesz, aby uniknąć znaki sterujące, trzeba będzie: text.isascii() and text.isprintable(). Samo użycie isprintablerównież nie jest wystarczające, ponieważ uzna, że ​​znak taki jak ¿może być (poprawnie) nadający się do wydruku, ale nie znajduje się w sekcji do drukowania ascii, więc musisz sprawdzić oba, jeśli chcesz oba. Jeszcze jedna gotcha: spacje są uważane za drukowalne, tabulatory i znaki nowej linii nie.
Luc
19

Wpadłem ostatnio na coś takiego - na przyszłość

import chardet

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

którego możesz użyć z:

string_ascii = string.decode(encoding['encoding']).encode('ascii')
Alvin
źródło
7
Oczywiście wymaga to biblioteki chardet .
StackExchange zasmuca tańca
1
tak, chociaż chardet jest domyślnie dostępny w większości instalacji
Alvin
7
chardet zgaduje kodowanie tylko z pewnym prawdopodobieństwem, takim jak to: {'confidence': 0.99, 'encoding': 'EUC-JP'}(co w tym przypadku było całkowicie błędne)
Suzana
19

Vincent Marchetti ma dobry pomysł, ale str.decodejest przestarzały w Pythonie 3. W Pythonie 3 możesz wykonać ten sam test, używając str.encode:

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

Zwróć uwagę, że wyjątek, który chcesz złapać, również zmienił się z UnicodeDecodeErrorna UnicodeEncodeError.

dr
źródło
Dane wejściowe OP to bajtowanie ( byteswpisz w Pythonie 3, który nie ma .encode()metody). .decode()w @Vincent Marchetti odpowiedź jest poprawna .
jfs
@JFSebastian OP pyta „Jak sprawdzić, czy ciąg znaków w Pythonie jest w ASCII?” i nie określa bajtów vs ciągów Unicode. Dlaczego mówisz, że jego wkład jest bajtowaniem?
drs
1
spójrz na datę pytania: 'é'było wtedy testowaniem.
jfs
1
@JFSebastian, ok, dobrze, biorąc pod uwagę tę odpowiedź, odpowiada na to pytanie, jakby zadano ją dzisiaj, myślę, że nadal jest ważna i pomocna. Coraz mniej osób przyjedzie tutaj, szukając odpowiedzi, jakby uruchomili Python w 2008 roku
dr
2
Znalazłem to pytanie, kiedy szukałem rozwiązania dla python3 i szybkie czytanie tego pytania nie wzbudziło podejrzeń, że był to konkretny python 2. Ale ta odpowiedź była bardzo pomocna - głosowanie!
josch
17

Twoje pytanie jest nieprawidłowe; błąd, który widzisz, nie wynika z tego, jak zbudowałeś Pythona, ale z pomieszania ciągów bajtów i ciągów Unicode.

Ciągi bajtów (np. „Foo” lub „bar” w składni Pythona) są ciągami oktetów; numery od 0-255. Ciągi znaków Unicode (np. „Foo” lub „bar”) są ciągami punktów kodu Unicode; numery od 0-1112064. Wygląda jednak na to, że interesuje Cię znak é, który (w twoim terminalu) jest sekwencją wielobajtową reprezentującą pojedynczy znak.

Zamiast tego ord(u'é')spróbuj:

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

To pokazuje, która sekwencja punktów kodowych „é” reprezentuje. Może dać ci [233] lub może dać [101, 770].

Zamiast tego chr()odwrócić, istnieją unichr():

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

Ten znak może faktycznie być reprezentowany jako pojedynczy lub wiele „kodów” unicode, które same reprezentują grafem lub znaki. Może to być „ez ostrym akcentem (tj. Kod 233)” lub „e” (kod 101), a następnie „wyraźny akcent na poprzedni znak” (kod 770). Tak więc ten sam znak może być przedstawiony jako struktura danych Python u'e\u0301'lub u'\u00e9'.

Przez większość czasu nie powinieneś się tym przejmować, ale może to stanowić problem, jeśli iterujesz ciąg znaków Unicode, ponieważ iteracja działa według punktu kodowego, a nie według znaku rozkładającego się. Innymi słowy, len(u'e\u0301') == 2i len(u'\u00e9') == 1. Jeśli jest to dla Ciebie ważne, możesz przechodzić między formularzami złożonymi i rozłożonymi za pomocą unicodedata.normalize.

Glosariusz Unicode może być pomocnym przewodnikiem do zrozumienia niektórych z tych problemów, wskazując, w jaki sposób poszczególne terminy odnoszą się do różnych części reprezentacji tekstu, co jest o wiele bardziej skomplikowane, niż wielu programistów zdaje sobie sprawę.

Glif
źródło
3
„E” nie nie koniecznie reprezentują pojedynczy punkt kodu. Mogą to być dwa punkty kodowe (U + 0065 + U + 0301).
jfs
2
Każdy znak abstrakcyjny jest zawsze reprezentowany przez pojedynczy punkt kodowy. Jednak punkty kodowe mogą być kodowane do wielu bajtów, w zależności od schematu kodowania. tzn. „é” to dwa bajty w UTF-8 i UTF-16 oraz cztery bajty w UTF-32, ale w każdym przypadku jest to wciąż jeden punkt kodowy - U + 00E9.
Ben Blank
5
@Ben Puste: U + 0065 + 0301 i U punkty kodowe i do czynienia z „e”, które mogą również być reprezentowane przez U + 00E9. Google „łączy ostry akcent”.
jfs
JF ma rację, łącząc U + 0065 i U + 0301 w celu utworzenia „é”, ale nie jest to odwracalne funkino. Otrzymasz U + 00E9. Według wikipedii te złożone punkty kodowe są przydatne do kompatybilności wstecznej
Martin Konecny
1
@teehoo - Jest to funkcja odwracalna w tym sensie, że możesz ponownie znormalizować punkt kodowy reprezentujący złożony znak na sekwencję punktów kodowych reprezentujących ten sam złożony znak. W Pythonie możesz to zrobić tak: unicodedata.normalize ('NFD', u '\ xe9').
Glyph
10

Co powiesz na to?

import string

def isAscii(s):
    for c in s:
        if c not in string.ascii_letters:
            return False
    return True
Miya
źródło
5
To się nie powiedzie, jeśli ciąg zawiera znaki ASCII, które nie są literami. Dla ciebie koduje przykłady, które obejmują znak nowej linii, spację, kropkę, przecinek, podkreślenie i nawiasy.
florisla
9

Znalazłem to pytanie, próbując ustalić, jak używać / kodować / dekodować ciąg, którego kodowania nie byłem pewien (i jak uciec / przekonwertować znaki specjalne w tym ciągu).

Moim pierwszym krokiem powinno być sprawdzenie typu ciągu - nie zdawałem sobie sprawy, że mogę uzyskać dobre dane na temat jego formatowania z typów. Ta odpowiedź była bardzo pomocna i doprowadziła do prawdziwego źródła moich problemów.

Jeśli stajesz się niegrzeczny i wytrwały

UnicodeDecodeError: Kodek „ascii” nie może dekodować bajtu 0xc3 na pozycji 263: porządek poza zakresem (128)

szczególnie, gdy kodujesz, upewnij się, że nie próbujesz unicode () łańcucha, który już JEST unicode - z jakiegoś okropnego powodu pojawiają się błędy kodeku ascii. (Zobacz także przepis na Python Kitchen i samouczki docs Python, aby lepiej zrozumieć, jak okropne to może być.)

W końcu zdecydowałem, że to, co chciałem zrobić, to:

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

W debugowaniu pomocne było również ustawienie domyślnego kodowania w moim pliku na utf-8 (umieść to na początku pliku python):

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

Umożliwia to testowanie znaków specjalnych („àéç”) bez konieczności używania znaków ucieczki unicode (u '\ xe0 \ xe9 \ xe7').

>>> specials='àéç'
>>> specials.decode('latin-1').encode('ascii','xmlcharrefreplace')
'&#224;&#233;&#231;'
Max P. Magee
źródło
2

Możesz użyć biblioteki wyrażeń regularnych, która akceptuje standardową definicję Posix [[: ASCII:]].

Steve Moyer
źródło
2

Żądło ( strtyp) w Pythonie to seria bajtów. Nie ma sposobu, aby po prostu spojrzeć na ciąg znaków, czy ta seria bajtów reprezentuje ciąg znaków ascii, ciąg znaków o 8-bitowym zestawie znaków, takich jak ISO-8859-1, lub ciąg znaków zakodowany za pomocą UTF-8 lub UTF-16 lub cokolwiek innego .

Jeśli jednak znasz stosowane kodowanie, możesz decodenapisać str w ciągu znaków Unicode, a następnie użyć wyrażenia regularnego (lub pętli), aby sprawdzić, czy zawiera znaki spoza zakresu, którego dotyczy.

JacquesB
źródło
1

Podobnie jak odpowiedź @ RogerDahla, ale skuteczniej jest zwierać, negując klasę postaci i używając wyszukiwania zamiast find_alllub 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

Wyobrażam sobie, że wyrażenie regularne jest do tego dobrze zoptymalizowane.

płyty grzewcze
źródło
0
import re

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

Aby dołączyć pusty ciąg jako ASCII, zmienić +się *.

Roger Dahl
źródło
-1

Aby zapobiec awariom kodu, możesz użyć try-exceptmetody catchTypeErrors

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

Na przykład

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

źródło
To tryopakowanie jest całkowicie bezcelowe. Jeśli "¶"jest łańcuchem Unicode, to ord("¶")będzie działać, a jeśli nie jest (Python 2), for c in srozłoży go na bajty, więc ordbędzie nadal działać.
Ry-
-5

Używam następujących do ustalenia, czy ciąg jest ascii czy Unicode:

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

Następnie użyj bloku warunkowego, aby zdefiniować funkcję:

def is_ascii(input):
    if input.__class__.__name__ == "str":
        return True
    return False
mvknowles
źródło
4
-1 AARRGGHH to traktuje wszystkie znaki z ord (c) w zakresie (128, 256) jak ASCII !!!
John Machin
Nie działa Spróbuj zadzwonić co następuje: is_ascii(u'i am ascii'). Mimo że litery i spacje są zdecydowanie ASCII, to wciąż zwraca, Falseponieważ wymusiliśmy, aby ciąg był unicode.
jpmc26