Предлагаю вашему вниманю пересказ замечательной статьи автора Jinja2, Werkzeug и Flask, соавтора Sphinx и Pygments Армина Ронаха. Я получил огромное удовольствие разбирая исходные коды его творений и очень многое для себя почерпнул. Армин пишет отличные фреймворки, и как никто другой может разъяснить, чем чреват переход с Python 2 на Python 3 и почему его не так легко осуществить.
Мысли о Python 3
В последнее время меня часто посещали мысли о состоянии, в котором находится Python 3. Хоть и не с первого взгляда, я полюбил Python и более чем доволен курсом, которым он идёт. Десять лет моя жизнь проходит вместе с Python. И пока это бОльшая часть моей жизни.
Заранее предупреждаю: это очень личная статья. Я насчитал сотню экземпляров одной заглавной буквы в этом тексте.
Это потому, что я очень признателен всем возможностям, полученным за последние два года: возможностям путешествовать по миру, общаться с людьми и разделять дух сотрудничества, который позволяет свободно распространяемым проектам, таким как Python, поощрять инновации и радовать людей. У Python замечательное сообщество, о чём я частенько забываю сказать вслух.
А с другой стороны, хоть я и люблю Python и люблю обсуждать пути и решения, я не связан с проектом какими-либо обязательствами, несмотря на преданность ему. Когда я посещаю совещания о языке, то сразу понимаю, почему мои предложения воспринимаются в штыки, а сам я считаюсь ещё той занозой. «Постоянно жалуется и ничего не делает». Есть столько всего, чего бы мне хотелось видеть в Python, но в конце концов, я его пользователь, а не разработчик.
Когда вы будете читать мои комментарии о Python 3, учитывая что его первая версия уже выпущена, у вас сложится впечатление, что я его ненавижу и вообще не хочу на него переходить. Ещё как хочу, но только не в той форме, в которой он сейчас находится.
Учитывая мой опыт того, что люди ссылаются на статьи спустя много времени после того как они были написаны, позвольте вначале разъяснить ситуацию с Python 3 на момент написания: вышла версия 3.2, следующая версия 3.3 и нет никаких планов когда-либо выпустить Python 2.8. Более того, существует PEP, в котором чёрным по белому написано: релизу не быть. Прекрасно развиваясь, PyPy остаётся проектом, архитектура которого настолько отдалена от всего остального, что его ещё долго никто не будет воспринимать всерьёз. Во многом PyPy делает вещи которые «я бы не сделал» и это мне кажется удивительным.
Почему мы используем Python?
Почему же мы используем Python? Мне кажется, что это очень правильный вопрос, который мы редко себе задаём. У Python есть куча изъянов, но я им всё-равно пользуюсь. На вечеринке, в последний день конференции PyCodeConf этого года, я успел многое обсудить с Ником Кофланом. Мы были подшофе и благодаря этому дискуссия получилась очень искренней. Мы согласились признать факт того, что Python не идеален, как язык, что над некоторыми изъянами продолжается работа и что при внимательном рассмотрении, некоторым из них нет оправданий. Был рассмотрен PEP о «yield from» как пример развития сомнительного дизайна (coroutine как generator) для придачи ему более-менее рабочего вида. Но даже с изменениями принятыми в «yield from», всё это очень далеко от удобства greenlet'ов.
Этот разговор был продолжением услышанного на лекции «Предвзятое мнение о языках программирования», которую читал Гери Бернард в тот же памятный день конференции. Мы пришли к общему мнению о том, что у блоков Ruby восхитительный дизайн, но по многим причинам он не прижился бы в Python (в его текущем состоянии).
Лично я не думаю, что мы используем Python потому, что это совершенный и безупречный язык. Более того, если вы вернётесь в прошлое и присмотритесь к ранним версиям Python то увидите, что он очень и очень уродлив. Не стоит удивляться тому, что в свои ранние годы Python оставался никем незамеченным.
Мне кажется, что размах полученный Python с тех пор, можно считать большим чудом. И вот почему, как мне кажется, мы используем Python: эволюция этого языка была очень плавной, а воплощённые идеи — верными. Ранний Python был ужасен, в нём отсутствовала концепция итераторов и более того, для итерации по словарю приходилось создавать промежуточный список всех его ключей. В какой-то момент исключения были строками, методы строки были не методами а функциями из одноимённого модуля (string). Синтаксис перехвата исключений мучает нас во всех ипостасях языка Python 2, а Юникод появился слишком поздно и частично — никогда.
Однако, в нём было и много чего хорошего. Пускай и небезупречная, идея о модулях с их собственными пространствами имён была восхитительной. Структура языка основанная на мультиметодах* до сих пор во многом не имеет себе равных. Мы ежедневно выигрываем от этого решения, хоть и не отдаём себе в этом отчёта. Этот язык всегда честно делал свою работу и не скрывал происходящее в интерпретаторе (traceback'и, кадры стека, опкоды, кодовые объекты, ast и т.д.), что вкупе с динамической структурой позволяет разработчику быстро произвести отладку и решить проблемы с лёгкостью недостижимой в других языках.
Часто подвергался критике и синтаксис основанный на отступах, но видя, сколько новых языков внедряют этот подход (на ум приходят HAML, CoffeeScript и многие другие) доказывает, что он получил признание.
Даже тогда, когда я не соглашаюсь с тем, как Реймонд* пишет что-то новое для стандартной библиотеки, качество его модулей не вызывает ни малейшего сомнения и это одна из основных причин, по которым я использую Python. Не могу представить себе работу с Python без доступа к модулю collections или itertools.
Но настоящей причиной, по которой я любил и боготворил Python, было предвкушение каждой новой версии, как у нетерпеливого ребёнка, ждущего Рождество. Мелкие, едва заметные улучшения приводили меня в восторг. Даже возможность указывать начало индекса для функции enumerate вызывали у меня чувство благодарности за новый релиз Python. И всё это с учётом обратной совместимости.
Импорт из __future__ — это то, что мы порой так ненавидим и именно то, что делало апгрейды лёгкими и безболезненными. Когда-то я пользовался PHP и совершенно не радовался новым релизам. В PHP не было пространств имён, зато всегда появлялись новые встроенные функции и с каждым релизом я очень надеялся избежать коллизий в названиях (знаю, что мог бы их избежать, если бы использовал префиксы, но это было задолго до того, как я научился основам разработки ПО).
Что же изменилось?
Как же получилось, что мне стало не до новых релизов Python? Могу говорить лишь за себя, но я заметил, что и у других изменилось отношение к новым релизам.
Я никогда не задавался вопросами о том, чем занимались разработчики ядра очередного Python 2.x.
Конечно, что-то было не так уж и хорошо продуманно, например реализация абстрактных классов или особенности их семантики. Но в основном всё сводилось к критике высокоуровневого функционала.
С появлением Python 3 появились и внешние факторы, из-за которых мне неожиданно пришлось изменить общий подход к работе с языком. Раньше я долго не использовал новые возможности языка, хоть и был им рад, т.к. в основном писал библиотеки. Было бы ошибкой использовать самое новое и самое лучшее. Код Werkzeug'a до сих пор забит хаками позволяющими ему работать на Python 2.3, хотя сейчас минимальные требования поднялись до версии 2.5. Я оставлял в коде багфиксы для стандартной библиотеки, ведь некоторые производители (печально известная Apple) никогда не обновляют интерпретатор до тех пор, пока в нём не будет найдена критическая уязвимость.
Всё это невозможно с Python 3. С ним всё превращается в разработку для 2.х либо 3.х. И никакого срединного решения не предвидится.
После анонса Python 3, Гвидо всегда восхищенно говорил о 2to3 и том, как она облегчит портирование. А вышло так, что 2to3 это худшее, что могло случиться с Python.
Я испытал огромные трудности при портировании Jinja2 с помощью 2to3, о чём впоследствии сильно сожалел. Более того, в отпочковавшемся проекте JSON Jinja, я убрал все хаки написанные для корректной работы 2to3 и никогда больше не буду его использовать. Как и многие другие, сейчас я вовсю стараюсь поддерживать код работающий как на версиях 2.х так и 3.х. Вы спросите почему? Потому, что 2to3 очень нетороплива, из рук вон плохо интегрируется в процесс тестирования, зависит от версии используемого Python 3 и ко всему прочему настраивается разве что с применением чёрной магии. Это болезненный процесс, сводящий на нет всё удовольствие получаемое от написания библиотек. Я любил обтёсывать Jinja2, но перестал это делать в тот момент, когда порт на Python 3 был готов, т.к. боялся что-либо в нём сломать.
Сейчас же, идея о разделяемой кодовой базе упирается в то, что я должен поддерживать Python вплоть до версии 2.5.
Перемены вызванные Python 3 привели весь наш код в негодность, что никак не служит оправданием для его незамедлительного переписывания и апгрейда. По моему глубоко субъективному мнению, Python 3.3/3.4 должен больше походить на Python 2 а Python 2.8 должен быть ближе к Python 3. Так сложилось, что Python 3 — это XHTML в мире языков программирования. Он не совместим с тем, что пытается заменить, а взамен практически ничего не предлагает кроме того, что он более «правильный».
Немного о Юникоде
Очевидно, что самым большим изменением в Python 3 стала обработка Юникода. Может показаться, что насаживание Юникода всем и каждому — это благо. А ещё, это взгляд на мир сквозь розовые очки, ведь в настоящем мире мы сталкиваемся не только с байтами и Юникодом, но и со строками с известной кодировкой. Хуже всего то, что во многом Python 3 стал этаким Fisher Price* в мире языков программирования. Некоторые возможности были удалены, т.к. разработчики ядра посчитали, что о них можно будет «легко порезаться». И всё это далось ценой изъятия широко используемого функционала.
Вот конкретный пример: операции с кодеками в 3.х на данный момент ограничены преобразованиями Unicode <-> bytes. Никаких bytes <-> bytes или Unicode <-> Unicode. Выглядит разумно, но приглядевшись вы увидите, что сей удалённый функционал как раз то, что жизненно необходимо.
Одним из самых замечательных свойств системы кодеков в Python 2 было то, что она создавалась с прицелом на разнообразную работу с огромным количеством кодировок и алгоритмов. Можно было использовать кодек чтобы кодировать и раскодировать строки, а также у кодека можно было попросить объект, предоставляющий операции на потоках и других неполных данных. A ещё, система кодеков одинаково хорошо работала с контент- и transfer- кодировками. Стоило написать новый кодек и зарегистрировать его, как каждая часть системы узнавала о нём автоматически.
Любой, кто брался за написание HTTP библиотеки на Python, с удовольствием открывал для себя, что кодеки можно использовать не только для декодирования UTF-8 (актуальная кодировка символов), но например и для gzip (алгоритм сжатия). Это относится не только к строкам, но и к генераторам или файловым объектам, если конечно знать, как ими пользоваться.
На настоящий момент, в Python 3, всё это попросту не работает. Они не только удалили эти функии из объекта string, но убрали и кодирование byte -> byte, взамен ничего не оставив. Если я не ошибаюсь, понадобилось 3 года для признания проблемы и начала обсуждения о возвращение вышеперечисленного функционала в 3.3.
Далее, Юникод протолкнули туда, где ему совсем не место. К таким местам относятся слой файловой системы и модуль URL. A ещё, куча Юникод-функционала была написана с точки зрения программиста живущего в 70-х.
Файловые системы UNIX основаны на байтах. Так уж оно устроено и с этим ничего не поделаешь. Естественно, было бы здорово это изменить, что вообще-то невозможно, не сломав существующего кода. A всё потому, что сменa кодировки — лишь малая часть того, что необходимо для Юникод-ориентированной файловой системы. Вдобавок, вопросы форм нормализации и хранения информации о регистре при уже проведённой нормализации остаются открытыми. Останься тип bytestring в Python 3, этих проблем можно было бы избежать. Однако его нет и его замена, тип byte, не ведёт себя так, как себя ведут строки. Он ведёт себя как тип данных, написанный в наказание людям пользующимися байтовыми данными, которые одновременно существуют в виде строки. Не похоже, чтобы он разрабатывался как инструмент, с помощью которого программисты могли бы решить эти проблемы. Проблемы, которые более чем реальны.
Так вот, если вы работаете с файловой системой из Python 3, то странное чувство не будет покидать вас несмотря на наличие новой кодировки с суррогатными парами и экранированием. Это болезненный процесс, болезненный потому, что не существует инструментa для разгребания этого бедлама. Python 3 как-бы обращается к вам, «Приятель, с этого момента у твоей файловой системы Юникод», но при этом не объясняет, с какого конца надо разгребать этот беспорядок. Он даже не проясняет, на самом ли деле файловая система поддерживает Юникод, или же это Python подделывает эту поддержку. Oн не раскрывает подробности о нормализации или о том, как нужно сравнивать имена файлов.
Он работает в лабораторных условиях, но ломается в условиях полевых. Так сложилось, что у моего мака американская раскладка клавиатуры, американская локаль, да практически всё американское, разве что даты и числа форматируются по-другому. В результате всего этого (и как я предполaгаю того, что я проапгрейдил свой мак со времён Tiger'a), у меня возникла следующая ситуация: зайдя на свой удалённый сервер, я получил локаль выставленную в строковое значение «POSIX». Вы спрашиваете, что за «POSIX»? А хрен его знает. Вот и Python будучи в таком же неведении как и я, решил работать с «ANSI_X3.4_1968». В этот памятный день я узнал, что у ASCII есть много имён. Оказалось, что это всего лишь одно из названий ASCII. И вот тебе на, мой удалённый интерпретатор Python криво отобразил содержимое директории с интернациализированными именами файлов. Как они там оказались? Я накидал туда статьи из Википедии с их изначальными названиями. Делал же я это с помощью Python 3.1, который замалчивал происходящее с файлами, вместо того, чтобы выдавать исключения или задействовать какие-либо хаки.
Но неисправности с файловыми системами — это всего лишь цветочки. Python также использует переменные окружения (где как вы знаете, полно мусора) для установки файловой кодировки по-умолчанию. Во время конференции, я попросил парочку посетителей угадать кодировку, использующуюся по-умолчанию для текстовых файлов в Python 3. Более 90% этой маленькой выборки были уверены, что это UTF-8. А вот и нет! Она устанавливается в зависимости от локали платформы. Как я вам и говорил, привет из 70-х.
Смеха ради, я залогинился на оба контролируемых мною сервера и обнаружил, что у одного из них при входе через консоль стоит кодировка latin1, которая переключается на latin15 когда вход осуществляется через ssh под рутом, и UTF-8, если я заходил через своего пользователя. Чертовски занимательно, но винить остаётся лишь самого себя. Не сомневаюсь, что я не единственный, чей сервер волшебным образом переключает кодировки учитывая, что по-умолчанию SSH пересылает настройки локали при логине.
Почему я пишу об этом здесь? Да потому, что снова и снова приходится доказывать, что поддержка Юникода в Python 3 доставляет мне куда больше неприятностей, чем в Python 2.
Кодирование и декодирование Юникода не встаёт на пути у того, кто следует Дзену Python 2 в том, что «явное лучше неявного». «Байты входят, Юникод выходит» — именно так работают куски приложений, которые общаются с другими сервисами. Это можно объяснить. Вы можете объяснить это хорошенько задокументировав. Вы подчеркнёте, что для внутренней обработки текста в виде Юникода есть свои причины. Вы расскажите пользователю о том, что мир вокруг нас суров и основан на байтах, поэтому вам приходится кодировать и декодировать для общения с этим миром. Эта концепция может быть в новинку для пользователя. Но стоит лишь подобрать нужные слова и все хорошенько расписать, как одной головной болью станет меньше.
Почему я говорю об этом с такой уверенностью? Потому, что с 2006 года, все мои программы насаживают пользователям Юникод, a количество запросов касательно Юникода не идет ни в какое сравнение с прорвой запросов о работе с пакетами или системой импортирования. Даже с distutils2, в царстве Python, пакеты остаются гораздо большей проблемой, чем Юникод.
Куда уж не естественное развитие событий: запрятать Юникод подальше от пользователя Python 3. Но обернулось это тем, что людям стало ещё сложнее представлять, как всё это работает. Нужно ли нам априори неявное поведение? Я в этом не уверен.
Несомненно, уже сейчас Python 3 на правильном пути. Я обнаружил, что всё больше разговоров идёт о возвращении некоторых API для работы с байтами. Моей наивной задумкой была идея о третьем типе строки в Python 3, под названием estr, или чего-нибудь в этом роде. Он работал бы точно так, как str в Python 2, хранил бы байты и имел такой же набор строковых API. Однако в нём бы также содержалась информация о кодировке, которая бы использовалась для прозрачного декодирования в Юникод-строку или приведения к байтовому объекту. Этот тип был бы священным граалем могущим облегчить портирование.
Но его нет, а интерпретатор Python не разрабатывался с заделом на ещё один тип строки.
Мы разрушили их Мир
Ник рассказывал о том, как разработчики ядра Python разрушили мир веб-программистов. Пока разрушения простилаются до тех пор, где заканчивается обратная НЕсовместимость Python. Но наш мир был разрушен не более, чем мир других разработчиков. Ведь у нас единый мир. Сеть основана на байтах с кодировками, но в основном это касается протоколов низкого уровня. Общение с большей частью того, что лежит на нижнем уровне, происходит на языке байтов с кодировками.
Однако, главные изменения коснулись образа
Да, это более правильный подход, но на деле у него больше нет никаких достоинств, кроме того что он более правильный.
Работая с функционалом ввода/вывода в Python 3, я убедился, что он великолепен. Но в реальности, он не идет ни в какое сравнение с тем, как работал Python 2. Может показаться, что у меня много предубеждений, ведь я так много работал с Python 2 и так мало с Python 3, однако, написание большего количества кода для достижения одинакового фунционала, считается дурным тоном. А с Python 3, мне приходиться всё это делать, учитывая все его аспекты.
Но ведь портирование работает!
Конечно же, портирование на Python 3 работает. Это было доказано, и не один раз. Но только потому что что-то возможно и проходит все тесты не значит, что всё хорошо сделано. Я — человек с недостатками и совершаю кучу ошибок. В то же время, я горжусь стремлением довести до блеска свои любимые API. Иногда я ловлю себя на том, что снова и снова переписываю кусок кода, чтобы он стал более удобным для пользователя. При работе с Flask я столько времени потратил на оттачивание основного функционала, что самое время начать говорить об одержимости.
Я хочу, чтобы он работал идеально. Когда я использую API для обычной задачи, мне хочется, чтобы они имели такой же уровень совершенства, каков присущ дизайну Porshe. Да, это — лишь внешний слой для разработчика, но продукт должен быть хорошо разработан от начала и до конца.
Я могу заставить свой код «работать» на Python 3 и всё равно я буду его ненавидеть. Я хочу, чтобы он работал. Но при этом, используя свои или чужие библиотеки, мне хочется получать такое же удовольствие с Python 3, какое я получаю от Python 2.
Jinja2, например, некорректно использует слой ввода/вывода в Python 3, так как невозможно использовать один и тот же код на 2.x и 3.x без переключения между реализацией во время исполнения. Теперь, шаблоны открываются в бинарном режиме как в 2.х, так и в 3.х, т.к. это — единственный надёжный подход, а после, Jinja2 сама декодирует данные из этого бинарного потока. Вообще-то это работает, спасибо нормализации разделителей новых строк. Но я более чем уверен, что все, кто работает в Windows и самостоятельно не нормализует разделители строк, рано или поздно попадут в ситуацию с месивом из различных разделителей, совершенно об этом не подозревая.
Принимая Python 3
Python 3 многое изменил, это факт. Без сомнения, за ним будущее в которое мы направляемся. Многое в Python 3 подаёт большие надежды: значительно улучшенная система импортирования, появление __qualname__, новый способ распространения пакетов Python, унифицированное представление строк в памяти.
Но в настоящее время, портирование библиотеки на Python 3 выглядит как разработка библиотеки на Python 2 и создание её (прошу прощения за мой французский) хитрожопой версии для Python 3 лишь бы доказать, что она работает. Про Jinja2 на Python 3 можно во всех отношениях сказать, что она чертовски уродлива. Это ужасно и мне должно быть стыдно за это. Например, в версии для Python 3, Jinja2 загружала два одномегабайтовых регулярных выражения в память, и я совершенно не заботился о её освобождении. Мне просто хотелось, чтобы она хоть как-нибудь работала.
Так почему же мне пришлось использовать мегабайтовые регулярные выражения в Jinja2? Да потому, что движок регулярных выражений в Python не поддерживает Unicode-категории. А с такими ограничениями пришлось выбирать меньшее зло из двух: либо забить на новые Unicode-идентификаторы в Python 3 и ограничиться идентификаторами ASCII, либо создать огромное регулярное выражение вручную, вписав в него все необходимые определения.
Сказанное выше — лучший пример объясняющий, почему я пока не готов к Python 3. Он не предоставляет инструменты для работы с его же нововведениями. Python 3 жизненно необходимы Unicode-ориентированные регулярные выражения, ему нужны API для работы с локалями, учитывающими взятый курс на Unicode. Ему нужен более продвинутый модуль path, раскрывающий поведение нижележащей файловой системы. Он должен сильнее насаждать единую стандартную кодировку для текстовых файлов, не зависящую от среды исполнения. Он должен предоставлять больше инструментов для работы с закодированными строками. Ему нужна поддержка IRI, а не только URL. Ему нужно что-то большее чем «yield from». В нём должны быть вспомогательные механизмы для транскодирования, которые необходимы для отображения URL в файловую систему.
Ко всему вышеперечисленному можно добавить выпуск Python 2.8, который бы был чуть ближе к Python 3. По мне, существует лишь один реалистичный способ перехода на Python 3: библиотеки и программы должны быть полностью осведомлены о Юникоде и интегрированы в новую экосистему Python 3.
Не пускайте дилетантов прокладывать ваш Путь
Самой большой ошибкой Python 3 является его бинарная несовместимость с Python 2. Тут я подразумеваю отсутствие возможности совместной работы интерпретаторов Python 2 и Python 3 в пространстве общего процесса. В результате, вы не можете запустить Gimp одновременно со скриптовыми интерфейсами как Python 2 так и Python 3. То же самое относится к vim и Blender. Мы просто-напросто не можем. Не сложно понаписать кучу хаков с отдельными процессами и вычурным IPC, но это никому не нужно.
Таким образом, программист, которому придётся раньше других осваивать Python 3, будет делать это из-под палки. И не факт, что этот программист вообще хорошо знаком с Python. А причина, положа руку на сердце, в том, что деньги крутятся вокруг Python 2. Даже если по ночам тратить все наши силы на Python 3, то днём мы всё-равно будем возвращаться к Python 2. Так будет до поры, до времени. Однако, если, кучка графических дизайнеров начнёт писать скрипты на Blender под Python 3, то вот вам и нужная адоптация.
Мне совсем не хочется видеть kak CheeseShop* будет мучаться от обилия кривых портов библиотек на Python 3. Мне совсем не хочется видеть ещё одну Jinja2 и особенно уродливую кучу кода призванного работать и на 2.х, и на 3.х. Туда же, хаки вроде sys.exc_info()[1], для обхода синтактических различий, хаки конвертирования литералов во время исполнения для совместимости с 2.х и 3.х и многое многое другое. Всё это плохо отражается не только на производительности во время исполнения, но и на основных постулатax Python: красивый и разборчивый код без хаков.
Признать неудачи, Учиться и Приспосабливаться
Сейчас самое время для нас собраться и обсудить всё то, что люди делают для работы их кода на 2.x и 3.х. Технологии эволюционируют быстрыми темпами и мне будет очень обидно наблюдать, как Python разваливается только потому, что кто-то упустил из виду тёмные тучи на горизонте.
Python не «слишком велик, чтобы о нём забыли». Он может очень быстро потерять свою популярность. Pascal и Delphi попали в узкую нишу, несмотря на то, что они оставались восхитительными языками даже после появления на свет C# и .NET framework. Больше всего на их падении сказался неправильный менеджмент. Люди до сих пор разрабатывают на Pascal, но много ли тех, кто начинает писать на нём новые проекты? Deplhi не работает на iPhone и Android. Он не очень-то хорошо интегрирован в рынок UNIX. И если быть честными, в некоторых областях Python уже сдаёт позиции. Python был достаточно популярен в области компьютерных игр, но этот поезд уже давно ушёл. В web-сообществе, новые конкуренты появляются как грибы после дождя, и нравится это нам или нет, JavaScript всё чаще и чаще наступает на позиции Python как скриптового языка программирования.
Delphi не смог вовремя приспособиться и народ просто перешёл на другие технологии. Если 2to3 это наш путь перехода на Python 3, то py2js — это путь перехода на JavaScript.
И вот что я предлагаю: могли бы мы составить список всего, что усложняет переход на Python 3 и список решений, позволяющих эти проблемы решить? Могли бы мы заново обсудить разработку Python 2.8, если он сможет помочь с портированием? Могли бы мы признать PyPy действительной имплементацией Python, достаточно весомой, чтобы повлиять на то, как пишем код?
Armin Ronacher,
7 Декабря 2011.
От переводчика: Прочитав эту статью, первым же желанием было поделиться с другими, возникло острое ощущение, что «мир должен знать». Пересказать статью помогали моя коллега Ирина Пирогова и моя жена Айла Мехтиева, за что им огромное спасибо!
Автор: BasicWolf