В истории любого популярного языка программирования есть свои "скелеты в шкафу", свои легенды и свои войны. Но немногие события могут сравниться по драматизму и последствиям с переходом Python на третью версию. Это был не просто апдейт, это был Великий Раскол, который длился больше десяти лет, разделил сообщество и оставил глубокие шрамы на теле экосистемы.
Если вы начали свой путь в Python в последние 5-6 лет, то, скорее всего, вы никогда не видели Python 2 в продакшене. Для вас это давно забытая история. Но для тех, кто пережил этот переход, это до сих пор вызывает "вьетнамские флешбеки". Сотни тысяч строк кода, тонны библиотек, годы страданий, споров и переписывания. Почему же это произошло? Зачем было ломать то, что и так прекрасно работало? Давайте погрузимся в эту сагу.
Начнем с главного: Python 2 не был плохим языком. Он был и остается одним из самых успешных проектов в истории ПО. Но, как и любая система, созданная эволюционным путем, он накопил немало "исторического багажа" — архитектурных решений, которые со временем стали либо нелогичными, либо откровенно тормозящими развитие.
К концу 2000-х годов, когда разработка Python 3 началась всерьез (первая альфа-версия вышла в 2008 году), стало ясно: дальнейшее развитие языка в рамках Python 2.x без нарушения обратной совместимости было невозможно. Некоторые фундаментальные проблемы были настолько глубоко закопаны, что требовали хирургического вмешательства, а не косметического ремонта.
Целью Python 3 было упростить и очистить язык, сделать его более логичным, предсказуемым и пригодным для будущих вызовов, особенно в мире, где доминирует Unicode. Это был долгосрочный взгляд, который требовал смелого шага: разорвать обратную совместимость.
Давайте посмотрим на конкретные изменения, которые вызвали наибольшую боль и стали причиной того, что тысячи проектов не могли просто так "обновиться".
print
Пожалуй, самое узнаваемое изменение и главный маркер различия между Python 2 и 3.
В Python 2 print
был инструкцией:
# Python 2
print "Hello, Schism!"
print "The answer is", 42
Это означало, что у print
были свои правила синтаксиса, он не мог быть частью выражения, не мог быть присвоен переменной и не всегда вел себя как обычная функция.
В Python 3 print
стал полноценной функцией:
# Python 3
print("Hello, Future!")
print("The answer is", 42)
Это изменение принесло гибкость (можно было передавать print
как аргумент, использовать sep
для разделителя, end
для завершения строки и file
для вывода в файл), но для миллионов строк кода это означало необходимость добавить скобки. Казалось бы, мелочь, но умноженная на масштабы, это была колоссальная работа.
Еще одно фундаментальное изменение, которое ломало логику математических операций, особенно в старых численных алгоритмах.
В Python 2:
# Python 2
result_int = 5 / 2 # result_int будет 2 (целочисленное деление)
result_float = 5.0 / 2 # result_float будет 2.5 (деление с плавающей точкой)
В Python 3:
# Python 3
result_float = 5 / 2 # result_float будет 2.5 (всегда деление с плавающей точкой)
result_int = 5 // 2 # result_int будет 2 (целочисленное деление)
Это изменение сделало /
интуитивно понятным (как в большинстве языков и на калькуляторе), но требовало внимательной перепроверки всех вычислений, чтобы избежать потери точности или неожиданных результатов.
str
vs bytes
Вот тут и начинается настоящая боль. Это не просто синтаксис, это фундаментальное изменение в работе со строками, которое переворачивало с ног на голову всю работу с текстом и бинарными данными.
В Python 2:
str
был набором байтов.unicode
был набором символов.str
+ unicode
могло сработать "случайно" или упасть с UnicodeDecodeError
.# Python 2
s1 = "привет" # Это str (байты)
s2 = u"привет" # Это unicode (символы)
print type(s1) # <type 'str'>
print type(s2) # <type 'unicode'>
# А теперь веселье:
# При работе с файлами, сетью, БД - бесконечные проблемы кодирования/декодирования
# Часто приходилось вручную .decode() и .encode()
В Python 3:
str
теперь всегда представляет последовательность Unicode-символов.bytes
, который представляет последовательность байтов.str
и bytes
несовместимы. Их нельзя просто так складывать или сравнивать. Для преобразования необходимо явно encode()
(из str
в bytes
) или decode()
(из bytes
в str
), указывая кодировку.# Python 3
s = "привет" # Это str (Unicode)
b = s.encode('utf-8') # Это bytes (закодированные байты)
print(type(s)) # <class 'str'>
print(type(b)) # <class 'bytes'>
# print(s + b) # Это вызовет TypeError!
# print(b.decode('utf-8') == s) # True
Это изменение было абсолютно необходимым для работы в современном мире, но оно потребовало полного переосмысления обработки текстовых данных во всех приложениях. Десятки тысяч строк кода, работающих с I/O, сетью, файлами, базами данных, требовали кропотливого пересмотра.
range
/ xrange
В Python 2 многие функции, возвращающие списки (например, range()
, map()
, filter()
, dict.keys()
, dict.items()
), генерировали их целиком в памяти. Для небольших коллекций это не проблема, но для больших объемов данных это приводило к колоссальному расходу памяти и замедлению работы.
Python 2 предлагал "ленивые" (iterator-based) версии некоторых из них: xrange()
, itertools.imap()
и т.д. Но это создавало дублирование и путаницу.
В Python 3 всё стало консистентно: range()
, map()
, filter()
и методы словарей (.keys()
, .items()
, .values()
) по умолчанию стали возвращать итераторы.
# Python 2
my_list_2 = range(1000000) # Создаст список из миллиона элементов в памяти
import sys
print sys.getsizeof(my_list_2) # Например, 8000000+ байт
my_xrange_2 = xrange(1000000) # Создаст итератор
print sys.getsizeof(my_xrange_2) # Гораздо меньше, например, 40 байт (размер объекта-итератора)
# Python 3
my_range_3 = range(1000000) # Создаст итератор (аналог xrange в Python 2)
print(sys.getsizeof(my_range_3)) # Гораздо меньше (48 байт на 64-бит системе)
# Если нужен список:
my_list_3 = list(range(1000000))
Это изменение было огромным шагом вперед для производительности и эффективности использования памяти, но оно могло сломать код, который полагался на то, что range()
возвращает список и позволял напрямую обращаться к элементам по индексу или выполнять операции, специфичные для списков.
next()
для итераторовЕще одно небольшое, но заметное изменение в работе с итераторами.
В Python 2 для получения следующего элемента из итератора использовалась функция next()
или метод iterator.next()
:
# Python 2
my_iterator = iter([1, 2, 3])
print my_iterator.next() # 1
print next(my_iterator) # 2 (работает только в Python 2.6+)
В Python 3 был стандартизирован только встроенный next()
:
# Python 3
my_iterator = iter([1, 2, 3])
print(next(my_iterator)) # 1
# print(my_iterator.next()) # AttributeError: 'list_iterator' object has no attribute 'next'
Это изменение упростило API, но требовало переписать все вызовы .next()
на next()
.
Множество модулей и функций были переименованы или перемещены, чтобы сделать стандартную библиотеку более логичной и последовательной.
Например:
urllib
, urllib2
, urlparse
в Python 2 превратились в унифицированный urllib
в Python 3 (с подмодулями urllib.request
, urllib.parse
и т.д.).StringIO
и cStringIO
стали io.StringIO
.pickle
стал более строгим и безопасным.raw_input()
стал input()
, а input()
из Python 2 (который выполнял введенный код!) был удален.Эти изменения, хотя и логичные, требовали детального аудита всего импортируемого и используемого кода.
Учитывая масштаб сломанной совместимости, реакция сообщества была предсказуемой: ожесточенное сопротивление.
Этот период "двуязычия" был болезненным. Разработчикам приходилось поддерживать две версии кода, библиотеки выпускали версии для Python 2 и Python 3, а новички не понимали, какую версию языка им учить. Фактически, сообщество разделилось на два лагеря, каждый со своими аргументами и фанатиками.
В центре этой бури оказался Гвидо ван Россум, создатель Python и его "Великодушный пожизненный диктатор" (Benevolent Dictator For Life, BDFL). Роль BDFL подразумевала, что Гвидо имел последнее слово по любым спорным вопросам развития языка. И решение о Python 3 было, пожалуй, самым сложным и непопулярным из всех, что он когда-либо принимал.
Это было видение будущего, которое требовало отсечь прошлое. Гвидо не пошел на компромиссы. Он верил, что эти фундаментальные изменения необходимы для долгосрочного здоровья и актуальности Python. Он выдерживал критику, споры и сопротивление, последовательно продвигая Python 3.
Несмотря на все боли и сопротивление, Python 3 медленно, но верно набирал обороты. Что же стало переломным моментом?
Сегодня Python 3 — это безальтернативный стандарт. Python 2 остался в истории, как напоминание о том, насколько сложным и болезненным может быть прогресс.
Ваша поддержка — это энергия для новых статей и проектов. Спасибо, что читаете!
История Python 2 vs Python 3 — это не просто перечень технических изменений. Это сага, полная уроков для каждого разработчика, для каждого архитектора систем и для каждого, кто участвует в развитии любого открытого проекта:
Сегодня мы видим, что Python 3 расцвел. Он стал самым популярным языком для Data Science, Machine Learning, Web-разработки, автоматизации. Все те изменения, которые казались болезненными тогда, сейчас воспринимаются как должное, как фундамент для дальнейшего роста.