автордың кітабын онлайн тегін оқу Python. Лучшие практики и инструменты
Научный редактор С. Бычковский
Переводчик С. Черников
Литературный редактор Н. Хлебина
Художник В. Мостипан
Корректоры О. Андриевич, Е. Павлович
Михал Яворски, Тарек Зиаде
Python. Лучшие практики и инструменты . — СПб.: Питер, 2021.
ISBN 978-5-4461-1589-1
© ООО Издательство "Питер", 2021
Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав.
Благодарю любимую жену Оливию за ее любовь, вдохновение и бесконечное терпение,
моих верных друзей Петра, Дэниела и Павла за их поддержку,
мою мать, открывшую предо мной удивительный мир программирования.
Михал Яворски
О создателях книги
Об авторах
Михал Яворски — программист на Python с десятилетним опытом. Занимал разные должности в различных компаниях: от обычного фулстек-разработчика, затем архитектора программного обеспечения и, наконец, до вице-президента по разработке в динамично развивающейся стартап-компании. В настоящее время Михал — старший бэкенд-инженер в Showpad. Имеет большой опыт в разработке высокопроизводительных распределенных сервисов. Кроме того, является активным участником многих проектов Python с открытым исходным кодом.
Тарек Зиаде — Python-разработчик. Живет в сельской местности недалеко от города Дижон во Франции. Работает в Mozilla, в команде, отвечающей за сервисы. Тарек основал французскую группу пользователей Python (называется Afpy) и написал несколько книг о Python на французском и английском языках. В свободное от хакинга и тусовок время занимается любимыми хобби: бегом или игрой на трубе.
Вы можете посетить его личный блог (Fetchez le Python) и подписаться на него в Twitter (tarek_ziade).
О научном редакторе
Коди Джексон — кандидат наук, основатель компании Socius Consulting, работающей в сфере IT и консалтинга по управлению бизнесом в Сан-Антонио, а также соучредитель Top Men Technologies. В настоящее время работает в CACI International ведущим инженером по моделированию ICS/SCADA. В IT-индустрии с 1994 года, еще со времен службы в ВМФ в качестве ядерного химика и радиотехника. До CACI он работал в университете в ECPI в должности ассистента профессора по компьютерным информационным системам. Выучился программированию на Python самостоятельно, написал книги Learning to Program Using Python и Secret Recipes of the PythonNinja.
Предисловие
Python — динамический язык программирования, применимый в широком спектре задач благодаря своей простой, но мощной сути. Писать на Python легко, но сделать код удобочитаемым, универсальным и простым в сопровождении — сложно. В третьем издании данной книги вы ознакомитесь с практическими рекомендациями, полезными инструментами и стандартами, используемыми профессиональными разработчиками на Python, так что сумеете преодолеть данную проблему.
Мы начнем эту книгу с новых возможностей, добавленных в Python 3.7. Изучим синтаксис Python и рассмотрим, как применять самые современные концепции и механизмы объектно-ориентированного программирования. Помимо этого, исследуем различные подходы к реализации метапрограммирования. Данная книга расскажет о присваивании имен при написании пакетов, создании исполняемых файлов, а также о применении мощных инструментов, таких как buildout и virtualenv, для развертывания кода на удаленных серверах. Вы узнаете, как создавать полезные расширения Python на языках C, C++, Cython и Pyrex. Кроме того, чтобы писать чистый код, вам будет полезно изучить инструменты управления кодом, написания ясной документации и разработки через тестирование.
Изучив эту книгу, вы станете экспертом в написании эффективного и удобного в сопровождении кода на Python.
Для кого эта книга
Книга написана для разработчиков на Python, желающих продвинуться в освоении этого языка. Под разработчиками мы имеем в виду в основном программистов, которые зарабатывают на жизнь программированием на Python. Дело в том, что книга сосредоточена на средствах и методах, наиболее важных для создания производительного, надежного и удобного в сопровождении программного обеспечения на Python.
Это не значит, что в книге нет ничего интересного для любителей. Она отлично подойдет для тех, кто хочет выйти на новый уровень в изучении Python. Базовых навыков языка будет достаточно, чтобы понять изложенный материал, хотя менее опытным программистам придется приложить некоторые усилия. Книга также будет хорошим введением в Python 3.7 для тех, кто слегка отстал от жизни и пользуется версией Python 2.7 или еще более ранней.
Наибольшую пользу данная книга принесет веб- и бэкенд-разработчикам, поскольку в ней представлены две темы, особенно важные именно для этих специалистов: надежное развертывание кода и параллелизм.
Что мы рассмотрим
В главе 1 описано текущее состояние Python и его сообщества. Мы увидим, как меняется язык, из-за чего это происходит и почему данные факты очень важны для тех, кто хочет называть себя профессионалом в Python. Мы также рассмотрим наиболее известные и канонические способы работы с кодом Python, а именно популярные инструменты обеспечения производительности и правила, которые сегодня фактически являются стандартами.
В главе 2 представлены современные способы создания повторяемых и последовательных сред разработки для программистов на Python. Мы сосредоточимся на двух популярных инструментах для изоляции среды: средах типа virtualenv и контейнерах Docker.
В главе 3 даны практические рекомендации по написанию кода на Python (идиомы языка), а также краткое описание отдельных элементов синтаксиса Python, которые могут оказаться новыми для программистов, более привыкших к старым версиям Python. Кроме того, мы изложим пару полезных идей о внутренних реализациях типа CPython и их вычислительной сложности в качестве обоснования для рассмотренных идиом.
В главе 4 рассмотрены более сложные концепции и механизмы объектно-ориентированного программирования, доступные в Python.
В главе 5 представлен обзор общих подходов к метапрограммированию для программистов на Python.
В главе 6 приведено руководство по наиболее общепринятому стилю написания кода на Python (PEP 8), а также указано, когда и почему разработчики должны соблюдать его. Вдобавок мы рассмотрим некоторые общие рекомендации по назначению имен.
В главе 7 описаны особенности создания пакетов на Python и даны рекомендации по созданию пакетов, распространяемых в виде открытого исходного кода в каталоге пакетов Python (Python Package Index, PyPI). Мы также рассмотрим тему, которую часто игнорируют, — исполняемые файлы.
В главе 8 представлены некоторые облегченные инструменты для развертывания кода Python на удаленных серверах. Развертывание — это одна из областей, где Python предстает во всей красе в реализации бэкенда для веб-сервисов и приложений.
В главе 9 объясняется, почему иногда удобно добавлять в код расширения на C и C++, и показывается, что при наличии подходящих инструментов сделать это будет проще, чем кажется.
В главе 10 рассказывается, как правильно управлять кодовой базой и почему следует использовать систему управления версиями. Мы опробуем на деле возможности такой системы (а именно, Git) в осуществлении непрерывных процессов, таких как непрерывная интеграция и непрерывная доставка.
В главе 11 приводятся общие правила написания технической документации, применимые к программному обеспечению, написанному на любом языке, а также различные инструменты, которые будут особенно полезны при создании документации для вашего кода на Python.
В главе 12 представлены плюсы подхода «разработка через тестирование» и подробно рассказывается о том, как использовать популярные инструменты Python для тестирования.
В главе 13 обсуждаются базовые правила оптимизации, которые должен знать каждый разработчик. Мы также научимся выявлять узкие места в производительности приложений и использовать общие инструменты профилирования.
В главе 14 показывается, как применить эти знания так, чтобы ваше приложение действительно работало быстрее или эффективнее с точки зрения используемых ресурсов.
В главе 15 объясняется, как реализовать параллелизм на Python с помощью различных подходов и готовых библиотек.
В главе 16 рассказывается, что такое событийно-ориентированное и сигнальное программирование и как оно связано с асинхронным и различными моделями параллелизма. Мы представим разные подходы к событийному программированию, доступные программистам на Python, а также полезные библиотеки, позволяющие применять эти шаблоны.
В главе 17 описаны несколько полезных паттернов проектирования и примеры их реализации на Python.
Приложение содержит краткое руководство по использованию языка разметки reStructuredText.
Как получить максимум от этой книги
Данная книга написана для программистов, работающих в любой операционной системе, где установлен Python 3.
Издание не подходит для начинающих, поэтому мы предполагаем, что вы установили Python в своей среде или вы знаете, как это сделать. Однако в книге учитывается тот факт, что не все могут знать о последних функциях Python или официально рекомендованных инструментах. Именно поэтому в первой главе приведен обзор наиболее часто используемых утилит (например, виртуальных сред и pip), которые в настоящее время профессиональные разработчики на Python считают стандартными инструментами.
Скачивание файлов с примерами кода
Вы можете скачать файлы примеров кода для этой книги на сайте github.com/PacktPublishing/Expert-Python-Programming-Third-Edition.
Чтобы скачать файлы кода, выполните следующие действия.
1. Перейдите по указанной ссылке на сайт github.com.
2. Нажмите кнопку Clone or Download.
3. Щелкните кнопкой мыши на ссылке Download ZIP.
4. Скачайте архив с файлами примеров.
После скачивания файла распакуйте его с помощью последней версии одной из следующих программ:
• WinRAR/7-Zip для Windows;
• Zipeg/iZip/UnRarX для Mac;
• 7-Zip/PeaZip для Linux.
Скачивание цветных изображений
Мы также выложили оригинальный файл PDF, в котором приведены цветные изображения снимков экрана/схем, используемых в этой книге. Вы можете скачать его по ссылке www.packtpub.com/sites/default/files/downloads/9781789808896_ColorImages.pdf.
Условные обозначения
В этой книге используется ряд текстовых и символьных обозначений.
Код в тексте: такой формат обозначает кодовые слова в тексте, имена таблиц базы данных, имена папок и файлов, расширения файлов, пути к файлам, URL-адреса, пользовательский ввод и инструменты Twitter. Например: «Любая попытка запустить код, в котором есть такие проблемы, заставит интерпретатор завершить работу, выбросив исключение SyntaxError».
Блок кода выглядит следующим образом:
print("hello world")
print "goodbye python2"
Любой ввод или вывод из командной строки записывается так:
$ Python3 script.py
Новые термины и важные слова выделены курсивом.
Так помечаются предупреждения и важные примечания.
А так — советы и секреты.
От издательства
Ваши замечания, предложения, вопросы отправляйте по адресу comp@piter.com (издательство «Питер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
На веб-сайте издательства www.piter.com вы найдете подробную информацию о наших книгах.
Часть I. Перед началом работы
Эта часть призвана помочь пользователю подготовиться к современным реалиям разработки на Python. Мы рассмотрим, как язык изменился за последние несколько лет и какими инструментами разработки пользуются современные программисты на Python.
1. Текущее состояние Python
Python удивителен.
На протяжении долгого времени одним из самых важных достоинств Python была его совместимость. Независимо от того, какую операционную систему используете вы или ваши клиенты, если у нее есть интерпретатор Python, то ваше написанное на Python ПО будет в ней работать. И что важнее всего — работать так, как нужно. Однако сейчас этим никого не удивишь. Современные языки, например Ruby и Java, предоставляют аналогичные возможности для взаимодействия. Но в наше время совместимость не самое важное качество языка программирования. С появлением облачных вычислений, веб-приложений и надежного программного обеспечения для создания виртуальных окружений вопросы совместимости и независимости от операционной системы отошли на второй план. Однако по-прежнему важны инструменты, позволяющие программистам эффективно писать надежное и удобное в сопровождении ПО. К счастью, Python относится к тем языкам, благодаря которым программисты могут работать наиболее эффективно, и для развития компаний это лучший выбор.
Python так долго не теряет актуальности благодаря тому, что постоянно развивается. Эта книга ориентирована на последнюю версию Python 3.7, и все примеры кода написаны именно в ней, если не сказано иное. Поскольку Python имеет очень длинную историю и еще есть программисты, пишущие на Python 2, данная книга начинается с обзора текущего статус-кво Python 3. В этой главе вы узнаете, как и почему Python изменился и как писать программное обеспечение, совместимое и со старыми, и с последними версиями Python.
В этой главе:
• где мы находимся и куда движемся;
• почему и как изменился язык Python;
• как не отставать от изменений в документации PEP;
• принятие Python 3 на момент написания этой книги;
• основные различия между Python 3 и Python 2;
• не только CPython;
• полезные ресурсы.
Технические требования
Для этой главы скачать последнюю версию Python можно по ссылке www.python.org/downloads/.
Альтернативные реализации интерпретатора Python можно найти на следующих сайтах:
• Stackless Python: github.com/stackless-dev/stackless;
• PyPy: pypy.org;
• Jython: www.jython.org;
• IronPython: ironpython.net;
• MicroPython: micropython.org.
Файлы с примерами кода для этой главы можно найти по ссылке github.com/PacktPublishing/Expert-Python-Programming-Third-Edition/tree/master/chapter1.
Где мы находимся и куда движемся
История Python началась где-то в конце 1980-х годов, но релиз версии 1.0 состоялся в 1994 году. То есть это не молодой язык. Мы бы могли пройтись по всей хронологии версий Python, однако на самом деле нас интересует только одна дата: 3 декабря 2008 года — выход Python 3.0.
На момент написания этой книги прошло почти десять лет с появления первого релиза Python 3. Кроме того, прошло семь лет после выпуска PEP 404 — официального документа, в котором был отменен выпуск Python 2.8 и официально закрыта вся серия 2.x. С тех пор прошло много времени, однако сообщество Python все еще делится на два лагеря: несмотря на то что язык развивается очень быстро, есть большая группа пользователей, которые не хотят идти с ним в ногу.
Почему и как изменился язык Python
Ответ прост — Python изменяется, поскольку в этом есть необходимость. Конкуренты не спят. Каждые несколько месяцев из ниоткуда появляется новый язык, претендующий на решение всех проблем своих предшественников. Разработчики быстро утрачивают интерес к большинству подобных проектов, и их популярность часто вызвана исключительно хайпом.
За этим кроется более серьезная проблема. Люди берутся за разработку новых языков, поскольку считают, что существующие не решают их проблем. Было бы глупо отрицать необходимость в новых решениях. Кроме того, все более широкое использование Python показывает: язык можно и нужно улучшать.
Множество улучшений в Python обусловлены потребностями конкретных сфер, в которых он применяется. Наиболее значимая из них — веб-разработка. Так, постоянно растущий спрос на скорость и производительность в этой области привел к тому, что работа с параллелизмом в Python значительно упростилась.
Некоторые изменения вызваны попросту солидным возрастом и зрелостью проекта Python. На протяжении многих лет он обрастал множеством неорганизованных и избыточных модулей стандартных библиотек и даже плохими проектными решениями. То есть выпуск Python 3 был призван подчистить и освежить язык. К сожалению, время показало: данный план имел и неприятные последствия. В течение долгого времени Python 3 использовался многими разработчиками несерьезно. Будем надеяться, это изменится.
Как не отставать от изменений в документации PEP
Сообщество Python придумало устоявшийся способ реагирования на изменения. Хотя рискованные идеи языка Python в основном обсуждаются в рассылках (python-ideas@python.org), по-настоящему серьезные изменения сопровождаются выходом документа под названием Python Enhancement Proposal (PEP).
Это формализованный документ, в котором подробно описывается предложение об изменении Python. Он также является отправной точкой для обсуждения в сообществе. Вся цель, формат и рабочий процесс вокруг данных документов также стандартизированы в документе PEP 1 (www.python.org/dev/peps/pep-0001).
PEP-документация очень важна для Python и, в зависимости от темы, выполняет разные функции:
•информирования — приводит информацию, необходимую разработчикам ядра Python, и графики выпуска версий Python;
• стандартизации — содержит указания по стилю кода, документации или другие руководящие принципы;
• проектирования — описывает предлагаемые функции.
Список всех предлагаемых PEP приведен в постоянно обновляемом документе PEP 0 (www.python.org/dev/peps/). Найти их легко, а ссылку на них нетрудно сформировать самостоятельно, поэтому в книге мы будем называть их лишь по номерам.
Документ PEP 0 — важный источник информации для тех, кому интересно, в каком направления движется язык Python, но некогда отслеживать каждое обсуждение в рассылках Python. В PEP 0 показано, какие документы уже были приняты, но еще не реализованы, а какие находятся на рассмотрении.
Документы PEP выполняют и другие функции. Очень часто люди задают вопросы а-ля:
• «Почему функция А работает именно так?»;
• «Почему в Python нет функции Б?».
В большинстве случаев в конкретных документах PEP уже имеется развернутый ответ. Существует много PEP-документов с описанием возможностей языка Python, которые были предложены, но не приняты. Эти документы играют роль своего рода исторической справки.
Внедрение Python 3 на момент написания этой книги
Если в Python столько новых и интересных функций, то наверняка он хорошо принят в сообществе? Сложно сказать. Некогда популярная страница «Стена суперсил Python 3» (python3wos.appspot.com), на которой отслеживалась совместимость самых популярных пакетов и Python 3, изначально была названа «Стеной позора Python 3». Этот сайт больше не поддерживается, однако на момент последнего обновления от 22 апреля 2018 года видно, что 191 из 200 наиболее популярных пакетов Python были совместимы с Python 3. Таким образом, можно убедиться, что эту версию хорошо приняли в сообществе программистов Python с открытым исходным кодом. Тем не менее это не значит, что все команды программистов полностью перешли на Python 3. По крайней мере, коль скоро большинство популярных пакетов Python доступны в Python 3, отговорки наподобие «то, чем мы пользуемся, еще не портировали» уже не актуальны.
Основная причина такой ситуации заключается в том, что портирование существующего приложения с Python 2 на Python 3 — всегда сложная задача. Есть инструменты, такие как 2to3, позволяющие выполнять автоматизированный перевод кода, но не гарантирующие, что результат будет правильным на 100 %. Кроме того, такой «переведенный» код может утратить производительность, если не прибегнуть к ручной регулировке. Перевод существующего сложного кода на Python 3 может повлечь огромные усилия и расходы, которые могут себе позволить не все организации. К счастью, подобные расходы можно распределить во времени. Некоторые хорошие методологии проектирования архитектуры программного обеспечения, такие как сервис-ориентированная архитектура или микросервисы, дают возможность постепенно достичь этой цели. Новые компоненты проекта (сервисы или микросервисы) можно писать по новой технологии, а существующие — портировать по одному.
В перспективе переход на Python 3 может иметь только положительные последствия для проекта. Согласно PEP 404 поддержка Python 2 закончилась в 2020 году. До этого времени мы можем ожидать только обновления версии патча, решающего проблемы безопасности, но ничего более. Кроме того, в будущем, возможно, настанет время, когда все крупные проекты, такие как Django, Flask и NumPy, отключат совместимость с 2.x и полностью перейдут на Python 3. В Django уже сделали этот шаг, и, начиная с версии 2.0.0, он больше не поддерживает Python 2.7.
Наше мнение по данному вопросу противоречиво. Мы думаем, что лучшим стимулом для отказа сообщества от Python 2 будет прекращение поддержки Python 2 при создании новых пакетов. Конечно, это ограничивает создание нового программного обеспечения, но, возможно, послужит единственно верным способом изменить мышление тех, кто никак не отвыкнет от Python 2.x.
Мы рассмотрим основные различия между Python 3 и Python 2 в следующем разделе.
Основные различия между Python 3 и Python 2
Как уже было сказано, в Python 3 нет обратной совместимости с Python 2 на уровне синтаксиса. Однако не все так плохо. Кроме того, не каждый модуль Python, написанный под версию 2.x, перестает работать в Python 3. Можно писать полностью кросс-совместимый код, который будет работать в обеих версиях без дополнительных инструментов или методов, но обычно это возможно только для простых приложений.
Почему это должно нас волновать
Несмотря на наше мнение о совместимости Python 2, которое мы высказали ранее в данной главе, нельзя просто взять и забыть об этой проблеме. Есть несколько действительно полезных пакетов, но в ближайшее время они вряд ли будут портированы.
Кроме того, иногда ограничения исходят от организации, в которой мы работаем. Уже имеющийся код может быть настолько сложным, что портировать его экономически нецелесообразно. Таким образом, даже если мы решили двигаться дальше и с этого момента пользоваться исключительно Python 3, сразу полностью отказаться от Python 2 все равно будет невозможно.
В наши дни трудно назвать себя профессиональным разработчиком, если не вносить свой вклад в деятельность сообщества. Таким образом, помочь разработчикам, пишущим открытый исходный код, внедрить совместимость Python 3 с существующими пакетами — отличный способ погасить моральный долг, возникший в результате использования последних. Конечно, это невозможно сделать, не зная различий между Python 2 и Python 3. Кстати, это также отличное упражнение для новичков в Python 3.
Основные синтаксические различия и распространенные ошибки
Документация Python — лучшее место, где можно почитать о различиях между версиями Python. Тем не менее для удобства читателей здесь перечислены наиболее важные различия. Это не отменяет того факта, что документация обязательна к прочтению тому, кто еще не знаком с Python 3 (docs.python.org/3.0/whatsnew/3.0.html).
Важнейшие нововведения Python 3 можно разделить на три группы:
• изменения синтаксиса, в которых одни элементы синтаксиса были удалены/изменены, а другие — добавлены;
• изменения в стандартной библиотеке;
• изменения типов данных и коллекций.
Изменения синтаксиса
Изменения синтаксиса, затрудняющие запуск кода, обнаружить легче всего, ведь код просто не сможет выполняться. Код Python 3, в котором используются новые элементы синтаксиса, не будет работать на Python 2, и наоборот. Элементы, удаленные из официального синтаксиса, сделают код Python 2 явно несовместимым с Python 3. Любая попытка запустить подобный код немедленно приведет к сбою интерпретатора, вызывая исключение SyntaxError. Ниже представлен пример «сломанного» скрипта из двух команд, ни одна из которых не будет выполнена из-за ошибки синтаксиса:
print("hello world")
print "goodbye python2"
Результат запуска скрипта на Python 3 выглядит следующим образом:
$ python3 script.py
File "script.py", line 2
print "goodbye python2"
^
SyntaxError: Missing parentheses in call to 'print'
Если говорить о новых элементах синтаксиса Python 3, то на перечисление всех различий уйдет много времени, и в каждой версии Python 3.x могут снова появиться новые элементы синтаксиса, которые будут так же несовместимы с более ранними версиями Python (даже если это уже Python 3.x). Наиболее важные из них рассмотрены в главах 2 и 3, так что нет необходимости перечислять их все здесь.
Список вещей, которые работали в Python 2 и вызывали синтаксические или функциональные ошибки в Python 3, гораздо короче. Ниже представлены наиболее важные несовместимые изменения:
•print уже не оператор, а функция, поэтому скобки обязательны;
• указание исключений изменилось с exceptexc,var на exceptexcasvar;
• оператор сравнения <> был заменен на !=;
• frommoduleimport* (docs.python.org/3.0/reference/simple_stmts.html#import) уже допускается не только на уровне модуля и больше не допускается внутри функций;
• from.[module]importname — теперь единственный общепринятый синтаксис для относительного импорта. Весь импорт, не начинающийся с точки, интерпретируется как абсолютный;
• функция sorted() и метод списков sort() больше не принимают аргумент cmp, нужно использовать аргумент key;
• целочисленное деление на числа с плавающей точкой возвращает числа с плавающей точкой. Отсечение дробной части достигается за счет оператора //, например 1//2. С числами с плавающей точкой это также работает: 5.0//2.0==2.0.
Изменения в стандартной библиотеке
Критические изменения в стандартной библиотеке обнаружить чуть сложнее, чем изменения синтаксиса. В каждой последующей версии Python добавляются, улучшаются или полностью удаляются стандартные модули. Данный процесс был распространен и в старых релизах Python (1.x и 2.x), так что в Python 3 это не является чем-то из ряда вон. В большинстве случаев, в зависимости от модуля, который был удален или реорганизован (например, urlparse перемещен в urllib.parse), он будет вызывать исключения на время импорта только после интерпретации. Поэтому такие проблемы легко выявить. Чтобы быть уверенными, что будут обнаружены все подобные моменты, необходимо тестировать весь код. В некоторых случаях (например, при использовании лениво загруженных модулей) проблемы, обычно заметные во время импорта, не будут проявляться, пока какая-либо функция не обратится к «проблемному» модулю. Именно поэтому важно убедиться, что во время теста выполняется каждая строчка кода.
Лениво загруженные модули
Лениво загруженный модуль — это модуль, который не был загружен во время импорта. В Python операторы import могут быть включены в функции, поэтому импорт будет происходить при вызове функции, а не во время основного импорта. Иногда такая загрузка модулей может быть разумным решением, но в большинстве случаев это обходной путь для плохо разработанной конструкции модуля (например, чтобы избежать циклического импорта). Такой код считается «с душком», и подобных действий вообще следует избегать. Уважительной причины для ленивой загрузки модулей стандартной библиотеки нет. В хорошо структурированном коде весь импорт должен быть сгруппирован в верхней части модуля.
Изменения типов данных, коллекций, строковых литералов
Разница в том, как Python представляет типы данных и коллекции, особенно заметна и создает больше всего проблем, когда разработчик пытается сохранить совместимость или просто портирует существующий код на Python 3. В то время как несовместимый синтаксис или изменения стандартной библиотеки легко найти и часто легко исправить, изменения в коллекциях и типах бывают неочевидны или требуют большого объема монотонной работы. Перечень таких изменений будет весьма длинным, поэтому официальная документация — самый лучший справочник.
Тем не менее здесь мы поговорим о том, как в Python 3 рассматриваются строковые литералы, поскольку это одно из самых спорных изменений Python 3, несмотря на то что это очень хороший ход, прояснивший многое.
Все строковые литералы теперь имеют кодировку Unicode, а у литералов bytestring должен быть префикс b или B. В Python 3.0 и 3.1 старый префикс Unicode U (например, u"foo") не принимается и вызывает синтаксическую ошибку. Отказ от него был основной причиной большинства споров. Стало очень трудно создать код, совместимый с различными ответвлениями Python, — в версии 2.x Python ссылался на эти префиксы при создании литералов Unicode. Данный префикс был возвращен обратно в Python 3.3, чтобы облегчить процесс интеграции, хотя в настоящее время в этом нет какого-либо синтаксического смысла.
Популярные инструменты и методы поддержания кросс-версионной совместимости
Поддержание совместимости между версиями Python — трудная задача. Она может добавить много дополнительной работы, в зависимости от размера проекта, но это тем не менее можно и нужно делать. Для пакетов, которые многократно используются во многих средах, это абсолютно необходимо. Пакеты с открытым исходным кодом без четко определенной и проверенной совместимости вряд ли станут популярны, да и сторонний код, применяемый в пределах компании, тоже будет полезно протестировать в различных средах.
Следует отметить, что, хоть эта глава сосредоточена в основном на совместимости между различными версиями Python, данные подходы применяются для поддержания совместимости с внешними зависимостями, такими как различные версии пакетов, бинарные библиотеки, системы или внешние сервисы.
Весь процесс можно разделить на три основных направления, расположенных в порядке их важности:
• определение и документирование целевой оценки совместимости и управления совместимостью;
• тестирование во всех средах и версиях, совместимость с которым была заявлена;
• реализация совместимости кода.
Заявление о том, что считается совместимым, — наиболее важная часть всего процесса, поскольку дает пользователям и разработчикам возможность иметь ожидания и делать предположения о работе кода и о том, как он может измениться в будущем. Наш код можно задействовать в качестве зависимости в различных проектах, в которых тоже будет внедрено управление совместимостью, поэтому способность понимать его поведение очень важна.
В этой книге мы всегда пытаемся предоставить несколько возможностей выбора и не давать абсолютных рекомендаций по конкретным вариантам, но здесь будет одно из немногих исключений. Лучший способ определить, каким образом совместимость может измениться в будущем, — использовать правильный подход к нумерации версий Semantic Versioning (semver) (semver.org). Это широко принятый стандарт маркировки изменений в коде версии с помощью всего лишь трех цифр. В нем также содержатся несколько советов о том, как работать с политикой устаревания. Вот выдержка из него (под лицензией Creative Commons — CC BY 3.0).
Допустим, номер версии приложения выглядит как MAJOR.MINOR.PATCH. Тогда прибавляем единицу к номеру:
1) MAJOR-версии, если вносятся изменения, делающие текущий код несовместимым с предыдущей версией;
2) MINOR-версии, если изменения вносятся вместе с обратной совместимостью;
3) PATCH-версии, если вы исправляете ошибки обратной совместимости.
Дополнительные отметки предварительных версий и метаданных могут быть дополнением к формату MAJOR.MINOR.PATCH.
Когда дело доходит до проверки совместимости кода с каждой заявленной версией и в любой среде (в нашем случае — версии Python), он должен быть проверен в каждой комбинации. Конечно, это почти невозможно, если у проекта много зависимостей, поскольку количество комбинаций быстро растет с каждой новой версией зависимости. Таким образом, как правило, ищут некий компромисс, чтобы на тестирование совместимости не уходило много времени. Подборка инструментов, призванная облегчить тестирование в так называемых матрицах, представлена в главе 12, в которой мы поговорим о процедуре тестирования в целом.
Преимущество использования проектов, которые следуют практике semver, заключается в том, что, как правило, тестировать нужно только крупные релизы, поскольку мелкие и патч-релизы гарантированно не имеют изменений без обратной совместимости. Конечно, это справедливо, только когда проект неукоснительно следует данной практике. К сожалению, ошибки случаются с каждым и несовместимые изменения возникают во многих проектах, даже в мелких патчах. Тем не менее нарушение объявленной semver строгой совместимости в мелких изменениях и патчах считается ошибкой, и ее нужно исправлять.
Реализация слоя совместимости — последний и наименее важный шаг процесса, если границы данной совместимости четко определены и тщательно протестированы. Тем не менее есть ряд инструментов и методов, которые должен знать каждый программист, занимающийся этим.
Основным является модуль __future__. Он портирует некоторые новые возможности в старые версии и принимает форму оператора импорта:
from __future__ import <feature>
Функции, предоставляемые оператором future, — это синтаксические элементы, которые не так уж легко обрабатывать различными способами. Данный оператор влияет только на тот модуль, где был использован. Ниже представлен пример интерактивной сессии Python 2.7, которая переносит литералы Unicode с Python 3.0:
Python 2.7.10 (default, May 23 2015, 09:40:32) [MSC v.1500 32 bit
(Intel)] on win32
Type "help", "copyright", "credits" or "license" for more
information.
>>> type("foo") # Старые литералы
<type 'str'>
>>> from __future__ import unicode_literals
>>> type("foo") # Теперь Unicode
<type 'unicode'>
Вот список всех доступных вариантов оператора __future__, которые должны знать разработчики, занимающиеся сопровождением:
•division — добавляет оператор деления Python 3 (PEP 238);
• absolute_import — заставляет каждую форму оператора import интерпретироваться «без точки», то есть как абсолютный импорт (PEP 328);
• print_function — заменяет оператор print функцией, то есть использование скобок становится обязательным (PEP 3112);
• unicode_literals — заставляет каждый строковый литерал интерпретироваться как литералы Unicode (PEP 3112).
Список всех доступных вариантов оператора __future__ невелик и охватывает лишь несколько свойств синтаксиса. Другие вещи, подвергшиеся изменению, например синтаксис функции metaclass (о ней мы поговорим в главе 5), поддерживать намного сложнее. Этот оператор также не поможет надежной работе с реорганизациями стандартных библиотек. К счастью, существуют инструменты, которые позволяют получить последовательный фрагмент готового к использованию совместимого кода. Наиболее известный — это Six (pypi.python.org/pypi/six/), который обеспечивает сопровождение как одиночный модуль. Другой перспективный, но чуть менее популярный инструмент — модуль future (python-future.org/).
Иногда разработчики могут не захотеть включать дополнительные зависимости в небольшие пакеты. Часто используется дополнительный модуль, обычно именуемый compat.py, который собирает весь код совместимости. Ниже представлен пример таких модулей из проекта python-gmaps (github.com/swistakm/python-gmaps):
# -*- coding: utf-8 -*-
"""Этот модуль обеспечивает совместимость
кода между разными версиями Python
"""
import sys
if sys.version_info < (3, 0, 0):
import urlparse # noqa
def is_string(s):
"""Возвращает True, если значение является строкой"""
return isinstance(s, basestring)
else:
# Примечание: urlparse перемещен в urllib.parse в Python 3
from urllib import parse as urlparse # noqa
def is_string(s):
"""Возвращает True, если значение является строкой"""
return isinstance(s, str)
Такие модули compat.py популярны даже в тех проектах, сопровождение которых зависит от Six (https://pypi.python.org/pypi/six/), поскольку это очень удобный способ хранения кода, позволяющий получить совместимость с различными версиями пакетов.
В следующем разделе мы рассмотрим, что такое CPython.
Не только CPython
Эталонная реализация интерпретатора Python называется CPython и, как следует из названия, полностью написана на языке C. Это всегда был C и, вероятно, будет еще очень долго. Данную реализацию выбирает большинство программистов на Python, поскольку она всегда идет в ногу со спецификациями языка и является интерпретатором, на котором протестировано большинство библиотек. Но, кроме C, интерпретатор Python был написан на нескольких других языках. Кроме того, существуют модифицированные версии интерпретатора CPython, доступные под разными названиями и адаптированные для некоторых нишевых приложений. Большинство из них сильно отстают от CPython, но позволяют использовать и продвигать язык в узкоспециализированных задачах.
В этом разделе мы обсудим некоторые из наиболее известных и интересных альтернативных реализаций Python.
Почему это должно нас волновать
Существует много реализаций Python. На «Вики»-странице Python по этой теме (wiki.python.org/moin/PythonImplementations) представлены десятки различных вариантов языка, диалектов или реализаций интерпретатора Python, созданных не на C. Одни из них реализуют лишь часть синтаксиса основного языка, функций и встроенных расширений, но есть почти полностью совместимые с CPython. Надо понимать, что, хотя некоторые из них — просто игрушка или эксперимент, большинство из них созданы для решения реальных проблем, которые было сложно или невозможно решить с помощью CPython.
Примеры таких проблем:
• запуск кода Python на встраиваемых системах;
• интеграция с кодом, написанным для фреймворков вроде Java или .NET или на разных языках;
• запуск кода Python в браузерах.
В следующих подразделах кратко описаны субъективно наиболее популярные и современные варианты, в настоящее время доступные для программистов на Python.
Stackless Python
Stackless Python преподносит себя как улучшенную версию Python. Он носит такое имя, поскольку позволяет избежать зависимости от стека вызова C и имеет свой собственный стек. Это, по сути, модифицированный код CPython, в котором также добавлены новые функции, отсутствовавшие в ядре Python на момент создания Stackless. Наиболее важными из них являются микропотоки, управляемые интерпретатором, как дешевая и облегченная альтернатива обычным потокам, которые должны зависеть от ядра системы и планирования задач.
Последние доступные версии 2.7.15 и 3.6.6 реализуют Python 2.7 и 3.6 соответственно. Все дополнительные функции в версии Stackless показаны в качестве основы в фреймворке через встроенный модуль stackless.
Stackless — не самая популярная альтернатива реализации Python, но о ней стоит знать, поскольку некоторые из реализованных в ней идей сильно повлияли на сообщество. Функциональность переключения ядра была извлечена из Stackless и опубликована в качестве самостоятельного пакета под названием greenlet, в настоящее время лежащего в основе многих полезных библиотек и фреймворков. Кроме того, большинство из его функций были вновь реализованы в PyPy — еще одной реализации Python, о которой мы поговорим позже. Официальную онлайн-документацию по Stackless Python можно найти по адресу stackless.readthedocs.io, а «Вики»-проект — на github.com/stackless-dev/stackless.
Jython
Jython — это реализация на Java. Код компилируется в байт-код Java и позволяет разработчикам легко задействовать классы Java в модулях Python. Jython дает возможность использовать Python как скриптовый язык верхнего уровня для сложных прикладных систем, например J2EE. Он также открывает Java-приложениям путь в мир Python. Создание Apache Jackrabbit (хранилище документов API на основе JCR, jackrabbit.apache.org) является хорошим примером того, что можно сделать с помощью Jython.
Основные отличия Jython от CPython:
• сбор мусора на Java вместо подсчета ссылок;
• отсутствие глобальной блокировки интерпретатора (global interpreter lock, GIL) позволяет более эффективно использовать несколько ядер в многопоточных приложениях.
Основной недостаток данной реализации языка — отсутствие поддержки расширений Python на С, поэтому написанные на С расширения не будут работать на Jython.
Последняя доступная версия Jython — Jython 2,7, и она соответствует версии языка 2.7. По заявлению разработчиков, в ней реализовано почти все ядро стандартной библиотеки Python и используются те же регрессионные тесты. К сожалению, Jython 3.x так и не был выпущен, и проект можно смело считать мертвым. Тем не менее Jython заслуживает хотя бы внимания, поскольку в свое время был уникальным явлением, которое значительно повлияло на другие реализации Python.
Официальная страница проекта: www.jython.org.
IronPython
IronPython — это объединение Python и .NET Framework. Проект поддерживается корпорацией Microsoft, где работают ведущие разработчики IronPython. Это довольно крутая реклама для продвижения языка. За исключением Java, .NET — одно из крупнейших сообществ разработчиков в Microsoft. Стоит также отметить, что Microsoft предоставляет набор бесплатных инструментов разработки, которые превращают Visual Studio в полноценную IDE для Python. Он распространяется в виде плагинов Visual Studio под названием Python Tools for Visual Studio (PVTS), доступных с открытым исходным кодом на GitHub (microsoft.github.io/PTVS).
Последний стабильный релиз — версия 2.7.8, и она совместима с Python 2.7. В отличие от Jython, здесь мы можем наблюдать активное развитие обеих веток — и 2.x, и 3.x, хотя поддержка Python 3 до сих пор официально не выпущена. Несмотря на то что .NET работает в основном на Microsoft Windows, IronPython можно также запустить на macOS и Linux. Это реализовано с помощью Mono, кросс-платформенной реализации .NET с открытым исходным кодом.
Основные отличия и преимущества CPython, по сравнению с IronPython, заключаются в следующем:
• как и в Jython, отсутствие глобальной блокировки интерпретатора (GIL) позволяет более полно использовать несколько ядер в многопоточных приложениях;
• код, написанный на C# и других языках .NET, легко интегрируется в IronPython и наоборот;
• он может работать во всех основных браузерах при наличии Silverlight (хотя Microsoft обещает прекратить поддержку Silverlight в 2021 году).
Есть у IronPython и отрицательные стороны — он очень похож на Jython, поскольку не поддерживает API расширений Python/C. Это важно для разработчиков, которые хотели бы использовать пакеты вроде NumPy, основанные на C. Сообщество несколько раз пыталось внедрить поддержку API Python/C в IronPython или по крайней мере совместимость с пакетом NumPy, но, к сожалению, ни один проект не стал успешным.
Узнать больше о IronPython можно на официальной странице проекта ironpython.net.
PyPy
PyPy — вероятно, самая интересная альтернативная реализация Python, поскольку в ней Python переписан на Python. Интерпретатор PyPy написан на Python. В CPython есть код на C, который делает всю работу. Но в PyPy этот код написан на чистом Python.
Это значит, что вы можете изменить поведение интерпретатора во время выполнения, а также реализовать паттерны проектирования, которые в CPython реализовать сложно.
PyPy в настоящее время полностью совместим с Python 2.7.13, в то время как последняя версия PyPy 3 совместима с Python версии 3.5.3.
В прошлом PyPy был интересен больше из теоретических соображений и только тем, кто увлечен особенностями языка. Обычно он не использовался в продакшене, но со временем это изменилось. В настоящее время многие тесты показывают, что, как ни удивительно, PyPy часто работает намного быстрее, чем реализация CPython. У этого проекта есть собственный бенчмаркинг, в котором отслеживается эффективность всех версий, измеренная с помощью десятков различных критериев (см. speed.pypy.org). Это говорит о том, что PyPy с JIT работает, как правило, в несколько раз быстрее, чем CPython. Эти и другие особенности PyPy побуждают все больше и больше разработчиков использовать PyPy в их production-среде.
Основные отличия PyPy, по сравнению с реализацией CPython, заключаются в следующем:
• используется сбор мусора вместо подсчета ссылок;
• имеется встроенный компилятор JIT, который дает серьезные улучшения в производительности;
• используется Stackless на уровне приложения, заимствованный из Stackless Python.
Как и почти любой другой альтернативной реализации Python, PyPy не хватает полноценной официальной поддержки расширений Python на языке C. Тем не менее в ней есть хоть какая-то поддержка расширений C через подсистему CPyExt, хотя у той пока нет нормальной документации. Кроме того, сообщество постоянно пытается портировать NumPy на PyPy, поскольку это наиболее востребованная функция.
Официальную страницу проекта PyPy можно найти на сайте pypy.org.
MicroPython
MicroPython — одна из самых молодых альтернативных реализаций в данном перечне, так как ее первая официальная версия была выпущена 3 мая 2014 года. Кроме того, это одна из самых интересных реализаций. MicroPython — интерпретатор Python, который был оптимизирован для использования на микроконтроллерах, то есть в стесненных условиях. Небольшой размер и кое-какие оптимизации позволяют ему работать всего в 256 килобайтах кода и всего в 16 килобайтах оперативной памяти.
Протестировать этот интерпретатор можно на контроллерах BBC — это разрядные устройства и пайборды, ориентированные на обучение программированию и основам электроники.
Интерпретатор MicroPython написан на C99 (это стандарт языка C) и может быть построен для многих аппаратных архитектур, включая x86, x86-64, ARM, ARM Thumb и Xtensa. Он основан на Python 3, однако ввиду многих различий синтаксиса нельзя достоверно сказать о полной совместимости с любой версией Python 3.x. Это скорее диалект Python 3 с функцией print(), ключевыми словами async/await и многими другими функциями Python 3. Не стоит ожидать, что ваши любимые библиотеки Python 3 будут работать должным образом без дополнительных настроек.
Узнать больше о MicroPython можно на официальной странице проекта micropython.org.
Полезные ресурсы
Лучший способ знать все о состоянии Python — быть в курсе всего нового и читать тематические ресурсы. В Интернете их множество. Наиболее важные и очевидные из них уже упоминались ранее, но для порядка повторим:
• документация Python;
• каталог пакетов Python (Python Package Index, PyPI);
• PEP 0 — индекс Python Enhancement Proposals (PEP).
Другие ресурсы, такие как книги и учебные пособия, тоже полезны, но быстро теряют актуальность. Не устаревают ресурсы, активно обновляемые сообществом. Те немногие, которые стоит рекомендовать, представлены ниже.
• Awesome Python (github.com/vinta/awesome-python) включает список популярных пакетов и структур.
• r/Python (www.reddit.com/r/Python/) — сабреддит Python, на котором можно найти новости и интересные посты о Python, каждый день размещаемые многими членами сообщества Python.
• Python Weekly (www.pythonweekly.com) — популярная информационная рассылка, в которой каждую неделю появляются десятки новых интересных пакетов и ресурсов Python.
• Pycoder’s Weekly (pycoders.com) — еще одна популярная еженедельная информационная рассылка с дайджестом новых пакетов и интересных статей. Контент там часто пересекается с Python Weekly, но иногда можно найти что-то уникальное, еще не опубликованное в другом месте.
На этих сайтах можно найти множество дополнительных материалов.
Резюме
Данная глава была посвящена текущему состоянию Python и изменениям, которые происходили на протяжении всей истории этого языка. Мы начали с обсуждения того, как и почему Python изменяется, и описали основные результаты этого процесса, особенно различия между версиями Python 2 и 3. Мы научились работать с данными изменениями и узнали о некоторых полезных методах, позволяющих писать код, совместимый с различными версиями языка и его библиотек.
Затем мы по-другому взглянули на идею изменений в языке программирования. Рассмотрели несколько популярных альтернативных реализаций Python и поговорили об их основных отличиях от реализации CPython по умолчанию.
В следующей главе мы опишем современные способы создания повторяемых и последовательных сред разработки для программистов Python и обсудим два популярных инструмента для изоляции окружающей среды: virtualenv и контейнеры Docker.
2. Современные среды разработки на Python
Глубокое понимание выбранного языка программирования — основа профессионализма. Это касается любой технологии. Действительно, трудно создавать хорошее программное обеспечение, не умея работать с инструментами и методами, общепринятыми в сообществе. В Python нет ни одной функции, которой не было бы в каком-либо другом языке. Если сравнивать синтаксис, выразительность или производительность, то всегда найдется решение, которое окажется лучше в том или ином смысле. Но вот чем Python действительно выделяется, так это экосистемой, возникшей вокруг языка. Сообщество Python долгие годы оттачивало стандартные методы и библиотеки, которые помогают создавать более надежное программное обеспечение в кратчайшие сроки.
Наиболее очевидная и важная часть экосистемы — огромная коллекция бесплатных пакетов и пакетов с открытым исходным кодом, которые решают множество проблем. Написание нового программного обеспечения — всегда дорогостоящий и трудоемкий процесс. Возможность использовать уже готовый код значительно сокращает время разработки. Для некоторых компаний это единственный способ сделать проекты экономически выгодными.
Разработчики на Python вложили много усилий в создание инструментов и стандартов для работы с пакетами с открытым исходным кодом, написанными другими разработчиками, начиная с виртуальных окружений, усовершенствованных интерактивных оболочек и отладчиков и заканчивая программами, которые позволяют найти и проанализировать огромную коллекцию пакетов, имеющихся в каталоге пакетов Python (Python Package Index, PyPI).
В этой главе мы обсудим:
• установку дополнительных пакетов Python с использованием pip;
• изоляцию сред исполнения;
• venv — виртуальное окружение Python;
• изоляцию среды на уровне системы;
• популярные инструменты повышения производительности.
Технические требования
Скачать бесплатные инструменты виртуализации, о которых мы будем говорить в этой главе, можно со следующих сайтов:
• Vagrant: www.vagrantup.com;
• Docker: www.docker.com.
Ниже приведены пакеты Python, которые упоминаются в этой главе, их вы можете скачать с PyPI:
•virtualenv;
• ipython;
• ipdb;
• ptpython;
• ptbdb;
• bpython;
• bpdb.
Установить эти пакеты можно с помощью следующей команды:
python3 -m pip install <имя-пакета>
Файлы с примерами кода для этой главы можно найти по адресу github.com/PacktPublishing/Expert-Python-Programming-Third-Edition/tree/master/chapter2.
Установка дополнительных пакетов Python с использованием pip
Сегодня многие операционные системы поставляются с Python в качестве стандартного компонента. Большинство дистрибутивов Linux и UNIX на основе FreeBSD, NetBSD, OpenBSD или macOS поставляются с Python либо сразу «из коробки», либо из репозитория. Многие из них даже используют его в своих основных компонентах — на Python работают инсталляторы Ubuntu (Ubiquity), Red Hat Linux (Anaconda) и Fedora (опять же Anaconda). К сожалению, обычно предустановленной версией является Python 2.7, которая уже устарела.
Из-за популярности Python в качестве компонента операционной системы многие пакеты PyPI доступны и в виде нативных пакетов, управляемых такими инструментами, как apt-get (Debian, Ubuntu), rpm (Red Hat Linux) или emerge (Gentoo). Следует помнить, однако, что список доступных библиотек весьма ограничен и они в основном устарели по сравнению с PyPI. Поэтому для получения новых пакетов в последней версии всегда нужно использовать pip, как было рекомендовано Python Packaging Authority (PyPA). Несмотря на то что это независимый пакет, начиная с CPython 2.7.9 и 3.4, он по умолчанию идет в комплекте с каждой новой версией. Установить новый пакет очень просто:
pip install <имя-пакета>
Среди прочего pip позволяет устанавливать конкретные версии пакетов (с помощью команды pipinstallpackage-name==version) и обновлять их до последней доступной версии (используя переключатель --upgrade). Полное описание применения большинства инструментов командной строки, представленных в книге, можно легко получить, запустив команду с переключателем -h или --help. Ниже приведен пример сеанса, который демонстрирует наиболее часто используемые опции:
$ pip show pip
Name: pip
Version: 18.0
Summary: The PyPA recommended tool for installing Python packages.
Home-page: https://pip.pypa.io/
Author: The pip developers
Author-email: pypa-dev@groups.google.com
License: MIT
Location: /Users/swistakm/.envs/epp-3rd-ed/lib/python3.7/site-packages
Requires:
Required-by:
$ pip install 'pip>=18.0'
Requirement already satisfied: pip>=18.0 in (...)/lib/python3.7/sitepackages
(18.0)
$ pip install --upgrade pip
Requirement already up-to-date: pip in (...)/lib/python3.7/site-packages
(18.0)
Не всегда pip бывает доступен по умолчанию. В Python 3.4 и далее (а также в Python 2.7.9) его можно загрузить с помощью модуля ensurepip:
$ python -m ensurepip
Looking in links:
/var/folders/z6/3m2r6jgd04q0m7yq29c6lbzh0000gn/T/tmp784u9bct
Requirement already satisfied: setuptools in /Users/swistakm/.envs/epp-3rd
ed/lib/python3.7/site-packages (40.4.3)
Collecting pip
Installing collected packages: pip
Successfully installed pip-10.0.1
Самая актуальная информация о том, как установить pip в более старых версиях Python, доступна на странице документации проекта по ссылке pip.pypa.io/en/stable/installing/.
Изоляция сред выполнения
Можно использовать pip для установки систем пакетов. В UNIX-системах и в Linux для этого нужны права суперпользователя, так что фактический вызов будет выглядеть следующим образом:
sudo pip install <имя-пакета>
Обратите внимание: в ОС Windows это не требуется, поскольку в ней по умолчанию нет интерпретатора Python и Python на Windows, как правило, устанавливается пользователем вручную без привилегий суперпользователя.
Не рекомендуется выполнять установку общесистемных пакетов непосредственно из PyPI. Данное утверждение на первый взгляд может противоречить предыдущему о том, что PyPA рекомендует использовать pip, но тому есть серьезные причины. Как объяснялось ранее, Python часто является составной частью многих пакетов, доступных в репозиториях ОС, и на нем может работать много сервисов. В распределительных системах немало усилий тратится на выбор правильных версий пакетов для обеспечения совместимости. Очень часто в пакеты Python, доступные в репозиториях, включены также пользовательские патчи или намеренно старые версии, чтобы обеспечить совместимость с некоторыми другими компонентами системы. Принудительное обновление такого пакета с помощью pip до версии, которая нарушает обратную совместимость, может привести к критическим багам в ряде важных системных сервисов.
Делать подобные вещи даже на локальном компьютере в целях разработки не рекомендуется. Безрассудно использовать pip таким образом — почти всегда риск, в конечном итоге создающий проблемы, которые очень трудно отлаживать. Это не значит, что установка пакетов из PyPI строго запрещена, но делать это нужно сознательно и с пониманием риска.
К счастью, существует простое решение данной проблемы: изоляция среды. Есть различные инструменты, позволяющие изолировать среду выполнения Python на разных уровнях абстракции системы. Основная идея заключается в том, чтобы изолировать зависимости проекта от пакетов, которые нужны системным сервисам. Преимущества такого подхода заключаются в следующем.
• Это позволяет решить дилемму «Проекту X нужна версия 1.x, но проекту Y необходима 4.x». Программист может работать над несколькими проектами с различными зависимостями без риска их влияния друг на друга.
• Проекты больше не ограничиваются версиями пакетов, которые установлены в распределительных системах хранилищ разработчика.
• Нет рисков поломки других системных сервисов, которые зависят от определенных версий пакетов, так как новые версии пакетов доступны только в изолированной среде.
• Список пакетов-зависимостей можно заморозить и легко воспроизвести на другом компьютере.
Если вы работаете параллельно над несколькими проектами, то быстро обнаружите, что невозможно сохранить их зависимости, не прибегая к какой-либо изоляции.
Сравнение изоляции на уровне приложений с изоляцией на уровне системы. Самый простой и облегченный подход к изоляции — использование виртуальных окружений на уровне приложений. Они выполняют изоляцию интерпретатора Python и пакетов, доступных внутри него. Такие окружения весьма просты в установке, и очень часто их хватает для обеспечения надлежащей изоляции в процессе разработки небольших проектов и пакетов.
К сожалению, их не всегда хватает, когда нужно обеспечить достаточную согласованность и воспроизводимость. Несмотря на то что программное обеспечение, написанное на Python, как правило, считается очень компактным, все равно есть шанс столкнуться с проблемами, которые возникают в специфических системах или даже конкретных распределениях таких систем (например, Ubuntu по сравнению с Gentoo). Это очень распространено в крупных и сложных проектах, особенно если они зависят от скомпилированных расширений Python или внутренних компонентов хостинга операционной системы.
В подобных случаях изоляция на уровне системы отлично дополнит ваш рабочий процесс. При таком подходе делается попытка повторить и изолировать полные операционные системы со всеми их библиотеками и важнейшими системными компонентами либо с классическими инструментами виртуализации системы (например, VMWare, Parallels и VirtualBox) или контейнерными системами (например, Docker и Rocket). Некоторые из доступных решений, позволяющие выполнить такую изоляцию, рассматриваются далее в этой главе.
venv — виртуальное окружение Python
Есть несколько способов изолировать среду выполнения Python. Самый простой и очевидный, хоть и трудный в сопровождении, — вручную изменить значения переменных среды PATH и PYTHONPATH и/или переместить бинарные исходники Python в другое, кастомизированное (настроеное специально для этого) место, где мы бы могли хранить зависимости проекта таким образом, что это изменило бы то, как Python распознает доступные пакеты. К счастью, есть инструменты, которые могут помочь в поддержании виртуальных окружений и установленных для них пакетов. В основном это virtualenv и venv. Они делают, по сути, то же самое, что мы будем делать вручную. Текущая стратегия зависит от конкретной реализации инструмента, но они, как правило, более удобны в использовании и могут дать дополнительные преимущества.
Создать новое виртуальное окружение можно с помощью следующей команды:
python3.7 -m venv ENV
Нужно заменить ENV на желаемое имя для нового окружения. Это создаст новый каталог ENV в текущем рабочем каталоге. Внутри появится несколько новых каталогов:
•bin/ — здесь хранятся новый исполняемый файл Python и скрипты/исполняемые файлы других пакетов;
• lib/ и include/ — эти каталоги содержат вспомогательные файлы библиотек для нового Python в виртуальном окружении. Новые пакеты будут установлены в ENV/Lib/pythonX.Y/site-packages/.
Созданное новое окружение нужно активировать в текущем сеансе оболочки с помощью команды UNIX:
source ENV/bin/activate
Таким образом изменяется состояние текущих сессий оболочки благодаря воздействию на переменные окружения. Чтобы пользователь понимал, что он активировал виртуальное окружение, в подсказке появится приписка (ENV). Приведем пример сеанса, который создает и активирует новое окружение:
$ python -m venv example
$ source example/bin/activate
(example) $ which python
/home/swistakm/example/bin/python
(example) $ deactivate
$ which python
/usr/local/bin/python
Важно отметить, что venv полностью зависит от состояния, которое хранится в файловой системе. Она не дает каких-либо дополнительных возможностей и не позволяет отслеживать, какие пакеты должны быть установлены. Виртуальные окружения также не портируемы, и их нельзя перенести на другую машину. То есть новое виртуальное окружение создается заново для каждого нового развертывания приложения. Из-за этого пользователи venv часто хранят зависимости проекта в файле requirements.txt (общепринятое имя), как показано в следующем коде:
# Строки после решетки (#) рассматриваются как комментарии
# Строгие имена версий лучше для воспроизводимости
eventlet==0.17.4
graceful==0.1.1
# Для проектов, которые хорошо протестированы с различными
# версиями зависимостей, принимаются относительные спецификаторы
falcon>=0.3.0,<0.5.0
# Пакетов без версий следует избегать, если не требуется последняя версия
pytz
При наличии таких файлов все зависимости можно легко установить с помощью pip, поскольку он принимает файл зависимостей на выходе:
pip install -r requirements.txt
Важно помнить: файл требований не всегда идеальное решение, так как в нем есть только те зависимости, которые будут установлены. Из-за этого весь проект может без проблем функционировать в одних средах разработки и совсем не запускаться в других, если файл требований устарел и не соответствует фактическому состоянию среды. Конечно, есть команда pipfreeze, выводящая все пакеты в текущей среде, но ее не следует применять бездумно. Эта команда выводит все пакеты и даже те из них, которые не задействованы в проекте, а установлены только для тестирования.
Примечание для пользователей Windows
В venv под Windows действует другое соглашение об именах во внутренней структуре каталогов. Нужно использовать Scripts/, Libs/, а также Include/ вместо bin/, lib/ и include/, чтобы лучше соответствовать соглашениям в области развития в этой операционной системе. Команды для активации/деактивации среды также отличаются: нужно применять ENV/Scripts/activate.bat и ENV/Scripts/deactivate.bat вместо source в скриптах activate и deactivate.
Устаревший скрипт pyvenv
Модуль venv включает дополнительный скрипт командной строки pyvenv. Начиная с Python 3.6, он был отмечен как устаревший, и его использование официально не рекомендуется, поскольку команда pythonX.Y -m venv явно говорит о том, какая версия Python будет применяться для создания новой среды, в отличие от скрипта pyvenv.
venv против virtualenv. Задолго до создания стандартного модуля библиотеки pyenv инструмент virtualenv был одним из самых популярных средств создания облегченных виртуальных окружений. Его название так и расшифровывается: виртуальное окружение, или среда (virtual environment). Он не входит в стандартный дистрибутив Python, поэтому его можно получить с помощью pip. Если вы хотите применять данный инструмент, то стоит его устанавливать во всей системе (используя sudo в системах на базе Linux и UNIX).
Мы бы рекомендовали использовать модуль venv вместо virtualenv всегда, когда это возможно. Он должен выбираться по умолчанию для проектов, ориентированных на версию Python 3.4 и выше. Применять venv в Python 3.3 может быть немного неудобно из-за отсутствия встроенной поддержки setuptools и pip. Для проектов, ориентированных на более широкий спектр сред выполнения Python (в том числе альтернативные интерпретаторы версий 2.x), virtualenv — более удачный выбор.
В следующем разделе мы рассмотрим изоляцию среды на уровне системы.
Изоляция среды на уровне системы
В большинстве случаев реализация ПО выполняется быстро, поскольку разработчики по многу раз задействуют множество существующих компонентов. «Не занимайтесь самокопированием» — вот популярное правило и девиз многих программистов. Применение других пакетов и модулей с целью включить их в базу кода — лишь часть этой культуры. Еще повторно используемыми компонентами можно считать бинарные библиотеки, базы данных, системные сервисы, сторонние API и т.д. Даже целые операционные системы можно считать такими компонентами.
Бэкенд-сервисы веб-приложений — отличный пример того, сколь сложными могут быть такие приложения. Самый простой стек, как правило, состоит из нескольких слоев (начиная с самого нижнего), таких как:
• база данных или другой вид хранилищ;
• код приложения, реализованный на Python;
• серверы HTTP, такие как Apache или NGINX.
Конечно, такие стеки могут быть еще проще, но это очень маловероятно. На самом деле большие приложения зачастую настолько сложны, что трудно выделить отдельные слои. Такие приложения могут использовать множество различных баз данных, делиться на несколько независимых процессов, а также задействовать много других системных сервисов для кэширования, создания очередей, регистрации, открытия сервисов и т.д. К сожалению, нет никаких ограничений по сложности, и код, похоже, следует второму закону термодинамики.
Вот что действительно важно: не все элементы стека программного обеспечения можно изолировать на уровне среды исполнения Python. Будь то сервер HTTP, например Nginx, или СУБД наподобие PostgreSQL, они, как правило, доступны в различных версиях и системах. Без подходящих инструментов сложно убедиться, что вся команда разработчиков использует одни и те же версии компонентов. Теоретически возможно, что все программисты в команде, работающей над одним проектом, смогут установить одинаковые версии сервисов на свои рабочие станции. Но все эти усилия тщетны, если разработчики не используют ту же ОС, что будет в production-среде. Просто нереально заставить программиста променять любимую систему на нечто другое.
Серьезной проблемой все еще является портируемость. Не все сервисы будут работать в production-средах так же, как на машине разработчика, и это вряд ли изменится. Даже поведение Python может отличаться в разных системах, несмотря на все те усилия, который сообщество приложило, чтобы сделать его кросс-платформенным. Как правило, поведение хорошо задокументировано и отмечается только в тех местах, которые непосредственно зависят от инициируемых системой вызовов, но полагаться на способность программиста помнить длинный список проблем совместимости — большая ошибка.
Популярное решение данной проблемы заключается в изоляции целых систем как среды приложения. Обычно это достигается за счет использования различных типов инструментов виртуализации системы. Виртуализация, конечно, снижает производительность, но у современных компьютеров, снабженных аппаратной поддержкой виртуализации, потери производительности, как правило, незначительны. А вот список возможных преимуществ довольно велик:
• среда разработки может точно соответствовать версиям системы и сервиса, используемым в продакшене, что помогает решить проблемы совместимости;
• определения для инструментов конфигурации системы, таких как Puppet, Chef, или Ansible (если они применяются), можно повторно использовать для настройки среды разработки;
• вновь прибывшие члены команды могут легко включиться в проект, если создание таких сред автоматизировано;
• программисты могут работать непосредственно с низкоуровневыми системными функциями, не всегда доступными в операционных системах, которые они используют для работы, например с файловыми системами в пространстве пользователя (FUSE), недоступными в Windows.
В следующем подразделе мы рассмотрим виртуальные среды разработки, использующие Vagrant.
Виртуальные среды разработки, использующие Vagrant
В настоящее время Vagrant — один из самых популярных инструментов для программистов, позволяющий управлять виртуальными машинами в локальной разработке. Он предоставляет разработчику простой и удобный способ описания среды разработки со всеми зависимостями с привязкой к исходному коду вашего проекта. Доступен для Windows, Mac OS, а также в некоторых популярных дистрибутивах Linux (см. www.vagrantup.com). Vagrant не имеет дополнительных зависимостей. Он создает новые среды разработки в виде виртуальных машин или контейнеров. Конкретная реализация будет зависеть от выбора поставщиков виртуализации. Поставщиком по умолчанию является VirtualBox, который предоставляется в комплекте с программой установки Vagrant, однако доступны и дополнительные провайдеры. Наиболее известные варианты: VMware, Docker, Linux Containers (LXC) и Hyper-V.
Наиболее важную конфигурацию Vagrant предоставляет в одном файле под названием Vagrantfile. Он должен быть независимым для каждого проекта. Ниже приведены его наиболее важные функции:
• выбор поставщика виртуализации;
• создание образа виртуальной машины;
• выбор способа инициализации;
• наличие общего хранилища между VM и хостом VM;
• наличие портов, которые передаются между VM и ее хостом.
Языком синтаксиса Vagrantfile является Ruby. В файле конфигурации есть удобный шаблон, позволяющий запустить проект, а также отличная документация, поэтому знание языка не требуется. Конфигурацию шаблона можно создать с помощью одной команды:
vagrant init
Эта команда создаст в текущем рабочем каталоге новый файл под названием Vagrantfile. Лучшее место для хранения данного файла, как правило, корневая папка проекта. Этот файл — конфигурация, которая создаст новую виртуальную машину с помощью поставщика по умолчанию и базового образа. Файл Vagrantfile, созданный командой vagrantinit, содержит много комментариев, которые дают описание всей процедуры настройки.
Ниже приведен пример минимального файла Vagrantfile для среды разработки Python 3.7 на основе операционной системы Ubuntu со значениями по умолчанию, в которых среди прочего установлена переадресация порта 80 на случай, если вы захотите заниматься веб-разработкой на Python:
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
# Для каждой среды Vagrant требуется контейнер.
# Их можно найти на https://vagrantcloud.com/search.
# Здесь мы используем версию Bionic системы Ubuntu с архитектурой x64.
config.vm.box = "ubuntu/bionic64"
# Создание отображения порта переадресации, которое позволяет получить
# доступ к указанному порту на машине из порта на хост-машине
# и разрешение доступа через 127.0.0.1 и отключение публичного доступа.
config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"
config.vm.provider "virtualbox" do |vb|
# Отображение VirtualBox GUI при загрузке машины
vb.gui = false
# Настройка объема памяти на VM
vb.memory = "1024"
end
# Активация с помощью скрипта оболочки
config.vm.provision "shell", inline: <<-SHELL
apt-get update
apt-get install python3.7 -y
SHELL
end
В предыдущем примере мы установили дополнение в системный пакет с помощью простого скрипта оболочки. Почувствовав, что Vagrantfile готов, вы можете запустить виртуальную машину, используя следующую команду:
vagrant up
Первый запуск может занять несколько минут, поскольку образ скачивается из Интернета. Каждый раз при запуске виртуальной машины будут выполнены процедуры инициализации, продолжительность которых зависит от выбора поставщика, образа и производительности вашей системы. Как правило, это занимает всего несколько секунд. После того как новая среда Vagrant будет запущена, разработчик сможет подключаться к ней через SSH, используя следующую команду:
vagrant ssh
Это можно сделать в любом месте в исходном дереве проекта ниже местоположения Vagrantfile. Для удобства разработчиков Vagrant будет обходить все каталоги выше текущего рабочего каталога пользователя в дереве файлов, найдет файл конфигурации и сопоставит его с соответствующим экземпляром виртуальной машины. Затем будет установлено надежное соединение с оболочкой, и со средой разработки можно будет взаимодействовать так же, как с обычной удаленной машиной. Единственное отличие состоит в том, что дерево всего исходного проекта (корнем считается местоположение Vagrantfile) доступно в файловой системе виртуальной машины в /vagrant/. Этот каталог автоматически синхронизируется с файловой системой, вследствие чего можно нормально работать в IDE или в любимом редакторе на хосте и относиться к сессии SSH на виртуальной машине Vagrant так же, как к обычной локальной сессии.
В следующем подразделе мы рассмотрим виртуальные среды, использующие Docker.
Виртуальные среды, использующие Docker
Контейнеры — альтернатива полноценным виртуальным машинам. Это облегченный инструмент виртуализации, в котором ядро и операционная система позволяют запускать несколько экземпляров изолированных пространств пользователя. Работа ОС распределяется между контейнерами и хостом, что теоретически требует меньше затрат, чем при полной виртуализации. Такой контейнер содержит только код приложения и его зависимости системного уровня, но с точки зрения его работы выглядит как полностью изолированная среда.
Программные контейнеры получили свою популярность в основном благодаря Docker, который является одной из доступных реализаций. Docker позволяет описать контейнер в виде простого текстового документа под названием Dockerfile. Контейнеры из таких определений можно собирать и сохранять. Docker также поддерживает постепенные изменения, поэтому при добавлении в контейнер чего-то нового его не придется переделывать с нуля.
Попробуем сравнить контейнеризацию и виртуализацию.
Контейнеризация против виртуализации
Различные инструменты вроде Docker и Vagrant имеют некоторые общие функции, но главное различие между ними заключается в их предназначении. Vagrant, о чем уже было сказано ранее, создан в первую очередь как инструмент разработки. Он позволяет загружать целую виртуальную машину одной командой, но не дает упаковать такую среду и включить в состав чего-то отдельным пакетом и затем развернуть ее. А вот Docker создан именно для этой цели — подготовки готовых контейнеров, которые можно отправлять и развертывать как единый пакет. При грамотной реализации это может значительно улучшить процесс развертывания продукта. Поэтому применять Docker и аналогичные решения (например, Rocket) для разработки имеет смысл только в том случае, если такие контейнеры нужно будет использовать в процессе развертывания в продакшене.
Из-за некоторых нюансов реализации поведение сред, основанных на контейнерах и на виртуальных машинах, может различаться. Если вы решили использовать контейнеры для разработки, но при этом они не нужны в продакшене, то потеряется часть гарантий совместимости, из-за которых вы, собственно, и занимались изоляцией среды. Но если вы уже применяете контейнеры в вашей целевой production-среде, то следует воспроизводить условия продакшена. К счастью, Docker, который в настоящее время является наиболее популярным контейнером, предоставляет удивительный инструмент docker-compose, делающий управление местной контейнерной средой чрезвычайно легким.
Напишем наш первый Dockerfile.
Создание первого Dockerfile
Каждая среда на основе Docker начинается с Dockerfile. Это формат описания того, как создать образ Docker. Можно считать образы Docker чем-то аналогичным образам виртуальных машин. Это один файл (состоящий из многих слоев), который включает в себя все системные библиотеки, файлы исходного кода и другие зависимости, необходимые для выполнения приложения.
Каждый слой образа Docker описывается в Dockerfile с помощью одной команды в следующем формате:
ИНСТРУКЦИЯ аргументы
Docker поддерживает множество команд, но вам нужны основные из них, чтобы начать работу:
•FROM<имя-образа> — описывает базовый образ, на котором будет основан новый;
• COPY<src>...<dst> — копирует файлы из локальнй сборки (обычно файлы проекта) и добавляет их в файловую систему контейнера;
• ADD<src>...<dst> — работает аналогично COPY, но автоматически распаковывает архивы и позволяет вставлять в <src> ссылки;
• RUN<команда> — запускает заданные команды поверх предыдущих слоев и подтверждает изменения, которые эта команда внесла в файловую систему в качестве нового слоя образа;
• ENTRYPOINT["<executable>","<param>",...] — настройка команды по умолчанию, которая будет работать в качестве контейнера. Если ни одна точка входа не указана в любом месте в слоях образа, то Docker по умолчанию выполняет /bin/sh-c;
• CMD["<param>",...] — определяет параметры по умолчанию для входных точек образа. Зная, что точка входа по умолчанию для Docker — это /bin/sh-c, эта команда может также принимать форму CMD["<executable>","<param>",...], хотя рекомендуется определить целевой исполняемый файл непосредственно в инструкции ENTRYPOINT и использовать CMD лишь для аргументов по умолчанию;
• WORKDIR<dir> — устанавливает текущий рабочий каталог для инструкций RUN, CMD, ENTRYPOINT, COPY и ADD.
Для правильной демонстрации типичной структуры Dockerfile предположим, что хотим задокерить встроенный в Python веб-сервер, доступный через модуль http.server с кое-какими файлами, нужными для этого сервера. Структура наших файлов проекта может выглядеть следующим образом:
.
├── Dockerfile
├── README
└── static
├── index.html
└── picture.jpg
Локально вы можете запустить http.server Python на HTTP-порте по умолчанию следующей простой командой:
python3.7 -m http.server --directory static/ 80
Данный пример, конечно, весьма тривиален, и использовать для этого Docker — все равно что колоть орехи кувалдой. Поэтому для целей данного примера представим: у нас в проекте есть много кода, генерирующего эти статические файлы. Нам нужно доставить только их, а не код, который их генерирует. Кроме того, предположим, получатели нашего образа знают, как использовать Docker, но не знают, как работать с Python.
Итак, наша цель заключается в следующем:
• скрыть от пользователя некоторые сложности, особенно тот факт, что мы задействуем Python и встроенный в него HTTP-сервер;
• упаковать исполняемый файл Python 3.7 со всеми его зависимостями и статическими файлами в основной каталог проекта;
• выставить настройки по умолчанию для запуска сервера на порте 80.
С учетом всех этих требований наш Dockerfile может иметь следующий вид:
# Определим основной образ.
# "python" является официальным образом Python.
# Разумно использовать «тонкие версии»
# для других облегченных образов на основе Python
FROM python:3.7-slim
# Для того чтобы образ был чист, переключимся
# на выбранный рабочий каталог "/app/", который
# обычно используется для этой цели.
WORKDIR /app/
# Это наши статические файлы, скопированные
# из дерева проекта в рабочий каталог.
COPY static/ static/
# Мы бы запустили "python -m http.server" локально,
# так что сделаем его точкой входа.
ENTRYPOINT ["python3.7", "-m", "http.server"]
# Мы хотим брать файлы из каталога static/
# на порте 80 по умолчанию, так что установим это в качестве аргументов
# по умолчанию для встроенного в Python HTTP-сервера.
CMD ["--directory", "static/", "80"]
Рассмотрим, как запустить контейнеры.
Запуск контейнеров
Прежде чем запускать контейнер, нужно в первую очередь создать образ, определенный в Dockerfile. Это можно сделать с помощью следующей команды:
docker build -t <name> <path>
Аргумент -t<name> позволяет дать образу понятное имя. Это совершенно не обязательно, но без него вы не сможете легко сослаться на вновь созданный образ. Аргумент <path> определяет путь к папке, где находится Dockerfile. Предположим, мы уже выполнили команду из корневого каталога проекта, который представили выше, и хотим дать образу имя webserver. Команда dockerbuild после этого даст следующий вывод:
$ docker build -t webserver .
Sending build context to Docker daemon 4.608kB
Step 1/5 : FROM python:3.7-slim
3.7-slim: Pulling from library/python
802b00ed6f79: Pull complete
cf9573ca9503: Pull complete
b2182f7db2fb: Pull complete
37c0dde21a8c: Pull complete
a6c85c69b6b4: Pull complete
Digest:
sha256:b73537137f740733ef0af985d5d7e5ac5054aadebfa2b6691df5efa793f9fd6d
Status: Downloaded newer image for python:3.7-slim
---> a3aec6c4b7c4
Step 2/5 : WORKDIR /app/
---> Running in 648a5bb2d9ab
Removing intermediate container 648a5bb2d9ab
---> a2489d084377
Step 3/5 : COPY static/ static/
---> 958a04fa5fa8
Step 4/5 : ENTRYPOINT ["python3.7", "-m", "http.server", "--bind", "80"]
---> Running in ec9f2a63c472
Removing intermediate container ec9f2a63c472
---> 991f46cf010a
Step 5/5 : CMD ["--directory", "static/"]
---> Running in 60322d5a9e9e
Removing intermediate container 60322d5a9e9e
---> 40c606a39f7a
Successfully built 40c606a39f7a
Successfully tagged webserver:latest
После создания вы можете просмотреть список доступных образов с помощью команды:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
webserver latest 40c606a39f7a 2 minutes ago 143MB
python 3.7-slim a3aec6c4b7c4 2 weeks ago 143MB
Поразительный размер образа контейнера
Вес простого образа Python в 143 Мбайт — это многовато, однако на самом деле беспокоиться не о чем. Для краткости мы задействовали базовый образ, который прост в применении. Есть и другие образы, размер которых был специально ужат, но они, как правило, для более опытных пользователей Docker. Кроме того, благодаря слоистой структуре образов Docker если вы применяете много контейнеров, то базовые слои можно кэшировать и использовать повторно, так что в конечном итоге вы не будете думать о размере.
Когда образ будет собран и помечен, вы сможете запустить контейнер с помощью команды dockerrun. Наш контейнер является примером веб-сервиса, поэтому мы должны дополнительно сказать Docker, что хотим открыть порты контейнера, связав их локально:
docker run -it --rm -p 80:80 webserver
Вот объяснение некоторых аргументов предыдущей команды:
•-it — это на самом деле две сопряженные опции: -i и -t. -i (от interactive) держит STDIN открытым, даже если процесс контейнера будет отсоединен, и -t (например, tty) выделяет псевдо-TTY для контейнера. Короче говоря, эти две опции позволяют увидеть живые логи от http.server и убедиться, что прерывание клавиатуры приведет к выходу из процесса. Он станет вести себя так же, как если мы запустим Python прямо из командной строки;
• --rm — дает Docker указание автоматически удалять контейнер при выходе;
•-p80:80 — дает Docker указание открыть порт 80, привязывая его к интерфейсу хоста.
Настройка сложных сред
Хотя использовать Docker довольно легко в простых проектах, все может усложниться, как только вы начнете применять его сразу в нескольких проектах. Бывает очень легко забыть о конкретных параметрах командной строки или о том, на каких образах надо открывать те или иные порты. Все становится очень сложно при наличии сервиса, который должен общаться с другими сервисами. Одиночные контейнеры должны содержать только один запущенный процесс.
Это значит, что вам не нужно будет устанавливать дополнительные инструменты мониторинга процессов, такие как Supervisor или Circus, а вместо этого следует создать несколько контейнеров, взаимодействующих друг с другом. Каждый сервис может использовать свой образ, обеспечивать различные варианты конфигурации и открывать порты, которые могут перекрывать или не перекрывать друг друга.
Лучший инструмент, который можно использовать для простых и сложных случаев, — это Compose. Он обычно распространяется с Docker, но в некоторых дистрибутивах Linux (например, Ubuntu) его может и не быть по умолчанию и его придется установить как отдельный пакет из репозитория пакетов. Compose — это мощная утилита командной строки под именем docker-compose, которая позволяет описывать мультиконтейнеры приложения с помощью синтаксиса YAML.
Compose ожидает, что в каталоге проекта находится специально названный файл docker-compose.yml. Пример такого файла для нашего предыдущего проекта может выглядеть следующим образом:
version: '3'
services:
webserver:
# Инструктирует Compose собирать образ
# из локального каталога (.)
build: .
# Эквивалентно опции "-p" команды docker build
ports:
- "80:80"
# Эквивалентно опции "-t" команды docker build
tty: true
Если создать в проекте файл docker-compose.yml, то всю вашу прикладную среду можно запустить и остановить двумя простыми командами:
•docker-composeup;
•docker-composedown.
Полезные рецепты Docker для Python
Docker и контейнеры — столь обширная тема, что невозможно обсудить ее всю в одной главе этой книги. Compose позволит легко начать работать с Docker, не имея особого понимания, как он функционирует внутри. Если вы новичок в Docker, то вам стоит немного притормозить, взять документацию и вдумчиво почитать ее, чтобы эффективно использовать Docker и преодолеть некоторые из неизбежных сложностей.
Ниже приведены несколько простых советов и рецептов, позволяющих решить большинство стандартных проблем, с которыми вы, вероятно, рано или поздно столкнетесь.
Уменьшение размера контейнеров
Общая проблема новых пользователей Docker — размер образов контейнеров. Действительно, контейнеры занимают много пространства по сравнению с простыми пакетами Python, но совсем немного по сравнению с размером образов для виртуальных машин. По-прежнему очень часто разработчики размещают несколько сервисов на одной виртуальной машине, но при использовании контейнеров у вас обязательно должен быть отдельный образ для каждого сервиса. Это значит, что при большом количестве сервисов расходы могут стать заметными.
Ограничить размер образов можно двумя дополнительными методами.
•Использовать базовый образ, который разработан специально для этой цели. Alpine Linux — пример компактного варианта Linux, специально приспособленного для создания очень маленьких и облегченных образов Docker. Базовый образ весит всего 5 Мбайт и включает элегантный менеджер пакетов, который позволяет сохранить компактность образа.
• Принять во внимание характеристики наложения файловой системы Docker. Образы Docker состоят из слоев, каждый из которых включает разницу в корневой файловой системе между собой и предыдущим слоем. Когда слой создан, размер образа уже не может быть уменьшен. Это значит, что если вам нужен системный пакет в качестве зависимости сборки и его можно позже удалить из образа, то вместо использования нескольких команд RUN будет лучше все делать в одной команде RUN с командами оболочки.
Эти два метода можно проиллюстрировать следующим Dockerfile:
# Здесь мы используем alpine для иллюстрации
# управления пакетами, так как ему не хватает Python
# по умолчанию. Для проектов Python в целом
# лучше выбрать python:3.7-alpine.
FROM alpine:3.7
# Добавить пакет python3, так как у образа alpine его нет по умолчанию.
RUN apk add python3
# Запуск нескольких команд в одной команде RUN.
# Пространство может быть использовано после "apk del py3-pip", потому что
# слой изображения создается только после выполнения всей инструкции.
RUN apk add py3-pip && \
pip3 install django && \
apk del py3-pip
# (...)
Обращение к сервисам внутри среды Compose
Сложные приложения часто состоят из нескольких сервисов, которые взаимодействуют друг с другом. Compose позволяет легко определить такие приложения. Ниже приведен пример файла docker-compose.yml, определяющего приложение как комбинацию двух сервисов:
version: '3'
services:
webserver:
build: .
ports:
- "80:80"
tty: true
database:
image: postgres
restart: always
Эта конфигурация определяет два сервиса:
•webserver — это основной контейнер сервиса приложения, образы которого взяты из локального Dockerfile;
• database — это контейнер базы данных PostgreSQL с официального образа Docker postgress.
Мы предполагаем, что сервис webserver хочет общаться с сервисом database по сети. Для настройки таких связей необходимо знать IP-адрес сервиса или имя хоста, чтобы их можно было использовать в качестве конфигурации приложения. К счастью, Compose — инструмент, который был разработан именно для таких сценариев, поэтому нам будет намного проще.
Всякий раз, когда вы запускаете среду с помощью команды docker-composeup, Compose создаст выделенную сеть Docker по умолчанию и будет регистрировать все сервисы в данной сети, используя их имена в качестве их имен хостов. Это значит, что сервис webserver может использовать database:5432 для связи с базой данных (5432 — порт PostgreSQL по умолчанию), а также любые другие сервисы, чтобы Compose имел возможность доступа к конечной точке HTTP сервиса веб-сервера http://webserver:80.
Несмотря на то что имена хостов в Compose легко предсказуемы, нежелательно жестко прописывать любые адреса в приложении или его конфигурации. Лучше всего задавать их через переменные среды, которые приложение может считать при запуске. В следующем примере показано, как определить произвольные переменные среды для каждого сервиса в файле docker-compose.yml:
version: '3'
services:
webserver:
build: .
ports:
- "80:80"
tty: true
environment:
- DATABASE_HOSTNAME=database
- DATABASE_PORT=5432
database:
image: postgres
restart: always
Обмен данными между несколькими средами Compose
Если вы создаете систему, состоящую из нескольких независимых сервисов и/или приложений, то наверняка заходите сохранить свой код в нескольких независимых кодовых хранилищах (проектах). Файлы docker-compose.yml для каждого приложения Compose, как правило, хранятся в одном и том же хранилище кода, где и код приложения. Сеть по умолчанию, которую Compose создает для одного приложения, изолирована от сетей других приложений. Итак, что вы можете сделать, если внезапно захотите, чтобы ваши независимые приложения общались друг с другом?
К счастью, такое намерение тоже легко реализуется с помощью Compose. Синтаксис файла docker-compose.yml позволяет определить именованную внешнюю сеть Docker как сеть по умолчанию для всех сервисов, определенных в этой конфигурации. Ниже приведен пример конфигурации, которая определяет внешнюю сеть с именем my-interservice-network:
version: '3'
networks:
default:
external:
name: my-interservice-network
services:
webserver:
build: .
ports:
- "80:80"
tty: true
environment:
- DATABASE_HOSTNAME=database
- DATABASE_PORT=5432
database:
image: postgres
restart: always
Такие внешние сети не управляются Compose, поэтому вам придется создать ее вручную с помощью команды dockernetworkcreate следующим образом:
docker network create my-interservice-network
Сделав это, вы сможете использовать созданную внешнюю сеть в других файлах docker-compose.yml для всех приложений, которые должны иметь свои сервисы, зарегистрированные в той же сети. Ниже приведен пример конфигурации для других приложений, которые смогут взаимодействовать с сервисами database и webserver через my-interservice-network, даже если они не определены в том же файле docker-compose.yml:
version: '3'
networks:
default:
external:
name: my-interservice-network
services:
other-service:
build: .
ports:
- "80:80"
tty: true
environment:
- DATABASE_HOSTNAME=database
- DATABASE_PORT=5432
- WEBSERVER_ADDRESS=http://webserver:80
В следующем разделе рассмотрим популярные инструменты повышения производительности.
Популярные инструменты повышения производительности
«Инструмент повышения производительности» — немного расплывчатый термин. С одной стороны, почти каждый пакет с открытым исходным кодом, появившийся в Интернете, является своего рода усилителем производительности, так как предоставляет готовые к использованию решения для некоторых проблем, и поэтому не нужно тратить время на изобретение велосипеда (в идеале). С другой стороны, можно сказать, что весь Python заточен на производительность, и это тоже, несомненно, верно. Почти все в данном языке и его сообществе словно предназначено для того, чтобы сделать разработку программного обеспечения как можно более продуктивной.
Поскольку написание кода — веселый процесс, многие программисты в свободное время пытаются создать инструменты, которые сделают его еще проще и еще веселее. Данный факт будет использоваться здесь в качестве основы для очень субъективного и ненаучного определения инструмента повышения производительности. Мы будем так называть ту часть программного обеспечения, которая делает разработку проще и веселее.
Инструменты повышения производительности сосредоточены главным образом на определенных элементах процесса разработки, таких как тестирование, отладка и управление пакетами, и не являются основными частями продуктов, которые разрабатываются. В ряде случаев эти инструменты могут даже не упоминаться нигде в кодовой базе проекта, несмотря на их ежедневное использование.
Ранее в этой главе мы уже обсуждали наиболее важные инструменты повышения производительности, pip и venv. В них есть пакеты для решения конкретных проблем, например профилирования и тестирования, и им посвящены отдельные главы в данной книге. Текущий раздел рассказывает о других инструментах, которые действительно стоит упомянуть, но отдельная глава в этой книге под них не отведена.
Пользовательские оболочки Python — ipython, bpython, ptpython и т.д.
Python-программисты тратят много времени на интерактивные сессии интерпретатора. Это допустимо для тестирования небольших фрагментов кода, доступа к документации или даже отладки кода во время выполнения. Обычно интерактивная сессия Python довольно проста и не предоставляет большое количество функций, таких как автозаполнение или помощники по самоанализу кода. К счастью, оболочка Python по умолчанию легко поддается расширению и настройке.
Если вы часто работаете в интерактивной оболочке, то можете легко изменить ее поведение. Python при запуске считывает переменную среды PYTHONSTARTUP и ищет путь к пользовательским сценариям инициализаций. Некоторые дистрибутивы ОС, где Python является встроенным компонентом (например, Linux, macOS), могут быть предварительно настроены на выполнение загрузочного скрипта по умолчанию. Они обычно находятся в домашнем каталоге пользователя под названием .pythonstartup. Эти сценарии часто задействуют модуль readline (основанный на библиотеке Readline GNU) совместно с rlcompleter для того, чтобы обеспечить интерактивное автозаполнение и историю команд.
Если у вас нет скрипта запуска по умолчанию, то вы можете легко создать собственный. Базовый скрипт для истории команд и автозаполнения выглядит просто:
# Файл запуска python
import atexit
import os
try:
import readline
except ImportError:
print("Completion unavailable: readline module not available")
else:
import rlcompleter
# Автозаполнение
readline.parse_and_bind('tab: complete')
# Путь к файлу истории в домашнем каталоге пользователя
# Можно использовать собственный путь
history_file = os.path.join(os.environ['HOME'],
'.python_shell_history')
try:
readline.read_history_file(history_file)
except IOError:
pass
atexit.register(readline.write_history_file, history_file)
del os, history_file, readline, rlcompleter
Создайте этот файл в вашем домашнем каталоге и назовите его .pythonstartup. Затем добавьте переменную PYTHONSTARTUP в вашу среду, используя путь к этому файлу.
Настройка переменной среды PYTHONSTARTUP
Если вы работаете под Linux или macOS, то самый простой способ — создать скрипт запуска в домашней папке. Затем нужно связать его с переменной среды PYTHONSTARTUP, которая задается в скрипте запуска оболочки. Например, в оболочках Bash и Korn используется файл .profile, в который вы можете вставить такую строку:
export PYTHONSTARTUP=~/.pythonstartup
Если вы работаете под Windows, то легко установить новую переменную среды с правами администратора в настройках системы, а затем сохранить скрипт в общем месте, а не задействовать заданное пользователем местоположение.
Писать скрипт для PYTHONSTARTUP — хорошее упражнение, но создавать хорошие пользовательские оболочки в одиночку бывает сложно и немногие разработчики находят на это время. К счастью, существует несколько реализаций пользовательской оболочки Python, которые позволяют чрезвычайно упростить интерактивные сессии в Python.
IPython
Оболочка IPython (ipython.readthedocs.io/en/stable/overview.html) имеет встроенную расширенную командную оболочку Python. Среди функций, которые она предоставляет, наиболее интересны следующие:
• динамический анализ объектов;
• доступ к оболочке системы через командную строку;
• прямая поддержка профилирования;
• работа с отладочными средствами.
Теперь IPython является частью более крупного проекта под названием Jupyter, предоставляющего интерактивные заметки/ноутбуки с кодом, который можно в них же выполнять и который можно записать на разных языках.
bpython
Оболочка bpython (bpython-interpreter.org) позиционирует себя как крутой интерфейс для интерпретатора Python. Вот некоторые из ее функций, перечисленных на странице проекта:
• подсветка синтаксиса;
• построчное автозаполнение кода с отображением вариантов при вводе;
• список передаваемых параметров для любой функции Python;
• автоотступы;
• поддержка Python 3.
ptpython
Оболочка ptpython (github.com/jonathanslenders/ptpython/) — иной подход к современным оболочкам Python. Интересно в данном проекте то, что реализация основных функций доступна в виде отдельного пакета, называемого prompt_toolkit (от того же автора). Это позволяет легко создавать различные эстетически приятные интерактивные интерфейсы командной строки.
Эту оболочку часто по функциональности сравнивают с bpython, но основное отличие состоит в том, что она позволяет работать в режиме совместимости с IPython и в ее синтаксисе есть дополнительные функции, такие как %pdb, %cpaste и %profile.
Включение оболочек в собственные скрипты и программы
Иногда возникает необходимость встроить цикл read-eval-print (REPL), похожий на интерактивную сессию Python, в ваше ПО. Это облегчает экспериментирование с кодом и его проверку «изнутри». Самый простой модуль, который позволяет эмулировать интерактивный интерпретатор Python, входит в стандартную библиотеку и называется code.
Скрипт, который запускает интерактивную сессию, состоит из одного импорта и вызова функции:
import code
code.interact()
Вы можете легко внести в него незначительные изменения, например сменить значение ввода или добавить какие-нибудь сообщения, но для вещей покруче потребуется намного больше работы. Если вы хотите получить больше возможностей, например подсветку кода, завершение выполнения или прямой доступ к системной оболочке, то всегда лучше использовать что-то созданное кем-то ранее. К счастью, все интерактивные оболочки, упомянутые выше, можно встроить в вашу программу так же легко, как модуль code.
Ниже приведены примеры того, как можно сослаться на все ранее упомянутые оболочки внутри вашего кода:
# Пример для IPython
import IPython
IPython.embed()
# Пример для bpython
import bpython
bpython.embed()
# Пример для ptpython
from ptpython.repl import embed
embed(globals(), locals())
Интерактивные отладчики
Отладка кода — неотъемлемая часть процесса разработки ПО. Многие программисты проводят большую часть своей жизни, используя только логи и операторы print в качестве основного средства отладки, но большинство профессиональных разработчиков предпочитают задействовать какой-нибудь отладчик.
Python поставляется с уже встроенным интерактивным отладчиком под названием pdb (см. docs.python.org/3/library/pdb.html). Его можно вызвать из командной строки в скрипте, чтобы Python запустил постмортем-отладку, если программа завершается аварийно:
python -m pdb script.py
Постмортем-отладка хоть и полезна, но не универсальна. Она полезна, только когда приложение завершается с исключением, если происходит ошибка. Часто некорректный код просто ведет себя неправильно, но не завершается ошибкой. В таких случаях можно установить пользовательские точки останова (брейкпойнты) в конкретных строках кода с помощью вот такой строки:
import pdb; pdb.set_trace()
Это заставит интерпретатор Python начать сеанс отладки во время выполнения с этой строки.
Отладчик pdb очень полезен для отслеживания проблем и на первый взгляд может показаться знакомым тем, кто работал с GNU Debugger (GDB). Поскольку Python — динамический язык, сессия отладки pdb часто бывает похожа на обычную сессию интерпретатора. Это значит, что разработчик не ограничивается отслеживанием выполнения кода, а может вызвать любой код и даже выполнить импорт модуля.
К сожалению, первый опыт работы с pdb может быть немного шокирующим из-за наличия коротких команд отладчика, таких как h, b, s, n, j и r. Если сомневаетесь, используйте команду helppdb, которую можно вводить во время сеанса отладчика, — она позволит получить немало дополнительной информации.
Сессия отладки в pdb выглядит очень просто и не дает дополнительных функций, таких как автозаполнение или подсветка кода. К счастью, в PyPI есть несколько пакетов, которые предоставляют такие функции из альтернативных оболочек Python, уже упоминавшихся выше. Наиболее известные примеры:
•ipdb — это отдельный пакет на основе ipython;
• ptpdb — отдельный пакет на основе ptpython;
•bpdb — идет в комплекте с bpython.
Резюме
Эта глава была целиком посвящена средам разработки для Python-программистов. Мы обсудили важность изоляции окружающей среды для проектов Python. Вы узнали о двух различных уровнях изоляции среды (уровень приложения и уровень системы), а также о нескольких инструментах, которые позволяют создавать их воспроизводимыми и целостными. В конце главы мы привели обзор нескольких инструментов, позволяющих улучшить экспериментирование с Python и отладку программ.
Теперь, когда в вашем арсенале есть все эти инструменты, вы готовы изучить следующие несколько глав, в которых мы обсудим особенности современного синтаксиса Python.
В следующей главе мы рассмотрим практические рекомендации по написанию кода на Python (идиомы языка) и приведем краткое описание отдельных элементов синтаксиса Python, которые могут быть новыми для «середнячков» Python, но знакомы тем, кто работал с более старыми версиями языка.
Кроме того, мы рассмотрим внутренние реализации CPython и их вычислительные сложности в качестве обоснования для этих идиом.
