2012-04-12 20 views
25

Sto creando dinamicamente classi python e so che non tutti i caratteri sono validi in questo contesto.Caratteri validi in un nome classe python

C'è un metodo da qualche parte nella libreria di classi che posso usare per disinfettare una stringa di testo casuale, in modo che possa usarlo come nome di classe? O quello o una lista dei personaggi ammessi sarebbe di grande aiuto.


aggiunta per quanto riguarda gli scontri con i nomi di identificatore: Come @Ignacio rilevare nella risposta qui sotto, qualsiasi carattere che è valid as an identifier è un carattere valido in un nome di classe. E puoi anche usare un reserved word come nome di classe senza problemi. Ma c'è un problema. Se usi una parola riservata, non sarai in grado di rendere la classe accessibile come altre classi (non create dinamicamente) (ad esempio, facendo globals()[my_class.__name__] = my_class). La parola riservata avrà sempre la precedenza in questo caso.

+3

E il motivo del voto negativo è ...? Questa è una domanda fondamentale, ma comunque valida: +1. – EOL

+0

Cosa fa cercando di creare una classe con il nome 'None' o' __debug__'? Secondo i seguenti documenti, mi aspetto che sollevare un 'SyntaxError': https://docs.python.org/2/library/constants.html – ArtOfWarfare

risposta

33

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

identificatori (indicati anche come nomi) sono descritti dalle seguenti definizioni lessicali:

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

identificatori sono illimitate di lunghezza. Il caso è significativo.

+0

Perfetto, grazie –

+2

Ecco l 'espressione regolare usata per definire identificatori validi: 'identificatore :: = (lettera |" _ ") (lettera | cifra |" _ ") *'. (Forse si desidera aggiungere qualcosa a questo effetto per la vostra risposta in modo che gli utenti non devono cercare la pagina web?) –

+0

essere pedanti, che non è un regex @ vuoto-pointer - è una grammatica. – Qix

5

La cosa che rende questo interessante è che il primo carattere di un identificatore è speciale. Dopo il primo carattere, i numeri da "0" a "9" sono validi per gli identificatori, ma non devono essere il primo carattere.

Ecco una funzione che restituirà un identificatore valido dato qualsiasi stringa casuale di caratteri. Ecco come funziona:

Per prima cosa, usiamo itr = iter(seq) per ottenere un iteratore esplicito sull'input. Poi c'è un primo ciclo, che utilizza l'iteratore itr per esaminare i caratteri finché non trova un primo carattere valido per un identificatore. Quindi esce da quel ciclo e avvia il secondo ciclo, utilizzando lo stesso iteratore (che abbiamo chiamato itr) per il secondo ciclo. L'iteratore itr mantiene il nostro posto per noi; i personaggi il primo ciclo estratto dall'iteratore sono ancora andati quando viene eseguito il secondo ciclo.

Questo è un modo pulito e pitonico per gestire una sequenza in due modi diversi. Per un problema questo semplice, potremmo avere una variabile booleano che indica se non abbiamo ancora visto o no il primo carattere:

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 

non mi piace questa versione quasi quanto la prima versione. La gestione speciale per un personaggio è ora aggrovigliata nell'intero flusso di controllo e sarà più lenta della prima versione, poiché deve costantemente controllare il valore di saw_first_char. Ma questo è il modo in cui dovresti gestire il flusso del controllo nella maggior parte delle lingue! L'iteratore esplicito di Python è una funzionalità ingegnosa, e penso che renda questo codice molto migliore.

Il ciclo su un iteratore esplicito è veloce quanto consentire a Python di ottenere implicitamente un iteratore e l'iteratore esplicito ci consente di suddividere i loop che gestiscono le diverse regole per le diverse parti dell'identificatore. Quindi l'iteratore esplicito ci fornisce un codice più pulito che funziona anche più velocemente. Win/win.

+0

perché hai il 'ITR = iter (ss)' line ... non sarebbe 'per ch in seguenti:' avere lo stesso risultato, la stessa se non migliori prestazioni, e migliorare la leggibilità? – ArtOfWarfare

+0

@ArtOfWarfare Ho modificato la risposta per spiegare. – steveha

+0

Huh. Non l'ho mai visto prima. Terrò in mente quel disegno la prossima volta che anch'io ho bisogno di gestire una porzione prima e dopo di un'iterazione. – ArtOfWarfare

1

Questa è una vecchia questione, ormai, ma mi piacerebbe per aggiungere una risposta su come farlo in Python 3 dato che ho realizzato un'implementazione.

I caratteri consentiti sono documentati qui: https://docs.python.org/3/reference/lexical_analysis.html#identifiers. Includono molti personaggi speciali, tra cui la punteggiatura, il carattere di sottolineatura e un'intera serie di personaggi stranieri. Fortunatamente il modulo unicodedata può essere d'aiuto. Ecco la mia implementazione attuazione direttamente quello che dice la documentazione 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 

Questo codice è adattato da qui sotto CC0: https://github.com/Ghostkeeper/Luna/blob/d69624cd0dd5648aec2139054fae4d45b634da7e/plugins/data/enumerated/enumerated_type.py#L91. È stato ben testato

Problemi correlati