Мысли о Python 3
Предлагаю вашему вниманю пересказ замечательной статьи автора 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 2 очень часто использовались объекты Unicode для общения с нижними уровнями. При необходимости, объекты кодировались в байты и наоборот. Приятным для нас побочным эффектом, была например возможность ускорить некоторые операции, кодируя и декодируя данные на ранних стадиях и передавая их в канал понимающий Юникод. Во многом это позволяет функционировать модулю сериализации в ядре. К примеру, Pickle общается с потоками поддерживающими как байты, так и Юникод. В какой-то степени то же самое можно сказать и о simplejson. И вот, появляется Python 3, в котором внезапно нужно разделять Unicode и байтовые потоки. Многие API не выживут на пути к Python 3, без кардинальных изменений в их интерфейсах.
Да, это более правильный подход, но на деле у него больше нет никаких достоинств, кроме того что он более правильный.
Работая с функционалом ввода/вывода в 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.
От переводчика: Прочитав эту статью, первым же желанием было поделиться с другими, возникло острое ощущение, что «мир должен знать». Пересказать статью помогали моя коллега Ирина Пирогова и моя жена Айла Мехтиева, за что им огромное спасибо!
Python Language
Несовместимость, перемещающаяся с Python 2 на Python 3
В отличие от большинства языков, Python поддерживает две основные версии. С 2008 года, когда был выпущен Python 3, многие сделали переход, в то время как многие из них этого не сделали. Чтобы понять оба варианта, в этом разделе рассматриваются важные различия между Python 2 и Python 3.
замечания
В настоящее время существуют две поддерживаемые версии Python: 2.7 (Python 2) и 3.6 (Python 3). Кроме того, версии 3.3 и 3.4 получают обновления безопасности в исходном формате.
Python 2.7 совместим с предыдущими версиями Python и может запускать код Python из версий 1.x и 2.x Python без изменений. Он широко доступен с обширной коллекцией пакетов. Он также считается устаревшим разработчиком CPython и получает только защиту и исправление ошибок. Разработчики CPython намерены отказаться от этой версии языка в 2020 году .
Согласно Python Enhancement Proposal 373 , запланированные будущие выпуски Python 2 после 25 июня 2016 года не будут исправлены, но исправления ошибок и обновления безопасности будут поддерживаться до 2020 года. (В нем не указано, какой точной датой в 2020 году станет дата заката Python 2.)
Python 3 намеренно нарушил совместимость в обратном направлении, чтобы решить проблемы, с которыми языковые разработчики сталкивались с ядром языка. Python 3 получает новые разработки и новые функции. Это версия языка, которую разработчики языка намерены продвигать вперед.
За время между первоначальным выпуском Python 3.0 и текущей версией некоторые функции Python 3 были перенесены в Python 2.6, а другие части Python 3 были расширены, чтобы иметь синтаксис, совместимый с Python 2. Поэтому можно писать Python, который будет работать как на Python 2, так и на Python 3, используя будущие импорты и специальные модули (например, шесть ).
Будущий импорт должен быть в начале вашего модуля:
Для получения дополнительной информации о модуле __future__ см. Соответствующую страницу в документации Python .
Инструмент 2to3 — это программа Python, которая преобразует код Python 2.x в код Python 3.x, см. Также документацию Python .
В пакете 6 предусмотрены утилиты для совместимости с Python 2/3:
- унифицированный доступ к переименованным библиотекам
- переменные для типов string / unicode
- функции для метода, который был удален или был переименован
Ссылка на различия между Python 2 и Python 3 приведена здесь .
Операция печати и функции печати
В Python 2 print — это утверждение:
В Python 3 функция print() — это функция с аргументами ключевого слова для общего использования:
Функция печати имеет следующие параметры:
sep — это то, что отделяет объекты, которые вы передаете, для печати. Например:
end — это то, что следует за завершением инструкции print. Например:
Повторная печать после завершающего оператора печати, не относящегося к новой строке, будет напечатана в той же строке:
Примечание. Для будущей совместимости функция print также доступна в Python 2.6; однако его нельзя использовать, если разбор инструкции print не отключен
Эта функция имеет тот же формат, что и Python 3, за исключением того, что ему не хватает параметра flush .
См. PEP 3105 для обоснования.
Строки: байты против Unicode
В Python 2 есть два варианта строки: те из байтов с типом ( str ) и те, что сделаны из текста с типом ( unicode ).
В Python 2 объект типа str всегда является байтовой последовательностью, но обычно используется как для текстовых, так и для двоичных данных.
Строковый литерал интерпретируется как байтовая строка.
Есть два исключения: вы можете явно определить литерал Unicode (text) , префиксного литерала с помощью u :
Кроме того, вы можете указать, что строковые литералы всего модуля должны создавать литералы Unicode (text):
Чтобы проверить, является ли ваша переменная строкой (либо Unicode, либо байтовая строка), вы можете использовать:
В Python 3 тип str — это текстовый тип Unicode.
Кроме того, Python 3 добавил объект bytes , подходящий для двоичных «blobs» или записи в независимые от кодирования файлы. Чтобы создать объект байтов, вы можете префикс b в строковый литерал или вызвать метод encode строки:
Чтобы проверить, является ли значение строкой, используйте:
Также возможно префикс строковых литералов с префиксом u чтобы упростить совместимость между базами данных Python 2 и Python 3. Поскольку в Python 3 все строки по умолчанию Unicode, добавление строкового литерала с u имеет никакого эффекта:
Python 2 в сырце Unicode строки префикс ur не поддерживаются, однако:
Обратите внимание, что вы должны encode объект Python 3 text ( str ), чтобы преобразовать его в представление bytes этого текста. Кодировка по умолчанию этого метода — UTF-8 .
Вы можете использовать decode чтобы задать объект bytes для текста Unicode, который он представляет:
Хотя тип bytes существует как в Python 2, так и в 3, тип unicode существует только в Python 2. Чтобы использовать неявные строки Unicode Python 3 в Python 2, добавьте следующее в начало файла кода:
Другое важное различие заключается в том, что индексирование байтов в Python 3 приводит к выводу int так:
В то время как разрезание в размере одного результата приводит к объекту длиной 1 байт:
Кроме того, Python 3 исправляет некоторые необычные действия с помощью unicode, т. Е. Обратные байтовые строки в Python 2. Например, разрешена следующая проблема :
Целостный отдел
Стандартный символ разделения ( / ) работает по-разному в Python 3 и Python 2 при применении к целым числам.
При делении целого на другое целое число в Python 3 операция деления x / y представляет собой истинное деление (использует метод __truediv__ ) и производит результат с плавающей запятой. Между тем, та же операция в Python 2 представляет собой классическое разделение, которое округляет результат до отрицательной бесконечности (также известный как слово ).
| Код | Выход Python 2 | Выход Python 3 |
|---|---|---|
| 3 / 2 | 1 | 1,5 |
| 2 / 3 | 0 | 0,6666666666666666 |
| -3 / 2 | -2 | -1,5 |
Поведение округления к нулю было устарело в Python 2.2 , но осталось в Python 2.7 для обратной совместимости и было удалено в Python 3.
Примечание. Чтобы получить результат с плавающей точкой в Python 2 (без округления по полу), мы можем указать один из операндов с десятичной точкой. Вышеприведенный пример 2/3 который дает 0 в Python 2, должен использоваться как 2 / 3.0 или 2.0 / 3 или 2.0/3.0 для получения 0.6666666666666666
| Код | Выход Python 2 | Выход Python 3 |
|---|---|---|
| 3.0 / 2.0 | 1,5 | 1,5 |
| 2 / 3.0 | 0,6666666666666666 | 0,6666666666666666 |
| -3.0 / 2 | -1,5 | -1,5 |
Существует также оператор деления пола ( // ), который работает одинаково в обеих версиях: он округляется до ближайшего целого. (хотя float возвращается при использовании с float). В обеих версиях оператор // сопоставляется с __floordiv__ .
| Код | Выход Python 2 | Выход Python 3 |
|---|---|---|
| 3 // 2 | 1 | 1 |
| 2 // 3 | 0 | 0 |
| -3 // 2 | -2 | -2 |
| 3.0 // 2.0 | 1,0 | 1,0 |
| 2.0 // 3 | 0.0 | 0.0 |
| -3 // 2.0 | -2,0 | -2,0 |
В явном виде можно обеспечить прямое разделение или разделение полов с использованием собственных функций в модуле operator :
Хотя ясное и ясное, использование операторских функций для каждого деления может быть утомительным. Частое изменение поведения оператора / часто бывает предпочтительным. Общей практикой является устранение типичного поведения деления путем добавления from __future__ import division в качестве первого оператора в каждом модуле:
| Код | Выход Python 2 | Выход Python 3 |
|---|---|---|
| 3 / 2 | 1,5 | 1,5 |
| 2 / 3 | 0,6666666666666666 | 0,6666666666666666 |
| -3 / 2 | -1,5 | -1,5 |
from __future__ import division гарантирует, что оператор / представляет истинное деление и только внутри модулей, которые содержат импорт __future__ , поэтому нет никаких веских причин не включать его во все новые модули.
Примечание . Некоторые другие языки программирования используют округление до нуля (усечение), а не округление до отрицательной бесконечности, как это делает Python (т.е. на этих языках -3 / 2 == -1 ). Такое поведение может вызвать путаницу при переносе или сравнении кода.
Примечание по операндам float . В качестве альтернативы from __future__ import division можно использовать обычный символ разделения / и гарантировать, что хотя бы один из операндов является float: 3 / 2.0 == 1.5 . Однако это можно считать плохой практикой. Слишком просто написать average = sum(items) / len(items) и забыть использовать один из аргументов для float. Более того, такие случаи могут часто уклоняться от уведомления во время тестирования, например, если вы тестируете массив, содержащий float s, но получаете массив int s в процессе производства. Кроме того, если тот же код используется в Python 3, программы, ожидающие, что 3/2 3 / 2 == 1 будет True, будут работать неправильно.
См. PEP 238 для более подробного обоснования того, почему оператор разделения был изменен в Python 3 и почему следует избегать разделения в старом стиле.
См. Раздел « Простая математика» для получения дополнительной информации о разделении.
Сокращение больше не является встроенным
В Python 2 reduce доступно либо как встроенная функция, либо из пакета functools (начиная с версии 2.6), тогда как в Python 3 reduce доступно только из functools . Однако синтаксис reduce как в Python2, так и в Python3 одинаковый и reduce(function_to_reduce, list_to_reduce) .
В качестве примера рассмотрим сокращение списка до одного значения путем деления каждого из соседних чисел. Здесь мы используем функцию truediv из библиотеки operator .
В Python 2.x это просто:
В Python 3.x пример становится немного сложнее:
Мы также можем использовать from functools import reduce чтобы избежать reduce вызова с помощью имени пространства имен.
Различия между диапазонами и функциями xrange
В Python 2 функция range возвращает список, тогда как xrange создает специальный объект xrange , который является неизменной последовательностью, которая, в отличие от других встроенных типов последовательностей, не поддерживает срез и не имеет ни методов index ни count :
В Python 3 xrange был расширен до последовательности range , что теперь создает объект range . Нет типа xrange :
Кроме того, поскольку Python 3.2, range также поддерживает нарезку, index и count :
Преимущество использования специального типа последовательности вместо списка состоит в том, что интерпретатору не нужно выделять память для списка и заполнять его:
Поскольку последнее поведение обычно желательно, первое было удалено в Python 3. Если вы все еще хотите иметь список в Python 3, вы можете просто использовать конструктор list() для объекта range :
Совместимость
Чтобы поддерживать совместимость между версиями Python 2.x и Python 3.x, вы можете использовать builtins модуль из future внешнего пакета для достижения как совместимости с переходом, так и обратной совместимости :
range в future библиотеке поддерживает нарезку, index и count во всех версиях Python, как и встроенный метод на Python 3.2+.
Распаковка итераций
В Python 3 вы можете распаковать итерацию, не зная точного количества элементов в ней и даже иметь переменную, удерживающую конец итерабельного. Для этого вы предоставляете переменную, которая может собирать список значений. Это делается путем размещения звездочки перед именем. Например, распаковка list :
Примечание . При использовании синтаксиса *variable variable всегда будет списком, даже если исходный тип не был списком. Он может содержать ноль или более элементов в зависимости от количества элементов в исходном списке.
Аналогично, распаковка str :
Пример распаковки date ; _ Используется в этом примере в качестве переменной (холостой мы заинтересованы только в year стоимости):
Стоит отметить, что, поскольку * ест переменное количество элементов, вы не можете иметь два * s для одного и того же итерабельного в задании — он не будет знать, сколько элементов входит в первую распаковку, и сколько во втором :
До сих пор мы обсуждали распаковку в заданиях. * и ** были расширены в Python 3.5 . Теперь в одном выражении можно иметь несколько операций распаковки:
Также можно распаковать итерируемый аргумент функции:
Распаковка словаря использует две соседние звезды ** ( PEP 448 ):
Это позволяет как переопределять старые значения, так и объединять словари.
Python 3 удаляет распаковку в функции. Следовательно, в Python 3 не работает следующее
См. PEP 3113 для подробного обоснования.
Поднятие и обработка исключений
Это синтаксис Python 2, обратите внимание на запятые , на raise и except строк:
В Python 3, , синтаксис отбрасывается и заменяется скобкой и в as ключевого слова:
Для обратной совместимости синтаксис Python 3 также доступен в Python 2.6, поэтому он должен использоваться для всего нового кода, который не должен быть совместим с предыдущими версиями.
Python 3 также добавляет цепочку исключений , в которой вы можете указать, что причиной этого исключения является другое исключение. Например
Исключение, возникающее в инструкции except имеет тип DatabaseError , но исходное исключение помечено как атрибут __cause__ этого исключения. Когда отображается трассировка, исходное исключение также будет отображаться в traceback:
Если вы выбрали except блок без явной цепочки:
Ни один из них не поддерживается в Python 2.x; исходное исключение и его трассировка будут потеряны, если в исключающем блоке будет создано другое исключение. Следующий код может использоваться для совместимости:
Чтобы «забыть» ранее заброшенное исключение, используйте raise from None
Теперь трассировка будет просто
Или, чтобы сделать его совместимым с Python 2 и 3, вы можете использовать шесть пакетов следующим образом:
.next () для итераторов, переименованных
В Python 2 итератор может быть пройден с помощью метода, называемого next на самом итераторе:
В Python 3 метод .next был переименован в .__next__ , признав свою «магическую» роль, поэтому вызов .next повысит AttributeError . Правильный способ доступа к этой функциональности как в Python 2, так и в Python 3 — это вызов next функции с итератором в качестве аргумента.
Этот код переносится в разных версиях от версии 2.6 до последних версий.
Сравнение различных типов
Можно сравнить объекты разных типов. Результаты являются произвольными, но непротиворечивыми. Они упорядочены так, что None меньше, чем что-либо еще, числовые типы меньше, чем нечисловые типы, а все остальное упорядочено лексикографически по типу. Таким образом, int меньше, чем str а tuple больше, чем list :
Первоначально это было сделано, поэтому список смешанных типов можно было отсортировать, и объекты будут сгруппированы по типу:
Исключение возникает при сравнении разных (нечисловых) типов:
Чтобы отсортировать смешанные списки в Python 3 по типам и добиться совместимости между версиями, вы должны предоставить ключ к отсортированной функции:
Использование str в качестве key функции временно преобразует каждый элемент в строку только для целей сравнения. Затем он видит строковое представление, начиная с [ , ' , < или 0-9 и он может сортировать эти (и все следующие символы).
Вход пользователя
В Python 2 пользовательский ввод принимается с использованием функции raw_input ,
В то время как в Python 3 пользовательский ввод принимается с использованием функции input .
В Python 2 input функция принимает вход и интерпретирует его. Хотя это может быть полезно, оно имеет несколько соображений безопасности и было удалено в Python 3. Для доступа к той же функциональности можно использовать eval(input()) .
Чтобы сохранить сценарий переносимым по двум версиям, вы можете поместить код ниже в верхней части своего сценария Python:
Изменения в словаре
В Python 3 многие из методов словаря отличаются поведением от Python 2, и многие также были удалены: has_key , iter* и view* исчезли. Вместо d.has_key(key) , который давно устарел, теперь нужно использовать key in d .
В Python 2 словарные keys методов, values и items возвращают списки. В Python 3 вместо этого они возвращают объекты вида ; объекты представления не являются итераторами, и они отличаются от них двумя способами, а именно:
- они имеют размер (на них можно использовать функцию len )
- они могут повторяться много раз
Кроме того, как итераторы, изменения в словаре отражаются в объектах представления.
Python 2.7 поддерживает эти методы с Python 3; они доступны как viewkeys , viewvalues и viewitems . Чтобы преобразовать код Python 2 в код Python 3, соответствующие формы:
- d.keys() , d.values() и d.items() Python 2 должны быть изменены в list(d.keys()) , list(d.values()) и list(d.items())
- d.iterkeys() , d.itervalues() и d.iteritems() должны быть изменены на iter(d.keys()) или даже лучше, iter(d) ; iter(d.values()) и iter(d.items()) соответственно
- и, наконец, методы Python 2.7 вызовы d.viewkeys() , d.viewvalues() и d.viewitems() могут быть заменены на d.keys() , d.values() и d.items() .
Портирование кода Python 2, который выполняет итерации по словарным клавишам, значениям или элементам при их мутации, иногда бывает сложным. Рассматривать:
Код выглядит так, как если бы он работал аналогично в Python 3, но там метод keys возвращает объект вида, а не список, и если словарь изменяет размер при повторении, код Python 3 будет сбой с RuntimeError: dictionary changed size during iteration . Разумеется, решение должно правильно записываться for key in list(d) .
Аналогично, объекты просмотра ведут себя иначе, чем итераторы: нельзя использовать next() для них, и нельзя возобновить итерацию; он вместо этого перезапустится; если код Python 2 передает возвращаемое значение d.iterkeys() , d.itervalues() или d.iteritems() в метод, который ожидает итератор вместо итерабельного , то это должно быть iter(d) , iter(d.values()) или iter(d.items()) в Python 3.
Оператор exec является функцией в Python 3
В Python 2 exec — это оператор со специальным синтаксисом: exec code [in globals[, locals]]. В Python 3 exec теперь есть функция: exec(code, [, globals[, locals]]) , а синтаксис Python 2 поднимет SyntaxError .
Поскольку print была изменена из оператора в функцию, был __future__ импорт __future__ . Тем не менее, нет from __future__ import exec_function , поскольку он не нужен: оператор exec в Python 2 также может использоваться с синтаксисом, который выглядит точно так же, как вызов функции exec в Python 3. Таким образом, вы можете изменить утверждения
и последние формы гарантированно работают одинаково как в Python 2, так и в Python 3.
Ошибка функции hasattr в Python 2
В Python 2, когда свойство hasattr ошибку, hasattr игнорирует это свойство, возвращая False .
Эта ошибка исправлена в Python3. Поэтому, если вы используете Python 2, используйте
или вместо этого использовать getattr
Переименованные модули
Несколько модулей в стандартной библиотеке были переименованы:
| Старое название | Новое имя |
|---|---|
| _winreg | WinREG |
| ConfigParser | ConfigParser |
| copy_reg | copyreg |
| Очередь | очередь |
| SocketServer | SocketServer |
| _markupbase | markupbase |
| магнезии | reprlib |
| test.test_support | test.support |
| Tkinter | Tkinter |
| tkFileDialog | tkinter.filedialog |
| urllib / urllib2 | urllib, urllib.parse, urllib.error, urllib.response, urllib.request, urllib.robotparser |
Некоторые модули даже были преобразованы из файлов в библиотеки. Возьмите tkinter и urllib сверху в качестве примера.
Совместимость
Поддерживая совместимость между версиями Python 2.x и 3.x, вы можете использовать future внешний пакет, чтобы включить импорт стандартных пакетов стандартного пакета с именами Python 3.x в версиях Python 2.x.
Октальные константы
В Python 2 восьмой литерал можно определить как
Чтобы обеспечить кросс-совместимость, используйте
Все классы являются «классами нового стиля» в Python 3.
В Python 3.x все классы являются классами нового стиля ; при определении нового класса python неявно делает его наследуемым от object . Таким образом, указание object в определении class является полностью необязательным:
Оба этих класса теперь содержат object в своем mro (порядок разрешения метода):
В классах Python 2.x по умолчанию используются классы старого стиля; они не неявно наследуют от object . Это заставляет семантику классов различаться в зависимости от того, если мы явно добавляем object в качестве базового class :
В этом случае, если мы попытаемся напечатать __mro__ of Y , __mro__ аналогичный вывод, как в случае с Python 3.x :
Это происходит потому, что мы явно наследовали Y от объекта при его определении: class Y(object): pass . Для класса X который не наследуется от объекта, атрибут __mro__ не существует, попытка доступа к нему приводит к __mro__ AttributeError .
Чтобы обеспечить совместимость между обеими версиями Python, классы могут быть определены с object в качестве базового класса:
В качестве альтернативы, если переменная __metaclass__ задана для type в глобальной области видимости, все последующие классы в данном модуле являются неявно новыми, не требуя явно наследовать от object :
Удаленные операторы <> и «, синонимичные с! = И repr ()
В Python 2 <> является синонимом для != ; Аналогично, `foo` является синонимом для repr(foo) .
кодирование / декодирование в hex больше недоступно
Однако, как было предложено сообщением об ошибке, вы можете использовать модуль codecs для достижения того же результата:
Обратите внимание, что codecs.encode возвращает объект bytes . Чтобы получить объект str просто decode в ASCII:
Функция cmp удалена в Python 3
В Python 3 была удалена встроенная функция cmp вместе со специальным методом __cmp__ .
Функция cmp() следует рассматривать как ушедшую, а специальный метод __cmp__() больше не поддерживается. Используйте __lt__() для сортировки __eq__() с __hash__() и другими богатыми сравнениями по мере необходимости. (Если вам действительно нужна функция cmp() , вы можете использовать выражение (a > b) — (a < b) как эквивалент для cmp(a, b) .)
Более того, все встроенные функции, которые принимают параметр cmp теперь принимают только параметр ключевого key слова.
В functools модуле есть также полезная функция cmp_to_key(func) , которая позволяет конвертировать из cmp функции -style к key -style функции:
Преобразуйте функцию сравнения старого стиля в ключевую функцию. Используется с инструментами, которые принимают ключевые функции (такие как sorted() , min() , max() , heapq.nlargest() , heapq.nsmallest() , itertools.groupby() ). Эта функция в основном используется в качестве инструмента перехода для программ, преобразованных из Python 2, которые поддерживают использование функций сравнения.
Исключенные переменные в понимании списка
Как видно из примера, в Python 2 значение x просочилось: оно замаскировало hello world! и распечатал U , так как это было последним значением x когда цикл закончился.
Тем не менее, в Python 3 x печатает первоначально определенный hello world! , так как локальная переменная из понимания списка не маскирует переменные из окружающей области.
Кроме того, ни одно из выражений генератора (доступно в Python начиная с версии 2.5), а также словарные или установочные представления (которые были переданы Python 2.7 из Python 3) в Python 2.
Обратите внимание, что в обоих Python 2 и Python 3 переменные будут просачиваться в окружающую область при использовании цикла for:
карта()
map() является встроенным, который полезен для применения функции к элементам итерации. В Python 2 map возвращает список. В Python 3 map возвращает объект карты , который является генератором.
В Python 2 вы можете передать None чтобы служить функцией идентификации. Это больше не работает в Python 3.
Более того, при передаче более одного итерабельного аргумента в Python 2, накладки map короче итераций с None (аналогично itertools.izip_longest ). В Python 3 итерация останавливается после кратчайшего повторения.
Примечание . Вместо map рассмотрите использование списков, совместимых с Python 2/3. Замена map(str, [1, 2, 3, 4, 5]) :
filter (), map () и zip () возвращают итераторы вместо последовательностей
В filter Python 2, встроенные функции map и zip возвращают последовательность. map и zip всегда возвращают список, а с filter тип возврата зависит от типа заданного параметра:
В Python 3 filter , map и zip обратный итератор вместо:
Поскольку Python 2 itertools.izip эквивалентен Python 3, zip izip был удален на Python 3.
Абсолютный / относительный импорт
В Python 3 PEP 404 изменяет способ работы импорта с Python 2. Имплицитный относительный импорт больше не разрешен в пакетах и from . import * импорт разрешен только в модульном уровне кода.
Чтобы достичь поведения Python 3 в Python 2:
- функция абсолютного импорта может быть включена с from __future__ import absolute_import
- явный относительный импорт поощряется вместо имплицитного относительного импорта
Для пояснения в Python 2 модуль может импортировать содержимое другого модуля, расположенного в том же каталоге, что и ниже:
Обратите внимание, что местоположение foo неоднозначно из оператора импорта. Этот тип неявного относительного импорта, таким образом, обескуражен в пользу явного относительного импорта , который выглядит следующим образом:
Точка . позволяет явное объявление местоположения модуля в дереве каталогов.
Подробнее о относительном импорте
Рассмотрим некоторый пользовательский пакет, называемый shapes . Структура каталогов выглядит следующим образом:
circle.py , square.py и triangle.py все import util.py в качестве модуля. Как они будут ссылаться на модуль на одном уровне?
. используется для относительного импорта на уровне одного уровня.
Теперь рассмотрим альтернативный макет модуля shapes :
Теперь, как эти 3 класса ссылаются на util.py?
The .. используется для относительного импорта родительского уровня. Добавить еще . s с количеством уровней между родительским и дочерним.
Файловый ввод-вывод
file больше не является встроенным именем в 3.x ( open still works).
Внутренние данные ввода-вывода файлов были перенесены в стандартный библиотечный модуль io , который также является новым домом для StringIO :
Режим файла (text vs binary) теперь определяет тип данных, полученных при чтении файла (и типа, необходимого для записи):
Кодировка для текстовых файлов по умолчанию соответствует тому, что возвращается locale.getpreferredencoding(False) . Чтобы явно указать кодировку, используйте параметр ключевого слова для encoding :
Функция round () для циклического размыкания и возврата
раунд ()
В Python 2, используя round() для числа, равного близкому к двум целым числам, вернется один из самых удаленных от 0. Например:
Однако в Python 3 round() вернет четное целое число (например , округление банкиров ). Например:
Функция round () следует стратегии от половины до четного округления , которая будет округлять половинные числа до ближайшего четного целого числа (например, round(2.5) теперь возвращает 2, а не 3.0).
Согласно ссылке в Википедии , это также известно как беспристрастное округление , округленное округление , округление статистики , голландское округление , гауссово округление или нечетное округление .
Половина и округление — это часть стандарта IEEE 754, а также режим округления по умолчанию в Microsoft .NET.
Эта стратегия округления имеет тенденцию уменьшать общую ошибку округления. Так как в среднем количество округленных чисел совпадает с количеством округленных чисел, ошибки округления сокращаются. Другие методы округления вместо этого имеют тенденцию иметь отклонение вверх или вниз в средней ошибке.
круглый () тип возврата
Функция round() возвращает тип float в Python 2.7
Начиная с Python 3.0, если второй аргумент (число цифр) опущен, он возвращает int .
Верно, ложно и нет
В Python 2, True , False и None есть встроенные константы. Это означает, что можно переназначить их.
Вы не можете сделать это с помощью None с Python 2.4.
В Python 3, True , False и None теперь используются ключевые слова.
Возвращаемое значение при записи в файл-объект
В Python 2 запись непосредственно в дескриптор файла возвращает None :
В Python 3 запись в дескриптор возвращает количество символов, записанных при записи текста, и количество байтов, записанных при написании байтов:
long vs. int
В Python 2 любое целое число, большее, чем C ssize_t , будет преобразовано в long тип данных, обозначенный суффиксом L в литерале. Например, в 32-битной сборке Python:
Однако в Python 3 был удален long тип данных; независимо от того, насколько велика целое число, это будет int .
Класс Boolean Value
В Python 2, если вы хотите самостоятельно определить логическое значение класса, вам необходимо реализовать метод __nonzero__ в вашем классе. Значение по умолчанию равно True.
Базовый ввод, вывод и форматирование строки в Python
Базовый ввод, вывод и форматирование строки в Python
Чтобы быть полезной, программе обычно необходимо общаться с внешним миром, получая входные данные от пользователя и отображая данные результатов обратно пользователю. Этот урок познакомит вас с вводом и выводом Python.
Ввод может осуществляться непосредственно от пользователя через клавиатуру или из какого-либо внешнего источника, такого как файл или база данных. Вывод может быть отображен непосредственно на консоль или IDE, на экран через графический интерфейс пользователя (GUI) или снова на внешний источник.
В previous tutorial в этой вводной серии вы:
Увидел сравнение некоторых различных парадигм, используемых языками программирования для реализации определенной итерации
Узнали об итераторах и итераторах, двух концепциях, которые составляют основу определенной итерации в Python
Связали все вместе, чтобы узнать о циклах Python для for
Без лишних слов, давайте окунемся!
Чтение ввода с клавиатуры
Программы часто должны получать данные от пользователя, обычно путем ввода с клавиатуры. Самый простой способ сделать это в Python — с помощью + input () + .
+ Входной сигнал ([<запрос>]) +
_ Читает строку ввода с клавиатуры. _
+ input () + приостанавливает выполнение программы, чтобы позволить пользователю вводить строку ввода с клавиатуры. Когда пользователь нажимает клавишу [.keys] # Enter #, все набранные символы читаются и возвращаются в виде строки:
Обратите внимание, что новая строка, сгенерированная, когда пользователь нажимает клавишу [.keys] # Enter #, не включена в возвращаемую строку.
Если вы включите необязательный аргумент + <prompt> + , + input () + отобразит его как приглашение для пользователя, прежде чем приостановить чтение ввода:
+ input () + всегда возвращает строку. Если вам нужен числовой тип, вам нужно преобразовать строку в соответствующий тип с помощью встроенных функций + int () + , + float () + или + complex () + ):
В приведенном выше примере выражение + n + 100 + в строке 3 недопустимо, поскольку + n + является строкой, а + 100 + является целым числом. Строка 8 преобразует + n + в целое число, поэтому оператор + print () + в строке 10 завершается успешно.
+ raw_input () + в Python 2 считывает ввод с клавиатуры и возвращает его. + raw_input () + в Python 2 ведет себя так же, как + input () + в Python 3, как описано выше.
Но в Python 2 также есть функция с именем + input () + . В Python 2 + input () + читает ввод с клавиатуры, parses и оценивает его как выражение Python, а затем возвращает полученное значение.
Python 3 не предоставляет единственной функции, которая делает именно то, что Python 2 + input () + делает. Эффект можно имитировать в Python 3 с помощью выражения + eval (input ()) + . Однако это считается угрозой безопасности, поскольку позволяет пользователю запускать произвольный, потенциально вредоносный код.
См. Https://docs.python.org/3/library/functions.html#eval[Python документацию] для получения дополнительной информации о + eval () + и https://en.wikipedia.org/wiki./Eval [Wikipedia + eval + page] для обсуждения потенциальных угроз безопасности.
Запись вывода на консоль
В дополнение к получению данных от пользователя, программа также обычно должна представлять данные обратно пользователю. Вы можете отобразить данные программы на консоли в Python с помощью + print () + .
Неформатированный консольный вывод
Чтобы отобразить объекты на консоли, передайте их как разделенный запятыми список аргументов в + print () + .
_ Отображает строковое представление каждого + <obj> + на консоли. _
По умолчанию + print () + отделяет каждый объект одним пробелом и добавляет новую строку в конец вывода:
Любой тип объекта может быть указан в качестве аргумента + print () + . Если объект не является строкой, то + print () + преобразует его в соответствующее строковое представление, отображающее его:
Как видите, даже сложные типы, такие как списки, словари и функции, могут отображаться на консоли с помощью + print () + .
Ключевое слово Аргументы в + print () +
+ print () + принимает несколько дополнительных аргументов, которые обеспечивают скромный контроль над форматом вывода. Каждый из них представляет собой специальный тип аргумента, называемый ключевое слово аргумента . Эта вводная серия руководств будет включать в себя учебник по функциям и передаче параметров, чтобы вы могли больше узнать об аргументах ключевых слов.
А сейчас вот что вам нужно знать:
Аргументы ключевых слов имеют вид + <ключевое слово> = <значение> + . *Любые ключевые аргументы, передаваемые в + print () + , должны заканчиваться после списка отображаемых объектов.
В следующих разделах вы увидите, как эти ключевые аргументы влияют на вывод консоли, создаваемый + print () + .
+ Sep = + Аргумент ключевого слова
Добавление ключевого аргумента + sep = <str> + приводит к разделению объектов строкой + <str> + вместо единственного пробела по умолчанию:
Чтобы объединить объекты без пробелов между ними, укажите + sep = » + :
Вы можете указать любую произвольную строку в качестве разделителя с помощью ключевого слова + sep = + .
+ End = + Аргумент ключевого слова
Ключевое слово аргумент + end = <str> + заставляет вывод завершаться символом + <str> + вместо новой строки по умолчанию:
Например, если вы отображаете значения в цикле, вы можете использовать + end = + , чтобы значения отображались в одной строке, а не в отдельных строках:
Любая строка может быть указана как выходной терминатор с ключевым словом + end = + .
Аргументы ключевого слова выходного потока
+ print () + принимает два дополнительных ключевых аргумента, оба из которых влияют на обработку выходного потока:
Эти два ключевых аргумента представлены здесь для полноты картины. Возможно, вам не нужно слишком беспокоиться о выходных потоках на этом этапе. Они обсуждаются позже в этой серии в руководстве по File I/O.
Форматированный строковый вывод
+ print () + поддерживает форматирование вывода консоли, которое в лучшем случае является элементарным. Вы можете выбрать способ разделения печатных объектов и указать, что идет в конце печатной строки. Это об этом.
Во многих случаях вам потребуется более точный контроль над отображением данных, предназначенных для отображения. Python предоставляет несколько способов форматирования выходных строковых данных. В этом разделе вы узнаете об одном из старых: оператор* string modulo *.
В последних версиях Python появились более новые способы форматирования строковых данных, которые, возможно, превосходят оператор строкового модуля: string + .format () + method и f-strings . Вы узнаете об этом в следующем уроке этой серии. Вы также можете проверить эти статьи:
Хотя доступны и другие параметры форматирования, оператор строкового модуля по-прежнему широко используется. Если вы читаете существующий код Python, вы, вероятно, столкнетесь со строковым оператором по модулю, поэтому будет полезно ознакомиться с ним.
С другой стороны, если вы не знакомы с + printf () + , не беспокойтесь! Следующее должно иметь смысл.
Строковый оператор по модулю
Оператор modulo ( +% + ) обычно используется с числами, и в этом случае он вычисляет остаток от деления:
Для строковых операндов оператор по модулю выполняет совершенно другую функцию: форматирование строки. (Эти две операции не очень похожи друг на друга. Они имеют одинаковое имя, потому что они представлены одним и тем же символом: +% + .)
Вот как выглядит синтаксис строкового оператора по модулю:
В левой части оператора +% + , + <format_string> + — строка, содержащая один или несколько спецификаторов преобразования. + <Values> + справа вставляется в + <format_string> + вместо спецификаторов преобразования. Результирующая форматированная строка является значением выражения.
Давайте начнем с примера. Вот оператор + print () + , который отображает отформатированную строку, используя оператор строки по модулю:
Помимо представления самой строковой операции по модулю, символ + ‘%’ + также обозначает спецификаторы преобразования в строке формата — в этом случае + ‘% d’ + , + ‘% s’ + и + ‘%. 2f’ + .
В выходных данных каждый элемент из набора значений преобразуется в строковое значение и вставляется в строку формата вместо соответствующего спецификатора преобразования:
Первым элементом в кортеже является + 6 + , числовое значение, которое заменяет + ‘% d’ + в строке формата.
Следующим элементом является строковое значение + ‘bananas’ + , которое заменяет + ‘% s’ + .
Последний элемент — это значение с плавающей точкой + 1.74 + , которое заменяет + ‘%. 2f’ + .
В результате получается строка «+6 бананов стоимостью $ 1,74 +», как показано на следующей диаграмме:
Если нужно вставить несколько значений, они должны быть заключены в кортеж, как показано выше. Если есть только одно значение, оно может появиться само по себе:
Также обратите внимание, что строковая операция по модулю не только для печати. Вы также можете отформатировать значения и присвоить их другой строковой переменной:
(Опять же, если вы знакомы с функциями, связанными с + printf () + , то это напоминает + sprintf () + . Если нет, то не переживайте.)
Спецификаторы конверсии
Спецификаторы преобразования появляются в + <format_string> + и определяют, как значения форматируются, когда они вставляются.
Спецификатор преобразования начинается с символа +% + и состоит из следующих компонентов:
+% + и + <тип> + обязательны. Остальные компоненты, показанные в квадратных скобках, не являются обязательными.
В следующей таблице приведены действия каждого компонента спецификатора преобразования:
Introduces the conversion specifier
Indicates one or more flags that exert finer control over formatting
Specifies the minimum width of the formatted result
Determines the length and precision of floating point or string output
Indicates the type of conversion to be performed
Читайте дальше, чтобы узнать, как это работает.
Тип конверсии
Тип преобразования + <тип> + является последним компонентом спецификатора преобразования:
Ввод, вывод и импорт в Python
В Python существует множество встроенных функций — использовать их можно прямо в командной строке.
Функции вроде input() и print() используются для операций ввода и вывода. Подробное знакомство мы начнем с вывода.
Вывод
Функция print() используется для вывода данных на экран. Эти данные мы можем записать и в файл, но об этом мы поговорим позже.
Пример использования
Вывод:
Еще один пример
Вывод:
Как вы могли заметить, во втором примере с print() между строкой и переменной стоит пробел. По умолчанию вывод происходит именно так, но это можно изменить.
Синтаксис
- objects — то, что мы хотим вывести.
- sep — разделитель между переменными. По умолчанию это пробел.
- end — то, чем кончается строка. По умолчанию это переход на новую строку.
- file — объект, указывающий, куда нужно производить вывод. По умолчанию данные выводятся на экран — sys.stdout .
Примеры
Вывод:
Форматирование вывода
Иногда нужно отформатировать вывод, чтобы он выглядел соответствующим образом. Для этого есть метод str.format() . Его можно использовать с любым строковым объектом.
Фигурные скобки <> здесь выступают в виде заполнителей. Порядок их вывода можно изменять с помощью индексов (индексов кортежа).
Вывод:
Для форматирования строк можно использовать именованные аргументы.
Для форматирования строк можно использовать sprintf() . Это старый способ, который использовался еще в языке Си. Для этого используется оператор % .
До этого момента все наши программы были статическими — все значения переменных мы объявляли заранее.
Теперь нам нужно больше свободы — возможно, мы захотим получить данные от пользователя. В Python для этих целей существует функция input() . Ее синтаксис выглядит так:
promt — это строка, которую мы хотим вывести на экран.
Как можно заметить, введенное значение — строка, а не число. Преобразовать это значение в число можно с помощью функции int() или float() .
Эту же операцию можно выполнить с помощью функции eval() . У eval() есть преимущество — эта функция может проводить расчеты даже если в качестве аргумента передана строка.
Импорт модулей
По мере количества строк вашего кода будет не лишним начинать пользоваться модулями.
Модуль — это файл, содержащий функции и операторы. У всех модулей в Python есть имя, которое заканчивается расширением .py .
Операторы внутри модуля могут быть импортированы в другой модуль или в интерпретатор Python. Для этого мы используем ключевое слово import .
Например, мы можем импортировать модуль math . Делается это следующим образом:
Пример использованияя
Вывод:
Теперь все переменные внутри модуля math доступны в нашей программе. Можно импортировать и только определенные функции и переменные из модуля.