Почему print("Hello") работает в Python 2

Опубликовано: 24 мая 2020, Вс, автор: Андрей Семакин Обновлено: 24 мая 2020, Вс 4 минуты

Почему 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

тэги: python, print