Доступ к атрибутам на литералах работает для всех типов, кроме `int`; почему?
Проблема с вызовом метода __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
У меня возникло несколько вопросов:
- Почему вызов
something.__str__()
работает для всех типов, кромеint
? - Разве
123
не является объектом типаint
?
Помогите, пожалуйста, понять эту проблему!
4 ответ(ов)
Вы можете решить проблему, добавив круглые скобки:
(4).__str__()
Дело в том, что лексер воспринимает "4." как начало десятичного числа, ожидая, что после точки будет другая цифра.
Также этот вариант сработает:
x = 4
x.__str__()
В этом случае переменная x
уже является целым числом, и вызов метода __str__()
проходит без проблем.
Так вы думаете, что можете танцевать работать с плавающей запятой?
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"
Чтобы исправить проблему, добавьте пробел после 4
:
4 .__str__()
В противном случае лексер разберёт это выражение на токены "4."
, "__str__"
, "("
и ")"
, то есть первый токен будет интерпретирован как число с плавающей запятой. Лексер всегда пытается создать самый длинный возможный токен.
Да, 4..hex()
также является допустимым выражением. Оно возвращает строку '0x1.0000000000000p+2'
— но стоит отметить, что в этом случае результатом будет число с плавающей запятой.
Скачать видео с YouTube с помощью Python в определённую директорию
Генерация / синтез звука на Python?
Как проверить, написан ли код на Python 2.7 или 3 и выше?
Фиксация количества знаков после запятой с помощью f-строк
Преобразование байтового массива обратно в массив numpy