Ответ 1
Вероятно, это следствие используемого алгоритма синтаксического анализа. Простая ментальная модель состоит в том, что токенизатор пытается сопоставить все шаблоны маркеров и распознает найденное длинное совпадение. На более низком уровне токенизатор работает по-символу и принимает решение, основанное только на текущем состоянии и символе ввода - не должно быть никакого возврата или повторного чтения ввода.
После объединения шаблонов с общими префиксами - в этом случае шаблон для int
литералов и неотъемлемая часть шаблона float
литералов - то, что происходит в токенизаторе, состоит в том, что он:
- Считывает
1
и вводит состояние, которое указывает "чтение либо литераfloat
, либоint
" - Считывает
.
и переходит в состояние "чтение литералаfloat
" - Читает
_
, который не может быть частью литералаfloat
. Парсер испускает1.
как токенfloat
. - Выполняет синтаксический анализ, начиная с
_
, и в итоге выдает__class__
как токен идентификатора.
Помимо этого: Этот подход к токенированию также является причиной того, что общие языки имеют ограничения синтаксиса, которые у них есть. Например. идентификаторы содержат буквы, цифры и символы подчеркивания, но не могут начинаться с цифра. Если это разрешено,
123abc
может быть идентификатор или целое число123
, за которым следует идентификаторabc
.Лексоподобный токенизатор распознает это как первое, поскольку он ведет к самому длинному единственному токену, но никто не любит держать детали как это в их голове при попытке прочитать код. Или при попытке пишите и отлаживайте токенизатор в этом отношении.
Затем синтаксический анализатор пытается обработать поток токенов:
<FloatLiteral: '1.'> <Identifier: '__class__'>
В Python литерал, за которым напрямую следует идентификатор - без оператора между токенами - не имеет смысла, поэтому парсер берет. Это также означает, что причина, по которой Python будет жаловаться на 123abc
недействительным синтаксисом, не является ошибкой токенизатора, "символ a
недопустим в целочисленном литерале", но ошибка парсера "идентификатор abc
не может непосредственно следует за целым литералом 123
"
Причина, по которой токенизатор не может распознать 1
как литерал int
, состоит в том, что символ, который делает его покидать, float
-or- int
, определяет, что он просто читал. Если он .
, это было начало литерала float
, который может продолжаться впоследствии. Если это что-то еще, это был полный литеральный токен int
.
Невозможно, чтобы токенизатор "вернулся" и перечитал предыдущий ввод как что-то еще. Фактически, токенизатор находится на слишком низком уровне, чтобы заботиться о том, что такое "доступ к атрибуту" и обрабатывать такие неоднозначности.
Теперь ваш второй пример действителен, потому что токенизатор знает, что литерал float
может содержать только один .
. Точнее: первый .
делает переход из состояния float
-or- int
в состояние float
. В этом состоянии он ожидает только цифры (или E
для научной/технической нотации, j
для сложных чисел...), чтобы продолжить литерал float
. Первый символ, который не является цифрой и т.д. (Т. Е. .
), определенно больше не является частью литерала float
, и токенизатор может испускать законченный токен. Таким образом поток токенов для вашего второго примера будет выглядеть следующим образом:
<FloatLiteral: '1.'> <Operator: '.'> <Identifier: '__class__'>
Что, конечно, синтаксический анализатор распознает как действительный Python. Теперь мы также знаем достаточно, почему предлагаемые обходные пути помогают. В Python разделение жетонов с пробелом необязательно - в отличие от, скажем, в Lisp. И наоборот, пробелы имеют отдельные токены. (То есть никакие токены, кроме string
литералов, могут содержать пробелы, они просто пропускаются между токенами.) Таким образом, код:
1 .__class__
всегда обозначается как
<IntLiteral: '1'> <Operator: '.'> <Identifier: '__class__'>
И поскольку закрывающая скобка не может появиться в литерале int
, это:
(1).__class__
читается следующим образом:
<Operator: '('> <IntLiteral: '1'> <Operator: ')'> <Operator: '.'> <Identifier: '__class__'>
Из сказанного следует, что, забавно, справедливо также следующее:
1..__class__ # => <type 'float'>
Десятичная часть литерала float
не является обязательной, а второй .
read сделает предыдущий ввод признанным как один.