Действительные символы в имени класса python
Я динамически создаю классы python, и я знаю, что не все символы действительны в этом контексте.
Есть ли метод где-то в библиотеке классов, который я могу использовать для дезинфекции случайной текстовой строки, чтобы я мог использовать ее как имя класса? Либо это, либо список допустимых символов будет хорошей помощью.
Дополнение о конфликтах с именами идентификаторов:. Как @Ignacio указал в ответе ниже, любой символ, который действителен как идентификатор является допустимым символом в имени класса. И вы даже можете без проблем использовать зарезервированное слово как имя класса. Но есть улов. Если вы используете зарезервированное слово, вы не сможете сделать класс доступным, как другие (нединамически созданные) классы (например, выполнив globals()[my_class.__name__] = my_class
). В этом случае зарезервированное слово всегда будет иметь приоритет.
Ответы
Ответ 1
Справочник по языку Python, §2.3, "Идентификаторы и ключевые слова"
Идентификаторы (также называемые именами) описываются следующими лексическими определениями:
identifier ::= (letter|"_") (letter | digit | "_")*
letter ::= lowercase | uppercase
lowercase ::= "a"..."z"
uppercase ::= "A"..."Z"
digit ::= "0"..."9"
Идентификаторы неограниченны по длине. Дело значимо.
Ответ 2
По Справочник по языку Python, § 2.3, "Идентификаторы и ключевые слова" , действительный идентификатор Python определяется как:
(letter|"_") (letter | digit | "_")*
Или в регулярном выражении:
[a-zA-Z_][a-zA-Z0-9_]*
Ответ 3
Вещь, которая делает это интересным, заключается в том, что первый символ идентификатора является особым. После первого символа цифры от 0 до 9 соответствуют действительным для идентификаторов, но они не должны быть первым символом.
Здесь функция, которая вернет действительный идентификатор, заданный любой случайной строкой символов. Вот как это работает:
Сначала мы используем itr = iter(seq)
, чтобы получить явный итератор на входе. Тогда есть первый цикл, который использует итератор itr
для просмотра символов до тех пор, пока не найдет допустимый первый символ для идентификатора. Затем он вырывается из этого цикла и запускает второй цикл, используя тот же самый итератор (который мы назвали itr
) для второго цикла. Итератор itr
хранит наше место для нас; символы, которые первый цикл вытащил из итератора, все еще исчезают, когда выполняется второй цикл.
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))
Это чистый и путинский способ обработки последовательности двумя разными способами. Для проблемы это просто, мы могли бы просто иметь логическую переменную, которая указывает, видели ли мы еще один символ или нет:
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
Мне не нравится эта версия почти так же, как и первая версия. Специальная обработка для одного символа теперь запутана во всем потоке управления, и это будет медленнее, чем первая версия, так как она должна постоянно проверять значение saw_first_char
. Но именно так вы должны были бы управлять потоком управления на большинстве языков! Явный итератор Python - отличная функция, и я думаю, что этот код намного лучше.
Looping на явном итераторе выполняется так же быстро, как позволить Python неявно получить для вас итератор, а явный итератор позволяет нам разделить циклы, обрабатывающие разные правила для разных частей идентификатора. Таким образом, явный итератор дает нам более чистый код, который также работает быстрее. Win/выигрыш.
Ответ 4
Это старый вопрос, но я хотел бы добавить ответ о том, как это сделать в Python 3, поскольку я сделал реализацию.
Разрешенные символы описаны здесь: https://docs.python.org/3/reference/lexical_analysis.html#identifiers. Они включают в себя довольно много специальных персонажей, включая знаки препинания, подчеркивания и целое количество иностранных персонажей. К счастью, модуль unicodedata
может помочь. Здесь моя реализация напрямую реализует то, что говорит документация 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
Этот код адаптирован здесь под CC0: https://github.com/Ghostkeeper/Luna/blob/d69624cd0dd5648aec2139054fae4d45b634da7e/plugins/data/enumerated/enumerated_type.py#L91. Он был хорошо протестирован.