0

Доступ к атрибутам на литералах работает для всех типов, кроме `int`; почему?

8

Проблема с вызовом метода __str__ у целых чисел в Python

Я прочитал, что в Python все является объектом, поэтому решил поэкспериментировать с различными типами данных и вызвать метод __str__ у них. Сначала это меня очень вдохновило, но затем возникло недоумение.

Вот что я попытался сделать:

>>> "hello world".__str__()
'hello world'
>>> [].__str__()
'[]'
>>> 3.14.__str__()
'3.14'
>>> 3..__str__()
'3.0'
>>> 123.__str__()
  File "<stdin>", line 1
    123.__str__()
              ^
SyntaxError: invalid syntax

У меня возникло несколько вопросов:

  1. Почему вызов something.__str__() работает для всех типов, кроме int?
  2. Разве 123 не является объектом типа int?

Помогите, пожалуйста, понять эту проблему!

4 ответ(ов)

0

Вы можете решить проблему, добавив круглые скобки:

(4).__str__()

Дело в том, что лексер воспринимает "4." как начало десятичного числа, ожидая, что после точки будет другая цифра.

Также этот вариант сработает:

x = 4
x.__str__()

В этом случае переменная x уже является целым числом, и вызов метода __str__() проходит без проблем.

0

Так вы думаете, что можете танцевать работать с плавающей запятой?

123 является объектом так же, как и 3.14. Проблема заключается в грамматических правилах языка; парсер считает, что мы пытаемся определить float, а не int с последующим вызовом метода.

Мы получим ожидаемое поведение, если обернем число в скобки, как показано ниже.

>>> (123).__str__()
'123'

Или если просто добавим пробел после 123:

>>> 123 .__str__()
'123'

Причина, по которой 123.__str__() не работает, заключается в том, что точка после 123 интерпретируется как десятичная точка некоторого частично объявленного числа с плавающей запятой.

>>> 123.__str__()
  File "", line 1
    123.__str__()
              ^
SyntaxError: invalid syntax

Парсер пытается интерпретировать __str__() как последовательность цифр, но, очевидно, он терпит неудачу — и мы получаем SyntaxError, по сути говорящее о том, что парсер наткнулся на что-то неожиданное.


Разъяснение

При попытке интерпретации 123.__str__() парсер Python мог бы использовать либо 3 символа и интерпретировать эти 3 символа как целое число, либо он мог бы использовать 4 символа и интерпретировать их как начало числа с плавающей запятой.

123.__str__()
^^^ - int
123.__str__()
^^^^- начало плавающей запятой

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

Когда он позже осознает, что __str__() никак не может быть интерпретирован как десятичные части числа с плавающей запятой, уже может быть слишком поздно; SyntaxError.

Примечание

 123 .__str__() # работает нормально

В приведенном выше фрагменте 123  (обратите внимание на пробел) должен интерпретироваться как целое число, поскольку никакое число не может содержать пробелов. Это означает, что оно семантически эквивалентно (123).__str__().

Примечание

 123..__str__() # работает нормально

Это тоже работает, потому что число может содержать не более одной десятичной точки, что делает его эквивалентным (123.).__str__().


Для языковых юристов

Этот раздел содержит лексическое определение соответствующих литералов.

floatnumber   ::=  pointfloat | exponentfloat
pointfloat    ::=  [intpart] fraction | intpart "."
exponentfloat ::=  (intpart | pointfloat) exponent
intpart       ::=  digit+
fraction      ::=  "." digit+
exponent      ::=  ("e" | "E") ["+" | "-"] digit+
integer        ::=  decimalinteger | octinteger | hexinteger | bininteger
decimalinteger ::=  nonzerodigit digit* | "0"+
nonzerodigit   ::=  "1"..."9"
digit          ::=  "0"..."9"
octinteger     ::=  "0" ("o" | "O") octdigit+
hexinteger     ::=  "0" ("x" | "X") hexdigit+
bininteger     ::=  "0" ("b" | "B") bindigit+
octdigit       ::=  "0"..."7"
hexdigit       ::=  digit | "a"..."f" | "A"..."F"
bindigit       ::=  "0" | "1"
0

Чтобы исправить проблему, добавьте пробел после 4:

4 .__str__()

В противном случае лексер разберёт это выражение на токены "4.", "__str__", "(" и ")", то есть первый токен будет интерпретирован как число с плавающей запятой. Лексер всегда пытается создать самый длинный возможный токен.

0

Да, 4..hex() также является допустимым выражением. Оно возвращает строку '0x1.0000000000000p+2' — но стоит отметить, что в этом случае результатом будет число с плавающей запятой.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь