Почему print("Hello") работает в Python 2
Одним из самых заметных отличий Python 3 от Python 2 является разжалование
print
из инструкции языка (statement) до обычной функции.
Python 3:
# простое использование
print("Hello")
# выводит: Hello
# переданные аргументы по умолчанию объединяются через пробел
print("Hello", "world!")
# выводит: Hello world!
# объединяющий символ можно заменить на свой
print("Pythonic", "attacks!", sep="_")
# выводит: Pythonic_attacks!
# можно заменить символ конца строки на пустую строку,
# чтобы продолжить выводить что-нибудь в той же строке
print("Hello ", end="")
print("world!")
# выводит: Hello world!
Python 2:
# простое использование
print "Hello"
# выводит: Hello
# перечисленные аргументы объединяются через пробел
print "Hello", "world!"
# выводит: Hello world!
# если нужен не пробел, а другой символ для объединения,
# то остаётся делать только вот так:
print "_".join(["Hello", "world!"])
# выводит: Hello_world!
# можно при помощи запятой в конце избежать вывода \n (символа конца строки)
# и продолжить печатать что-нибудь на той же строке:
print "Hello",
print "world!"
# выводит: Hello world!
# если сделать специальный импорт из “будущего”, то
# в Python 2.6+ `print` начинает работать как функция:
from __future__ import print_function
print("Hello", "world!")
# выводит: Hello world!
Обычно инструкциями в Python являются только очень базовые вещи,
такие как условия (if
), циклы (for
, while
),
возврат из функций (return
) и т.д.
Преобразование print
в функцию было сделано
во имя упрощения синтаксиса языка.
Инструкция print
для работы требовала значительного количества синтаксических
конструкций, например, для отсутствия символа переноса строки (,
) или
перенаправления вывода в файл (>>
).
Всё это можно было заставить играть по общим правилам
(и даже значительно расширить функционал) через позиционные и именованные
аргументы функций, вообще без какого-либо расширения синтаксиса языка.
Кроме того, print
как функцию можно использовать гораздо гибче — например,
фиксируя часть аргументов
(каррирование)
через
functools.partial
или использовать её в других функциях (передавать саму функцию как аргумент), типа
map()
или
filter()
.
Недавно в исходниках библиотеки
logging_tree
я увидел, что
print
используется как функция
без каких-либо импортов из будущего, при этом у библиотеки
заявлена поддержка версий Python,
начиная аж с 2.3:
print(build_description(node)[:-1])
Меня это удивило. Я даже
создал пулл-реквест,
будучи уверен, что нашёл ошибку. После того, как автор возразил,
что к нему не поступало никаких жалоб по поводу такого использования print
,
я решил всё-таки проверить, как это работает.
И к ещё большему моему удивлению, это и правда работало на всех версиях Python
2, до которых я смог дотянуться, включая 2.7, 2.6, 2.5, 2.4 и даже 2.3:
# как??
print("Hello")
# выводит: Hello
Немного поразмыслив и почитав, я осознал, где тут собака зарыта.
Хоть это и выглядит как вызов функции print()
, на самом деле это всё та же
старая добрая инструкция print
. Давайте разложим всё по полочкам:
# сначала отодвинем скобки от print, чтобы было нагляднее, что это инструкция:
print ("Hello")
# выводит: Hello
# в Python можно засунуть в скобки любое выражение, но пока оно там одно,
# то скобки не оказывают никакого влияния:
("Hello")
# получается просто строка: 'Hello'
# а если выражений несколько, или там есть запятая, то тогда уже
# создаётся кортеж:
("Hello",) # кортеж из одного элемента
("Hello", "world") # кортеж из двух элементов
# в нашем случае выражение только одно, поэтому скобки можно отбросить:
print "Hello"
# выводит: Hello
Как выяснилось, в Python 2 print
и правда можно так, что со стороны это будет
выглядеть как вызов функции, но для очень ограниченного множества случаев,
а именно — должен быть только один позиционный аргумент.
Такой код будет успешно выполняться и на Python 2 (благодаря описанному трюку),
и на Python 3 (по-настоящему).
Разница становится очевидной, если попытаться узнать тип:
# у инструкций нельзя запросить тип, это синтаксически невозможно
>>> type(print)
File "<stdin>", line 1
type(print)
^
SyntaxError: invalid syntax
# а у функций можно
>>> from __future__ import print_function
>>> type(print)
<type 'builtin_function_or_method'>
Трюки из той же серии:
# условие со скобками как в C:
>>> if(1 == 1):
... print("Equal!")
...
Equal!
# return как функция:
>>> def func():
... return(True)
...
>>> func()
True
Это код синтаксически верен, но просто обычно так не пишут. И правильно.
Заключение
Python 2.7 недавно получил свой последний релиз с исправлениями ошибок, и больше никогда не будет обновляться — даже новые найденные баги, связанные с безопасностью, не будут пофикшены. Хочется верить, что все заинтересованные уже обновились до Python 3, и никто больше активно не использует Python 2, в 2020-то году. К сожалению, вряд ли это так. Боюсь, что предсмертная агония Python 2 продлится ещё как минимум несколько лет. Поэтому знать про него хотя бы что-то всё равно полезно.
Благодаря этому случаю я узнал про странный трюк с print
и извлёк урок,
что нужно как минимум проверять свой фикс, прежде чем засылать куда-то
пулл-реквест.
Если понравилась статья, то подпишитесь на уведомления о новых постах в блоге, чтобы ничего не пропустить!
Дополнительное чтение
Обложка: Photo by mali maeder from Pexels