Регулярное выражение для подтверждения того, является ли строка допустимым идентификатором Python?

У меня есть следующее определение идентификатора:

Identifier --> letter{ letter| digit}

По сути, у меня есть функция идентификатора, которая получает строку из файла и проверяет ее, чтобы убедиться, что это действительный идентификатор, как определено выше.

Я пробовал это:

if re.match('\w+(\w\d)?', i):     
  return True
else:
  return False

но когда я запускаю свою программу каждый раз, когда она встречает целое число, она считает, что это допустимый идентификатор.

Например

c = 0 ;

он печатает c как действительный идентификатор, что нормально, но он также печатает 0 как действительный идентификатор.

Что я здесь делаю неправильно?


person user682194    schedule 29.03.2011    source источник
comment
Вы знаете, что ваше определение не совпадает с определением Python, верно? Python также допускает подчеркивание.   -  person Tom Zych    schedule 29.03.2011
comment
Все ответы регулярных выражений не совсем правильные, см. ниже.   -  person Hatshepsut    schedule 19.03.2019


Ответы (6)


Вопрос был задан 10 лет назад, когда еще доминировал Python 2. Как показали многие комментарии за последнее десятилетие, мой ответ нуждался в серьезном обновлении, начиная с большого предупреждения:

Ни одно регулярное выражение не будет правильно соответствовать всем (и только) допустимым идентификаторам Python. Это не для Python 2, это не для Python 3.

Причины:

  • Как указал @JoeCondron, зарезервированные ключевые слова Python, такие как True, if, return, являются не допустимыми идентификаторами, и регулярные выражения сами по себе не могут справиться с этим, поэтому требуется дополнительная фильтрация.

  • Python 3 позволяет использовать буквы и цифры, отличные от ASCII, в идентификаторе, но категории букв и цифр Unicode, принимаемые лексическим синтаксическим анализатором для действительного идентификатора, не соответствуют тем же категориям \d, \w, \W в модуль re, как показано в контрпримере @martineau и очень подробно объяснено удивительным исследованием @Hatshepsut.

Хотя мы могли попытаться решить первую проблему, используя keyword.iskeyword(), как предложил @Alexander Huszagh, и обойти другую, ограничившись только идентификаторами ascii, зачем вообще использовать регулярное выражение ?

Как сказала Хатшепсут:

str.isidentifier() работает

Просто используйте его, проблема решена.


В соответствии с вопросом мой первоначальный ответ 2012 года представляет собой регулярное выражение, основанное на Python. 2 официальное определение идентификатора:

identifier ::=  (letter|"_") (letter | digit | "_")*

Что можно выразить регулярным выражением:

^[^\d\W]\w*\Z

Пример:

import re
identifier = re.compile(r"^[^\d\W]\w*\Z", re.UNICODE)

tests = [ "a", "a1", "_a1", "1a", "aa$%@%", "aa bb", "aa_bb", "aa\n" ]
for test in tests:
    result = re.match(identifier, test)
    print("%r\t= %s" % (test, (result is not None)))

Результат:

'a'      = True
'a1'     = True
'_a1'    = True
'1a'     = False
'aa$%@%' = False
'aa bb'  = False
'aa_bb'  = True
'aa\n'   = False
person MestreLion    schedule 13.04.2012
comment
Возможно, стоит упомянуть, что это соответствует ключевым словам, таким как True, return и т. д. Я не предлагаю изменить регулярное выражение, но просто OP может захотеть иметь это в виду. - person JoeCondron; 08.06.2016
comment
@JoeCondron Это также очень легко сделать, поскольку Python содержит функцию keyword.iskeyword, которая является просто оболочкой для списка ключевых слов frostset. - person Alexander Huszagh; 01.01.2018
comment
По крайней мере, в Python 3.6 это не работает для строки Unicode '℘᧚', несмотря на то, что является действительным идентификатором в Python 3 (а не ключевым словом). - person martineau; 19.06.2018

str.isidentifier() работает. Ответы регулярных выражений неправильно не соответствуют некоторым допустимым идентификаторам python и неправильно соответствуют некоторым недопустимым.

str.isidentifier() Возвращает true, если строка является допустимым идентификатором в соответствии с определением языка, раздел Идентификаторы и ключевые слова.

Используйте keyword.iskeyword() для проверки зарезервированных идентификаторов, таких как def и class.

Комментарий @ martineau дает пример '℘᧚', где решения регулярных выражений терпят неудачу.

>>> '℘᧚'.isidentifier()
True
>>> import re
>>> bool(re.search(r'^[^\d\W]\w*\Z', '℘᧚'))
False

Почему это происходит?

Давайте определим наборы кодовых точек, которые соответствуют заданному регулярному выражению, и набор, который соответствует str.isidentifier.

import re
import unicodedata

chars = {chr(i) for i in range(0x10ffff) if re.fullmatch(r'^[^\d\W]\w*\Z', chr(i))}
identifiers = {chr(i) for i in range(0x10ffff) if chr(i).isidentifier()}

Сколько совпадений регулярных выражений не являются идентификаторами?

In [26]: len(chars - identifiers)                                                                                                               
Out[26]: 698

Сколько идентификаторов не соответствует регулярному выражению?

In [27]: len(identifiers - chars)                                                                                                               
Out[27]: 4

Интересно - какие?

In [37]: {(c, unicodedata.name(c), unicodedata.category(c)) for c in identifiers - chars}                                                       
Out[37]: 
set([
    ('\u1885', 'MONGOLIAN LETTER ALI GALI BALUDA', 'Mn'),
    ('\u1886', 'MONGOLIAN LETTER ALI GALI THREE BALUDA', 'Mn'),
    ('℘', 'SCRIPT CAPITAL P', 'Sm'),
    ('℮', 'ESTIMATED SYMBOL', 'So'),
])

Чем отличаются эти два набора?

Они имеют разные значения Unicode «Общая категория».

In [31]: {unicodedata.category(c) for c in chars - identifiers}                                                                                 
Out[31]: set(['Lm', 'Lo', 'No'])

Из википедии это Letter, modifier; Letter, other; Number, other. Это согласуется с документами, поскольку \d — это только десятичные цифры:

\d Соответствует любой десятичной цифре Unicode (то есть любому символу в категории символов Unicode [Nd])

А как насчет другого пути?

In [32]: {unicodedata.category(c) for c in identifiers - chars}                                                                                 
Out[32]: set(['Mn', 'Sm', 'So'])

Это Mark, nonspacing; Symbol, math; Symbol, other.

Где это все задокументировано?

Где это реализовано?

https://github.com/python/cpython/commit/47383403a0a11259acb640406a8efc38981d2255

Я все еще хочу регулярное выражение

Посмотрите модуль regex в PyPI.

Эта реализация регулярного выражения обратно совместима со стандартным модулем «re», но предлагает дополнительные функции.

Он включает фильтры для «Общей категории».

person Hatshepsut    schedule 06.01.2019
comment
Можете ли вы привести пример, когда это работает, но регулярное выражение не работает? - person martineau; 18.03.2019
comment
Действительно, вы правы, но это меня удивляет, потому что документация re, похоже, указывает на то, что она поддерживает строки Unicode (даже без флага re.UNICODE в Python 3.x). - person martineau; 18.03.2019
comment
@martineau Из любопытства, как вы наткнулись на это конкретное? - person Hatshepsut; 19.03.2019
comment
Я разрабатывал регулярное выражение для распознавания имен специальных методов Python, т. е. тех, которые начинаются и заканчиваются двумя символами подчеркивания. __ — он же дандер-имена, поэтому искал на этом сайте общий, который распознавал бы любой допустимый идентификатор. Думаю, мне придется отказаться от использования регулярного выражения re... на самом деле, теперь я подозреваю, что ограничение/ошибка модуля может быть причиной того, что метод stringisidentifier был добавлен в Python 3. - person martineau; 19.03.2019
comment
... после вашего недавнего обновления: Да, я знаю, что есть сторонние библиотеки регулярных выражений, но предпочел бы ограничить то, что я делаю, стандартной библиотекой, и поэтому буду использовать комбинацию str.isidentifer() вместе с str.startswith() & str.endswith() для обнаружения имен дандеров (код не особенно критичен к скорости). Спасибо за ответы (и обновления ответов). - person martineau; 19.03.2019
comment
Какое удивительное исследование и ответ! Заставил меня полностью переделать свой собственный. Я не знал о str.isidentifier() и не был уверен, что он был доступен в 2012 году. Но в 2021 году вообще не имеет смысла использовать регулярное выражение. - person MestreLion; 05.05.2021

Для Python 3 вам необходимо обрабатывать буквы и цифры Unicode. Так что, если это беспокоит, вы должны согласиться с этим:

re_ident = re.compile(r"^[^\d\W]\w*$", re.UNICODE)

[^\d\W] соответствует символу, который не является цифрой и не «не буквенно-цифровым», что переводится как «символ, который является буквой или символом подчеркивания».

person Tim Pietzcker    schedule 29.03.2011
comment
Почти так... но не совсем... он не будет работать с однобуквенными идентификаторами a, а также допускает aa@#$% в качестве допустимого идентификатора. - person MestreLion; 13.04.2012
comment
@MestreLion: Спасибо, конечно, ты прав. Я отредактировал свой ответ. - person Tim Pietzcker; 13.04.2012

\w соответствует цифрам и символам. Попробуйте ^[_a-zA-Z]\w*$

person Joe    schedule 29.03.2011
comment
Будьте осторожны, Python 3 позволяет использовать в своих идентификаторах все буквы и цифры Unicode. - person Tim Pietzcker; 29.03.2011
comment
Должно ли это быть [_a-zA-Z]\w*, поскольку вы хотите сопоставить 0 или более после начального символа? - person William Stein; 03.06.2011
comment
Это будет соответствовать $%@#% в качестве действительного идентификатора. - person MestreLion; 13.04.2012

Работает как шарм: r'[^\d\W][\w\d]+'

person acesaif    schedule 27.12.2018

Вопрос касается регулярных выражений, поэтому мой ответ может выглядеть не по теме. Дело в том, что регулярное выражение — это просто неправильный подход.

Заинтересованы в проблемных персонажах?

Используя str.isidentifier, можно выполнять проверку посимвольно, добавляя к ним префикс, скажем, подчеркивание, чтобы избежать ложных срабатываний, таких как цифры и т. д. Как имя может быть действительным, если один из его (префиксных) компонентов не является (?) Например.

def checker(str_: str) -> 'set[str]':
    return {
        c for i, c in enumerate(str_)
        if not (f'_{c}' if i else c).isidentifier()
    }
>>> checker('℘3᧚₂')
{'₂'}

Какое решение касается неавторизованных первых символов, таких как цифры или, например. . Видеть

>>> checker('᧚℘3₂')
{'₂', '᧚'}
>>> checker('3᧚℘₂')
{'3', '₂'}
>>> checker("a$%@#%\n")
{'@', '#', '\n', '$', '%'}

Нужно улучшить, поскольку он не проверяет ни зарезервированные имена, ни ничего не говорит о том, почему иногда проблематичен, тогда как всегда... но вот мой подход без регулярных выражений.


Мой ответ в ваших терминах:

if not checker(i):
    return True
else:
    return False

которые могут быть заключены в

return not checker(i)
person keepAlive    schedule 08.05.2021