2012-04-12 64 views
25

Dynamicznie tworzę klasy Pythona i wiem, że nie wszystkie znaki są poprawne w tym kontekście.Poprawne znaki w klasie Pythona

Czy istnieje jakaś metoda w bibliotece klas, której można użyć do odkażenia losowego ciągu tekstowego, aby można go było użyć jako nazwy klasy? Albo ta lista dozwolonych znaków byłaby dobrą pomocą.


Dodawanie dotyczące starć z nazwami Identyfikator: Jak @Ignacio wskazano w poniższej odpowiedzi, każda postać, która jest valid as an identifier jest ważny znak w nazwie klasy. Możesz nawet użyć nazwy reserved word jako nazwy klasy bez żadnych problemów. Ale jest haczyk. Jeśli użyjesz zarezerwowanego słowa, nie będziesz w stanie uczynić klasy dostępną jak inne (nie utworzone dynamicznie) klasy (np. Robiąc globals()[my_class.__name__] = my_class). Zastrzeżone słowo zawsze będzie miało pierwszeństwo w takim przypadku.

+3

A powodem odrzucenia głosowania jest ...? To podstawowe pytanie, ale ważne: +1. – EOL

+0

Co robi próba utworzenia klasy o nazwie "Brak" lub "__debug__"? Zgodnie z poniższymi dokumentami, spodziewam się, że podniesie to "SyntaxError": https://docs.python.org/2/library/constants.html – ArtOfWarfare

Odpowiedz

33

Python Language Reference, §2.3, "Identifiers and keywords"

identyfikatory (określane również jako nazwami) są przedstawione przez następujące definicje słownikowych:

identifier ::= (letter|"_") (letter | digit | "_")* 
letter  ::= lowercase | uppercase 
lowercase ::= "a"..."z" 
uppercase ::= "A"..."Z" 
digit  ::= "0"..."9" 

identyfikatory nieograniczoną długość. Sprawa jest znacząca.

+0

Idealne, dzięki –

+2

Oto wyrażenie regularne używane do definiowania poprawnych identyfikatorów: 'identyfikator :: = (litera |" _ ") (litera | cyfra |" _ ") *'. (Być może chciałbyś dodać coś do tego efektu do swojej odpowiedzi, aby użytkownicy nie musieli przeszukiwać strony?) –

+0

Aby być pedantycznym, nie jest to regex @ void-pointer - to gramatyka. – Qix

5

Rzeczą, która sprawia, że ​​jest to interesujące, jest to, że pierwszy znak identyfikatora jest wyjątkowy. Po pierwszym znaku cyfry od "0" do "9" są ważne dla identyfikatorów, ale nie mogą być pierwszym znakiem.

Oto funkcja, która zwróci prawidłowy identyfikator podany w dowolnym losowym ciągu znaków. Oto, jak to działa:

Najpierw używamy , aby uzyskać jawny iterator na wejściu. Następnie jest pierwsza pętla, która używa iteratora itr do przeglądania znaków, dopóki nie znajdzie poprawnego pierwszego znaku dla identyfikatora. Następnie wychodzi z tej pętli i uruchamia drugą pętlę, , używając tego samego iteratora (który nazwaliśmy itr) dla drugiej pętli. Iterator itr zatrzymuje dla nas swoje miejsce; znaki, które pierwsza pętla wyjęta z iteratora są nadal nieobecne, gdy działa druga pętla.

def gen_valid_identifier(seq): 
    # get an iterator 
    itr = iter(seq) 
    # pull characters until we get a legal one for first in identifer 
    for ch in itr: 
     if ch == '_' or ch.isalpha(): 
      yield ch 
      break 
    # pull remaining characters and yield legal ones for identifier 
    for ch in itr: 
     if ch == '_' or ch.isalpha() or ch.isdigit(): 
      yield ch 

def sanitize_identifier(name): 
    return ''.join(gen_valid_identifier(name)) 

To jest czysty i Pythoniczny sposób obsługi sekwencji na dwa różne sposoby. Na problem ten prosty, możemy tylko mieć zmienną logiczną wskazującą, czy widzieliśmy jeszcze, czy nie pierwszy znak:

def gen_valid_identifier(seq): 
    saw_first_char = False 
    for ch in seq: 
     if not saw_first_char and (ch == '_' or ch.isalpha()): 
      saw_first_char = True 
      yield ch 
     elif saw_first_char and (ch == '_' or ch.isalpha() or ch.isdigit()): 
      yield ch 

Nie lubię tej wersji prawie tak samo jak w pierwszej wersji. Specjalna obsługa jednego znaku jest teraz zaplątana w cały proces sterowania, a to będzie wolniejsze niż pierwsza wersja, ponieważ musi stale sprawdzać wartość saw_first_char. Ale w ten sposób musiałbyś kontrolować przepływ w większości języków! Intuicyjny iterator Pythona to fajna funkcja i myślę, że czyni ten kod znacznie lepszym.

Zapętlanie w jawnym iteratorze jest równie szybkie, jak zezwolenie Pythonowi na implicite uzyskanie iteratora dla ciebie, a jawny iterator pozwala nam dzielić pętle, które obsługują różne reguły dla różnych części identyfikatora. Więc jawny iterator daje nam czystszy kod, który działa również szybciej. Wygraj/wygraj.

+0

Dlaczego linia 'itr = iter (seq)' nie ma "dla ch in seq:' ma dokładnie takie same wyniki, taką samą, jeśli nie lepszą wydajność, i lepszą czytelność? – ArtOfWarfare

+0

@ArtOfWarfare Edytowałem odpowiedź, aby wyjaśnić. – steveha

+0

Huh. Nigdy wcześniej tego nie widziałem. Będę pamiętać o tym projekcie następnym razem, gdy podobnie będę potrzebował obsługi przed i po części iteracji. – ArtOfWarfare

1

Jest to stary pytanie teraz, ale chciałbym aby dodać odpowiedź na to, jak to zrobić w Pythonie 3, ponieważ wykonałem implementację.

Dozwolone znaki są tutaj udokumentowane: https://docs.python.org/3/reference/lexical_analysis.html#identifiers. Obejmują one wiele znaków specjalnych, w tym znaki interpunkcyjne, podkreślenia i całą masę obcych postaci. Na szczęście może pomóc moduł unicodedata. Oto moja realizacja wdrożenia bezpośrednio co mówi dokumentacja Python:

import unicodedata 

def is_valid_name(name): 
    if not _is_id_start(name[0]): 
     return False 
    for character in name[1:]: 
     if not _is_id_continue(character): 
      return False 
    return True #All characters are allowed. 

_allowed_id_continue_categories = {"Ll", "Lm", "Lo", "Lt", "Lu", "Mc", "Mn", "Nd", "Nl", "Pc"} 
_allowed_id_continue_characters = {"_", "\u00B7", "\u0387", "\u1369", "\u136A", "\u136B", "\u136C", "\u136D", "\u136E", "\u136F", "\u1370", "\u1371", "\u19DA", "\u2118", "\u212E", "\u309B", "\u309C"} 
_allowed_id_start_categories = {"Ll", "Lm", "Lo", "Lt", "Lu", "Nl"} 
_allowed_id_start_characters = {"_", "\u2118", "\u212E", "\u309B", "\u309C"} 

def _is_id_start(character): 
    return unicodedata.category(character) in _allowed_id_start_categories or character in _allowed_id_start_categories or unicodedata.category(unicodedata.normalize("NFKC", character)) in _allowed_id_start_categories or unicodedata.normalize("NFKC", character) in _allowed_id_start_characters 

def _is_id_continue(character): 
    return unicodedata.category(character) in _allowed_id_continue_categories or character in _allowed_id_continue_characters or unicodedata.category(unicodedata.normalize("NFKC", character)) in _allowed_id_continue_categories or unicodedata.normalize("NFKC", character) in _allowed_id_continue_characters 

Kod ten jest przystosowany stąd pod CC0: https://github.com/Ghostkeeper/Luna/blob/d69624cd0dd5648aec2139054fae4d45b634da7e/plugins/data/enumerated/enumerated_type.py#L91. Zostało dobrze przetestowane.

Powiązane problemy