Подтвердить что ты не робот

Получение атрибутов целых чисел и поплавков

Я играл с интерпретатором Python (Python 3.2.3) и пробовал следующее:

>>> dir(1)

Это дало мне все атрибуты и методы объекта int. Затем я попытался:

>>> 1.__class__

Однако это заставило исключение:

File "<stdin>", line 1
1.__class__
          ^
SyntaxError: invalid syntax

Когда я пробовал то же самое с поплавком, я получил то, что ожидал:

>>> 2.0.__class__
<class 'float'> 

Почему литералы int и float ведут себя по-другому?

4b9b3361

Ответ 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 сделает предыдущий ввод признанным как один.

Ответ 2

Это проблема токенизации. . анализируется как начало дробной части числа с плавающей запятой.

Вы можете использовать

(1).__class__

чтобы избежать проблемы

Ответ 3

Потому что, если там есть . после числа, python думает, что вы создаете float. Когда он встречает что-то еще, что не является числом, оно будет вызывать ошибку.

Однако в float python не ожидает, что другой . будет частью значения, следовательно, результат! Оно работает.:)

Как получить атрибуты?

Вы можете легко обернуть его в круглые скобки. Например, см. Этот сеанс консоли:

>>> (1).__class__
<type 'int'>

Теперь Python знает, что вы не пытаетесь создать float, но ссылаетесь на сам int.

Бонус: добавление пробела после номера также работает.

>>> 1 .__class__
<type 'int'>

Кроме того, если вы хотите получить только __class__, type(1) сделает это за вас.

Надеюсь, это поможет!

Ответ 4

Или вы даже можете это сделать:

>>> getattr(1 , '__class__')
<type 'int'>

Ответ 5

Вам нужно скобки, чтобы окружить номер:

>>> (1).__class__
<type 'int'>
>>>

В противном случае Python видит . после номера и пытается интерпретировать все это как float.