<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Питонические атаки</title><link href="https://semakin.dev/" rel="alternate"></link><link href="https://semakin.dev/feeds/all.atom.xml" rel="self"></link><id>https://semakin.dev/</id><updated>2021-05-23T00:19:20+05:00</updated><subtitle>Про разработку в целом и про Python в частности</subtitle><entry><title>Настройка точечного обхода блокировок на роутере с OpenWRT</title><link href="https://semakin.dev/2020/12/openwrt_vpn_rkn/" rel="alternate"></link><published>2020-12-12T21:17:11+05:00</published><updated>2021-05-23T00:19:20+05:00</updated><author><name>Андрей Семакин</name></author><id>tag:semakin.dev,2020-12-12:/2020/12/openwrt_vpn_rkn/</id><summary type="html">&lt;p&gt;Как перестать думать о блокировках сайтов и начать жить&lt;/p&gt;</summary><content type="html">&lt;p&gt;Думаю, не стоит объяснять, почему проблема блокировок в рунете стоит настолько остро.
И дело даже не в пресловутых сериалах на торрентах. Если не обходить
блокировки, то банально сложно работать: то &lt;a href="https://bugs.python.org"&gt;bugs.python.org&lt;/a&gt;
заблокирован, то документация по нужному тебе сервису недоступна, то какой-нибудь
репозиторий с пакетами отвалился, из-за чего у тебя внезапно перестают собираться
контейнеры, то &lt;a href="https://linkedin.com/"&gt;LinkedIn&lt;/a&gt; пишет письма на почту,
а зайти на него ты не можешь. Например, вот
&lt;a href="https://web.archive.org/web/20200623061842/https://wiki.opennet.ru/BlockedRKN"&gt;здесь&lt;/a&gt;
можно найти список опенсорс-проектов, которые были заблокированы Роскомнадзором.
Это всё тянется еще с тех времён в 2018 году, когда
&lt;a href="https://roskomsvoboda.org/38434/"&gt;Роскомнадзор пытался накрыть Telegram ковровыми блокировками&lt;/a&gt;, блокируя без разбора всех направо и налево.
Какое-то время я пытался с этим жить, игнорируя проблему, но однажды мое терпение
лопнуло, во мне пробудился
&lt;a href="https://usher2.club/articles/msg-digitalresistance/"&gt;дух цифрового сопротивления&lt;/a&gt;,
и я решил настроить себе VPN — да так, чтобы это работало на всех моих устройствах сразу.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Resistance Dog Good Boy" src="https://semakin.dev/static/resistance_dog.png"&gt;&lt;/p&gt;
&lt;p&gt;На данный момент случайно заблокированных сайтов уже стало сильно меньше,
чем пару лет назад, но мне так понравилось не думать об этих блокировках вообще,
что я и дальше буду их обходить. Пожалуй, всегда.
Не питаю никакого оптимизма по поводу людей, которые управляют этим рубильником,
и не хочу зависеть от их спонтанных капризов.&lt;/p&gt;
&lt;p&gt;Так как большая часть моего трафика всё-таки ходит к незаблокированной части
интернета, то логично пускать через VPN лишь тот трафик, который в этом нуждается.
Ну нет ведь смысла гонять через VPN просмотр видео на YouTube или какую-нибудь игру?
В итоге получится просто более медленный доступ, а профита никакого.&lt;/p&gt;
&lt;p&gt;Стоит отметить, что если вам нужен обход блокировок только на одном устройстве,
там вам намного проще будет настроить VPN именно на нём. У многих провайдеров
VPN есть программы-клиенты, которые в несколько кликов по красивым кнопкам настроят
вам всё, что нужно — просто погуглите, сравните цены и условия.
Ещё можете посмотреть в сторону таких бесплатных проектов, как
&lt;a href="https://antizapret.prostovpn.org/"&gt;АнтиЗапрет&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Опишу свой процесс настройки роутера на OpenWRT с точечным обходом блокировок.
Мне уже приходилось делать это пару раз (то переезд со сменой провайдера, то смена
роутера), и вот на третий раз я решил наконец задокументировать этот процесс,
чтобы в следующие разы было легче. Может и ещё кому пригодится.&lt;/p&gt;
&lt;h2&gt;Роутер и прошивка&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://openwrt.org/"&gt;OpenWRT&lt;/a&gt; — это опенсорсный проект, который разрабатывает
прошивки для роутеров на основе Linux. В той или иной мере, пожалуй,
&lt;a href="https://openwrt.org/supported_devices"&gt;поддерживаются&lt;/a&gt; все существующие девайсы.
Внутри есть даже свой собственный пакетный менеджер.
Так как роутер с OpenWRT — это почти настоящая линукс-машина, то открывается
огромное поле для различных извращений — файловые шары, торрент-клиенты,
блокировка рекламы на уровне роутера, VPN — да что угодно.&lt;/p&gt;
&lt;p&gt;Мой нынешний роутер — это Xiaomi Mi WiFi Router 3G. Выбрал его, потому что в
нём достаточно мощи, чтобы на нём хорошо работала OpenWRT. Да и вообще,
Mir3G — это, похоже, один из самых популярных девайсов в тусовке людей, которые
занимаются дрессировкой роутеров, так что в сети по нему уже есть
&lt;a href="https://4pda.ru/forum/index.php?showtopic=837667"&gt;много мануалов и обсуждений на форумах&lt;/a&gt;.
Если захотите купить такой же, то будьте аккуратны с конкретной моделью — их две
под одним названием, а хорошая только
&lt;a href="https://4pda.ru/forum/index.php?showtopic=837667&amp;amp;st=14800#entry89639151"&gt;та, у которой есть порт USB3&lt;/a&gt;.
Такой роутер должно быть несложно купить на Авито или других досках объявлений.&lt;/p&gt;
&lt;p&gt;У меня на данный момент установлена почти последняя версия прошивки:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;OpenWrt 19.07.3, r11063-85e04e9f46
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Прошивка OpenWRT и базовая настройка роутера выходит за рамки этой статьи.
Поищите мануалы для вашего роутера. Далее предполагается, что у вас уже есть роутер с
OpenWRT, подключенный к интернету, и к нему
&lt;a href="https://openwrt.org/docs/guide-quick-start/sshadministration"&gt;настроен доступ по SSH&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Блокировки&lt;/h2&gt;
&lt;p&gt;Грубо говоря, существует два типа блокировок:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;по доменным именам;&lt;/li&gt;
&lt;li&gt;по IP-адресам.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Нам нужно победить оба. Разберёмся с каждым из них по порядку.&lt;/p&gt;
&lt;h2&gt;Шаг 1. Шифрованный DNS&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://ru.wikipedia.org/wiki/DNS"&gt;DNS (Domain Name System)&lt;/a&gt; — это распределенная
система (нет, это не сеть магазинов), состоящая из множества серверов по всему
миру, которая позволяет вашему компьютеру преобразовать человекочитаемое имя сайта
в машиночитаемый
&lt;a href="https://ru.wikipedia.org/wiki/IP-%D0%B0%D0%B4%D1%80%D0%B5%D1%81"&gt;IP-адрес&lt;/a&gt;,
например, из &lt;code&gt;github.com&lt;/code&gt; получить &lt;code&gt;140.82.121.3&lt;/code&gt;.
Этот процесс называется "разрешением" или "резолвингом" доменного имени.&lt;/p&gt;
&lt;p&gt;Блокировка по доменным именам зачастую реализуется провайдером следующим образом:
ваш роутер по умолчанию использует DNS-сервер, который контролируется провайдером
и для заблокированных доменных имён возвращает специальный адрес сайта-заглушки.
Именно это происходит, когда вы видите в браузере
"доступ к ресурсу ограничен в соответствии со 149-ФЗ".&lt;/p&gt;
&lt;p&gt;&lt;img alt="Доступ ограничен" src="https://semakin.dev/static/resource_is_blocked.png"&gt;&lt;/p&gt;
&lt;p&gt;Кажется, что решение очевидно — просто не пользоваться DNS-сервером провайдера,
а использовать, например,
&lt;a href="https://ru.wikipedia.org/wiki/Google_Public_DNS"&gt;Google DNS&lt;/a&gt;, IP-адреса которого
уже стоит знать наизусть как считалку — &lt;code&gt;8.8.8.8, 8.8.4.4&lt;/code&gt;.
Раньше это действительно работало, но сегодня провайдеры уже не дадут вам так
просто себя обмануть.&lt;/p&gt;
&lt;p&gt;Поскольку DNS — это очень старый протокол, в котором никак не было предусмотрено
шифрование от слова "вообще", у провайдеров есть возможность перехватывать ваши
запросы и подменять ответы, вставляя свой сайт-заглушку, даже если запрос шёл,
например, к Google DNS. Именно этим они и занимаются. По сути, провайдер
проводит против вас атаку &lt;a href="https://ru.wikipedia.org/wiki/DNS_hijacking"&gt;DNS hijacking&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Вот как это работает, если всё хорошо. В таком сценарии можно даже не заметить этого
&lt;a href="https://ru.wikipedia.org/wiki/%D0%90%D1%82%D0%B0%D0%BA%D0%B0_%D0%BF%D0%BE%D1%81%D1%80%D0%B5%D0%B4%D0%BD%D0%B8%D0%BA%D0%B0"&gt;"человека посередине"&lt;/a&gt;
в лице вашего провайдера:&lt;/p&gt;
&lt;p&gt;&lt;img alt="DNS-запрос к незаблокированному домену" src="https://semakin.dev/static/dns_blocking_1.png"&gt;&lt;/p&gt;
&lt;p&gt;А вот так это работает, когда всё плохо.
Провайдер наверняка даже не отправляет запрос к DNS-серверу, которому он предназначался,
а просто сразу же возвращает ответ, подставляя адрес своей заглушки:&lt;/p&gt;
&lt;p&gt;&lt;img alt="DNS-запрос к заблокированному домену" src="https://semakin.dev/static/dns_blocking_2.png"&gt;&lt;/p&gt;
&lt;p&gt;Но по-настоящему эффективное решение есть. Если провайдер не сможет увидеть, к
какому домену происходит обращение, то не сможет и подменить ответ. А ещё лучше,
если он вообще не будет знать, что происходит резолвинг имени. Этого можно достичь
шифрованием.&lt;/p&gt;
&lt;p&gt;Совсем недавно стали появляться шифрованные версии протокола DNS —
&lt;a href="https://en.wikipedia.org/wiki/DNSCrypt"&gt;DNSCrypt&lt;/a&gt;,
&lt;a href="https://ru.wikipedia.org/wiki/DNS_%D0%BF%D0%BE%D0%B2%D0%B5%D1%80%D1%85_TLS"&gt;DoT (DNS-over-TLS)&lt;/a&gt;
и &lt;a href="https://ru.wikipedia.org/wiki/DNS_%D0%BF%D0%BE%D0%B2%D0%B5%D1%80%D1%85_HTTPS"&gt;DoH (DNS-over-HTTPS)&lt;/a&gt;.
Судя по всему, именно последний из них (DoH) получит наибольшую популярность.
При использовании этого протокола DNS-запрос складывается внутрь HTTPS-запроса,
который, само собой, зашифрован. Для человека посередине, который перехватит
такой трафик, это будет выглядеть как просто один из миллиарда шифрованных
HTTP-запросов к Google или Cloudflare.&lt;/p&gt;
&lt;p&gt;DNS-over-HTTPS мы и собираемся настроить в первую очередь.
Ничего принципиально нового я не скажу, вся информация взята с
&lt;a href="https://openwrt.org/docs/guide-user/services/dns/doh_dnsmasq_https-dns-proxy"&gt;соответствующей страницы вики OpenWRT&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openwrt.org/ru/docs/guide-quick-start/sshadministration"&gt;Подключимся к роутеру по SSH&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;~ $ ssh root@192.168.1.1
root@192.168.1.1&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;s password:
BusyBox v1.30.1 &lt;span class="o"&gt;()&lt;/span&gt; built-in shell &lt;span class="o"&gt;(&lt;/span&gt;ash&lt;span class="o"&gt;)&lt;/span&gt;
_______                     ________        __
&lt;span class="p"&gt;|&lt;/span&gt;       &lt;span class="p"&gt;|&lt;/span&gt;.-----.-----.-----.&lt;span class="p"&gt;|&lt;/span&gt;  &lt;span class="p"&gt;|&lt;/span&gt;  &lt;span class="p"&gt;|&lt;/span&gt;  &lt;span class="p"&gt;|&lt;/span&gt;.----.&lt;span class="p"&gt;|&lt;/span&gt;  &lt;span class="p"&gt;|&lt;/span&gt;_
&lt;span class="p"&gt;|&lt;/span&gt;   -   &lt;span class="o"&gt;||&lt;/span&gt;  _  &lt;span class="p"&gt;|&lt;/span&gt;  -__&lt;span class="p"&gt;|&lt;/span&gt;     &lt;span class="o"&gt;||&lt;/span&gt;  &lt;span class="p"&gt;|&lt;/span&gt;  &lt;span class="p"&gt;|&lt;/span&gt;  &lt;span class="o"&gt;||&lt;/span&gt;   _&lt;span class="o"&gt;||&lt;/span&gt;   _&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="p"&gt;|&lt;/span&gt;_______&lt;span class="o"&gt;||&lt;/span&gt;   __&lt;span class="p"&gt;|&lt;/span&gt;_____&lt;span class="p"&gt;|&lt;/span&gt;__&lt;span class="p"&gt;|&lt;/span&gt;__&lt;span class="o"&gt;||&lt;/span&gt;________&lt;span class="o"&gt;||&lt;/span&gt;__&lt;span class="p"&gt;|&lt;/span&gt;  &lt;span class="p"&gt;|&lt;/span&gt;____&lt;span class="p"&gt;|&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt;__&lt;span class="p"&gt;|&lt;/span&gt; W I R E L E S S   F R E E D O M
-----------------------------------------------------
OpenWrt &lt;span class="m"&gt;19&lt;/span&gt;.07.3, r11063-85e04e9f46
-----------------------------------------------------
root@OpenWrt:~#
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Обновим список пакетов. Эту команду нужно выполнять после каждой перезагрузки роутера
перед установкой пакетов:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;opkg update
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Установим необходимые зависимости, которые в связке позволят принимать DNS-запросы
от клиентов в локальной сети и отправлять их во внешний интернет,
замаскированными под HTTPS-запросы. Третий пакет — это просто плагин для веб-интерфейса
OpenWRT LuCI, чтобы можно было через GUI настраивать поведение DoH:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;opkg install dnsmasq https-dns-proxy luci-app-https-dns-proxy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Чтобы плагин для LuCI заработал, нужно сделать выполнить вот такую команду:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;/etc/init.d/rpcd restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Готово! Теперь на компьютере, подключенном к роутеру,
можно проверить как это работает:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# это было до настройки&lt;/span&gt;
$ nslookup linkedin.com
...
Name:   linkedin.com
Address: &lt;span class="m"&gt;95&lt;/span&gt;.213.158.61

&lt;span class="c1"&gt;# это после настройки&lt;/span&gt;
$ nslookup linkedin.com
...
Name:   linkedin.com
Address: &lt;span class="m"&gt;13&lt;/span&gt;.107.42.14
Name:   linkedin.com
Address: &lt;span class="m"&gt;2620&lt;/span&gt;:1ec:21::14
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Обратите внимание, что адреса возвращаются разные:
до настройки DoH — неправильный, это результат подмены адресов провайдером,
а после — правильный. Можно считать это маленькой победой!&lt;/p&gt;
&lt;p&gt;По умолчанию используются DNS-over-HTTPS сервера от Google и Cloudflare —
очень разумный выбор, потому что на этих двух компаниях держится добрая половина
глобального интернета, и их блокировка наверняка приведёт к катастрофическим
последствиям, так что блокировать их, надеюсь, никто не будет.
Если же хочется сменить используемые сервера, то сделать это теперь можно
через веб-интерфейс: &lt;code&gt;Services &amp;gt; DNS HTTPS Proxy&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="OpenWRT LuCI DoH settings" src="https://semakin.dev/static/openwrt_luci_doh_settings.png"&gt;&lt;/p&gt;
&lt;p&gt;Сам по себе обход блокировок по DNS вряд ли даст ожидаемый эффект, потому что,
как правило, в довесок к доменному имени, ресурс блокируется еще и по IP-адресам
— видимо, для надежности. Это следующий тип блокировок, которые нужно победить.&lt;/p&gt;
&lt;p&gt;Стоит упомянуть про побочные эффекты от такой настройки — могут стать недоступными
различные внутренние ресурсы провайдера. Например, когда у меня кончается интернет
и я забываю его оплатить, провайдер просто не может меня переадресовать на
страницу, куда он обычно направляет своих забывчивых абонентов. Для меня выглядит
это всё так, будто интернет просто пропал, без каких-либо объяснений. Я даже первый
раз из-за этого чуть не поругался с поддержкой, а оказалось, что это я сам дурак.
Что ж, просто приходится платить заранее.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Если нравится статья, то
&lt;a href="https://semakin.dev/pages/subscribe/"&gt;подпишитесь на уведомления&lt;/a&gt;
о новых постах в блоге, чтобы ничего не пропустить!
А ещё читайте другие мои посты!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Шаг 2. Настройка VPN WireGuard&lt;/h2&gt;
&lt;p&gt;Для того, чтобы обходить блокировки по IP-адресам, придётся настроить VPN —
другого способа нет. VPN "спрячёт" от провайдера ваши IP-пакеты так, что он
не будет знать куда именно они направляются и что в себе содержат.
В качестве протокола предлагаю использовать WireGuard — легковесный VPN-протокол,
который к тому же довольно легко настраивать.&lt;/p&gt;
&lt;p&gt;Всё описанное дальше вдохновлено и по сути является пересказом вот
&lt;a href="https://habr.com/ru/post/440030/"&gt;этой замечательной статьи&lt;/a&gt;
на Хабре с некоторыми (минимальными) дополнениями от меня.&lt;/p&gt;
&lt;p&gt;VPN — это клиент-серверный протокол. Наш роутер будет клиентом, но также нам
обязательно нужен сервер. Можно либо поднять свой VPN-сервер самостоятельно
(смотрите инструкцию в статье по ссылке выше, это не сложно), либо оформить
подписку на какой-нибудь готовый VPN-сервис. Я пробовал оба варианта, но
поддерживать свой VPN-сервер оказалось достаточно трудозатратно, и это тоже,
как ни странно, стоит денег. Сложнее всего найти хостинг для своего VPN с
незаруиненной репутацией. Проблема, с которой я столкнулся, заключалась в
том, что все дешевые хостинги уже имеют плохую репутацию (спасибо вам,
господа спамеры), и некоторые ресурсы, типа Пикабу (который тоже, как выяснилось,
немножко заблокирован), сразу же заранее отвечают ошибкой 403.
Намного проще купить готовый VPN, и пусть лучше кто-то другой за меня
беспокоится о том, чтобы мне были доступны все нужные мне сайты.&lt;/p&gt;
&lt;p&gt;В последнее время появляется очень много VPN-сервисов, которые работают по
протоколу WireGuard — это нынче горячая тема. Недавно
&lt;a href="https://www.opennet.ru/opennews/art.shtml?num=52275"&gt;поддержка WireGuard была добавлена в основной состав ядра Linux&lt;/a&gt;.
Погуглите, выбор сервисов действительно есть. Сейчас я пользуюсь
&lt;a href="https://redshieldvpn.com/?r=d4KqAm7U6rHB4kdEwuDD"&gt;RedShieldVPN&lt;/a&gt;, и меня пока
что всё устраивает.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Бонус для читателей&lt;/strong&gt;: при регистрации
&lt;a href="https://redshieldvpn.com/?r=d4KqAm7U6rHB4kdEwuDD"&gt;по этой ссылке&lt;/a&gt; вам подарят
месяц бесплатного VPN.&lt;/p&gt;
&lt;p&gt;Дальше буду описывать процесс для RedShieldVPN, но, думаю, для других подобных
сервисов процесс должен быть похожим.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Переходим &lt;a href="https://my.redshieldvpn.com/manual/wg"&gt;в раздел WireGuard&lt;/a&gt;,
    авторизуемся, выбираем страну и скачиваем конфиг. Я обычно выбираю какую-нибудь
    европейскую страну, чтобы пинг был приемлемым. В этот раз возьму Финляндию.&lt;/p&gt;
&lt;p&gt;Вот примерно такой файл конфигурации скачался (ключи я заменил):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[Interface]&lt;/span&gt;
&lt;span class="na"&gt;Address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10.7.1.177/32, 6343:72e7:7ac3:e245:2675:1b79:4638:193a/128&lt;/span&gt;
&lt;span class="na"&gt;PrivateKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;private_key_goes_here&lt;/span&gt;
&lt;span class="na"&gt;DNS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10.254.254.254&lt;/span&gt;
&lt;span class="na"&gt;MTU&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1412&lt;/span&gt;

&lt;span class="k"&gt;[Peer]&lt;/span&gt;
&lt;span class="na"&gt;PublicKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;public_key_goes_here&lt;/span&gt;
&lt;span class="na"&gt;AllowedIPs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0/0, ::/0&lt;/span&gt;
&lt;span class="na"&gt;Endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;fin.lopata.today:51820&lt;/span&gt;
&lt;span class="na"&gt;PersistentKeepalive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Переключимся на роутер и установим там нужную зависимость:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;opkg install wireguard
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Дальше нужно настроить сеть. Это происходит в файле &lt;code&gt;/etc/config/network&lt;/code&gt;.
Открываем его при помощи &lt;code&gt;vi&lt;/code&gt; (если не умеете пользоваться этим редактором, то
лучше предварительно
&lt;a href="https://losst.ru/kak-polzovatsya-tekstovym-redaktorom-vim"&gt;почитайте что-нибудь&lt;/a&gt;
для подготовки психики) и дописываем в конец две секции вот такого содержания:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;config interface &amp;#39;wg0&amp;#39;
    option private_key &amp;#39;private_key_goes_here&amp;#39;
    list addresses &amp;#39;10.7.1.177/32&amp;#39;
    option listen_port &amp;#39;51820&amp;#39;
    option proto &amp;#39;wireguard&amp;#39;

config wireguard_wg0
    option public_key &amp;#39;public_key_goes_here&amp;#39;
    option allowed_ips &amp;#39;0.0.0.0/0&amp;#39;
    option route_allowed_ips &amp;#39;0&amp;#39;
    option persistent_keepalive &amp;#39;10&amp;#39;
    option endpoint_host &amp;#39;fin.lopata.today&amp;#39;
    option endpoint_port &amp;#39;51820&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Приватный и публичный ключи замените соответствующими значениями из конфига.
Поле &lt;code&gt;list addresses&lt;/code&gt; заполняется IPv4-адресом из строчки &lt;code&gt;Address&lt;/code&gt; в конфиге,
а IPv6 адрес мы просто игнорируем — позже станет понятно почему.
Поле &lt;code&gt;Endpoint&lt;/code&gt; из конфига WireGuard распадается, соответственно,
в поля &lt;code&gt;endpoint_host&lt;/code&gt; и &lt;code&gt;endpoint_port&lt;/code&gt; в конфиге OpenWRT.
Остальные поля статичные.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Перезапустим сеть на роутере:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;/etc/init.d/network restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Проверим VPN:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# wg show&lt;/span&gt;
interface: wg0
public key: C4zR9CoziizrJcPFtcUg4yyyG1S6+a+/bAuGi2BpmlM&lt;span class="o"&gt;=&lt;/span&gt;
private key: &lt;span class="o"&gt;(&lt;/span&gt;hidden&lt;span class="o"&gt;)&lt;/span&gt;
listening port: &lt;span class="m"&gt;51820&lt;/span&gt;

peer: public_key_goes_here
endpoint: &lt;span class="m"&gt;185&lt;/span&gt;.103.110.133:51820
allowed ips: &lt;span class="m"&gt;0&lt;/span&gt;.0.0.0/0
latest handshake: &lt;span class="m"&gt;33&lt;/span&gt; seconds ago
transfer: &lt;span class="m"&gt;1012&lt;/span&gt; B received, &lt;span class="m"&gt;3&lt;/span&gt;.53 KiB sent
persistent keepalive: every &lt;span class="m"&gt;10&lt;/span&gt; seconds
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Если команда &lt;code&gt;wg show&lt;/code&gt; выводит что-то подобное, то всё идёт хорошо.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Шаг 3. Скрипт для сбора списков заблокированных адресов&lt;/h2&gt;
&lt;p&gt;Это возможно благодаря крутейшему сервису
&lt;a href="https://antifilter.download/"&gt;antifilter.download&lt;/a&gt;, который обрабатывает выгрузки
Роскомнадзора и отдает их в виде списков IP-адресов в машиночитаемых форматах.
Мы настроим периодическую выгрузку списков заблокированных адресов с antifilter.download,
чтобы всегда иметь актуальную информацию о блокировках.
По моему опыту обновлять этот список раз в сутки (например, ночью) вполне достаточно.&lt;/p&gt;
&lt;p&gt;Кроме того, нам нужно настроить файрволл роутера, чтобы он помечал пакеты,
которые направляются к заблокированным адресам, специальным маркером.
Такие маркированные пакеты должны маршрутизироваться роутером через VPN.&lt;/p&gt;
&lt;p&gt;&lt;img alt="вот так будут бегать пакетики" src="https://habrastorage.org/webt/vy/rc/ji/vyrcjihaaozo-vzf1nkm9nlxpl0.gif"&gt;&lt;/p&gt;
&lt;p&gt;Начнём со скрипта, который реализует эту логику по обновлению списков заблокированных адресов.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Установим зависимости:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;opkg install ipset curl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Создадим файл &lt;code&gt;/etc/init.d/hirkn&lt;/code&gt; со скриптом следующего содержания:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="nv"&gt;START&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;
&lt;span class="nv"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/lst
mkdir -p &lt;span class="nv"&gt;$dir&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Downloading lists of blocked addresses...&amp;quot;&lt;/span&gt;
curl -z &lt;span class="nv"&gt;$dir&lt;/span&gt;/subnet.lst https://antifilter.download/list/subnet.lst --output &lt;span class="nv"&gt;$dir&lt;/span&gt;/subnet.lst
curl -z &lt;span class="nv"&gt;$dir&lt;/span&gt;/ipsum.lst https://antifilter.download/list/ipsum.lst --output &lt;span class="nv"&gt;$dir&lt;/span&gt;/ipsum.lst
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Restarting firewall...&amp;quot;&lt;/span&gt;
/etc/init.d/firewall restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Этот скрипт скачивает два списка заблокированных адресов — один в виде сетей
(иногда РКН блокирует адреса целыми подсетями), другой — обобщенный список
отдельных заблокированных адресов. За счёт обобщения в небольшие подсети он содержит
не миллионы строк, а всего лишь что-то около 15 тысяч.
Нам нужны оба списка, они не пересекаются между собой. Файлы сохраняются
в каталог &lt;code&gt;/tmp&lt;/code&gt;, который в OpenWRT хранится в оперативной памяти — это должно
работать быстро. В конце скрипт перезапускает файлволл.
Настройка файлволла будет происходит позже.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Добавим этому скрипту права на выполнение:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;chmod +x /etc/init.d/hirkn
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Добавим скрипт в "автозапуск", чтобы он выполнялся при включении роутера после
    всех остальных скриптов:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ln -s /etc/init.d/hirkn /etc/rc.d/S99hirkn
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Через &lt;code&gt;cron&lt;/code&gt; запланируем выполнение этого скрипта раз в сутки:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;crontab -e
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;В открывшемся редакторе нужно добавить на новой строке:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;0 4 * * * /etc/init.d/hirkn
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;На &lt;a href="https://crontab.guru/#0_4_*_*_*"&gt;crontab.guru&lt;/a&gt; можно посмотреть объяснение этой
строчки.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Включим &lt;code&gt;cron&lt;/code&gt;, потому что по умолчанию в OpenWRT он выключен:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;/etc/init.d/cron &lt;span class="nb"&gt;enable&lt;/span&gt;
/etc/init.d/cron start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Убедимся, что скрипт работает.&lt;/p&gt;
&lt;p&gt;Запустим его:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;/etc/init.d/hirkn
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Если файлы со списками заблокированных адресов появились, то всё ок:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# ls -lh /tmp/lst/&lt;/span&gt;
-rw-r--r--    &lt;span class="m"&gt;1&lt;/span&gt; root     root      &lt;span class="m"&gt;270&lt;/span&gt;.4K Dec  &lt;span class="m"&gt;7&lt;/span&gt; &lt;span class="m"&gt;07&lt;/span&gt;:18 ipsum.lst
-rw-r--r--    &lt;span class="m"&gt;1&lt;/span&gt; root     root         &lt;span class="m"&gt;132&lt;/span&gt; Dec  &lt;span class="m"&gt;7&lt;/span&gt; &lt;span class="m"&gt;07&lt;/span&gt;:18 subnet.lst
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Шаг 4. Дальнейшая настройка&lt;/h2&gt;
&lt;p&gt;Теперь осталось настроить роутер так, чтобы он правильно маршрутизировал
пакеты через VPN-туннель.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Создадим таблицу маршрутизации для VPN, добавив в файл
    &lt;code&gt;/etc/iproute2/rt_tables&lt;/code&gt; строчку следующего вида:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;99      vpn
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Добавим дефолтный маршрут для VPN через туннельный интерфейс:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ip route add table vpn default dev wg0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Чтобы сделать это изменение постоянным, чтобы оно переживало перезагрузки роутера,
создадим файл &lt;code&gt;/etc/hotplug.d/iface/30-rknroute&lt;/code&gt; с вот таким содержимым:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;
ip route add table vpn default dev wg0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;В файл &lt;code&gt;/etc/config/network&lt;/code&gt;, который мы уже редактировали в процессе настройки
    WireGuard, добавим правило, которое будет перенаправлять маркированные пакеты
    в таблицу маршрутизации VPN:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;config rule
    option priority &amp;#39;100&amp;#39;
    option lookup &amp;#39;vpn&amp;#39;
    option mark &amp;#39;0x1&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Теперь приступим к настройке файрволла. Это самая важная часть логики, которая
    просматривает списки заблокированных адресов, и маркирует пакеты.
    В файл &lt;code&gt;/etc/config/firewall&lt;/code&gt; допишем несколько абзацев:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;config zone
    option name &amp;#39;wg&amp;#39;
    option family &amp;#39;ipv4&amp;#39;
    option masq &amp;#39;1&amp;#39;
    option output &amp;#39;ACCEPT&amp;#39;
    option forward &amp;#39;REJECT&amp;#39;
    option input &amp;#39;REJECT&amp;#39;
    option mtu_fix &amp;#39;1&amp;#39;
    option network &amp;#39;wg0&amp;#39;

config forwarding
    option src &amp;#39;lan&amp;#39;
    option dest &amp;#39;wg&amp;#39;

config ipset
    option name &amp;#39;vpn_subnets&amp;#39;
    option storage &amp;#39;hash&amp;#39;
    option loadfile &amp;#39;/tmp/lst/subnet.lst&amp;#39;
    option match &amp;#39;dst_net&amp;#39;

config ipset
    option name &amp;#39;vpn_ipsum&amp;#39;
    option storage &amp;#39;hash&amp;#39;
    option loadfile &amp;#39;/tmp/lst/ipsum.lst&amp;#39;
    option match &amp;#39;dst_net&amp;#39;

config rule
    option name &amp;#39;mark_subnet&amp;#39;
    option src &amp;#39;lan&amp;#39;
    option proto &amp;#39;all&amp;#39;
    option ipset &amp;#39;vpn_subnets&amp;#39;
    option set_mark &amp;#39;0x1&amp;#39;
    option target &amp;#39;MARK&amp;#39;

config rule
    option name &amp;#39;mark_ipsum&amp;#39;
    option src &amp;#39;lan&amp;#39;
    option proto &amp;#39;all&amp;#39;
    option ipset &amp;#39;vpn_ipsum&amp;#39;
    option set_mark &amp;#39;0x1&amp;#39;
    option target &amp;#39;MARK&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Первый из них настраивает зону для VPN — из неё разрешён лишь выход пакетов
и включён маскарадинг.&lt;/p&gt;
&lt;p&gt;Второй абзац разрешает переход из зоны &lt;code&gt;lan&lt;/code&gt;, где по умолчанию находятся все
устройства, подключенные к роутеру, в зону для VPN.&lt;/p&gt;
&lt;p&gt;Третий и четвертый абзацы заставляют файрволл загрузить файлы со списками
заблокированных адресов и распихать по
&lt;a href="https://ru.wikipedia.org/wiki/%D0%A5%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D0%B0"&gt;хеш-таблицам&lt;/a&gt;
в памяти.
Как известно, поиск по хеш-таблице происходит за константное время, так что
роутер сможет эффективно определять направляется ли пакет к заблокированному
адресу или к обычному.&lt;/p&gt;
&lt;p&gt;Пятый и шестой абзацы отвечают за маркировку пакетов: "если пакет направляется
на заблокированный адрес, то добавим ему пометку &lt;code&gt;0x1&lt;/code&gt;".&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Перезапустим сеть:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;/etc/init.d/network restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;И запустим наш скрипт:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;/etc/init.d/hirkn
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;После этого трафик к заблокированным сайтам должен побежать через VPN.
    Если нет, то попробуйте перезагрузить роутер.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Шаг 5. Отключаем IPv6&lt;/h2&gt;
&lt;p&gt;Этот шаг мой самый нелюбимый, потому что я за всё новое и против всего старого.
По моему глубокому убеждению IPv4 уже давно должен умереть и быть вытеснен шестой
версией протокола. К сожалению,
&lt;a href="https://www.google.com/intl/en/ipv6/statistics.html#tab=ipv6-adoption"&gt;дела у IPv6 пока идут не так гладко&lt;/a&gt;,
как хотелось бы (сейчас он занимает всего около 30% процентов трафика).&lt;/p&gt;
&lt;p&gt;Проблема в том, что antifilter.download выдаёт только заблокированные IPv4 адреса.
Это значит, что наш обход блокировок не будет работать по IPv6.
Если разрешить вашему роутеру работать по IPv6, то многие ваши устройства предпочтут
открывать сайты по IPv6 либо напарываясь на страницы с блокировками от провайдеров,
либо просто получая ошибки подключения по таймауту.&lt;/p&gt;
&lt;p&gt;Отключаем IPv6 (команды взяты &lt;a href="https://superuser.com/a/1532705/993215"&gt;отсюда&lt;/a&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;uci &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;network.lan.ipv6=off&amp;#39;&lt;/span&gt;
uci &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;network.wan.ipv6=off&amp;#39;&lt;/span&gt;
uci &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;dhcp.lan.dhcpv6=disabled&amp;#39;&lt;/span&gt;
/etc/init.d/odhcpd disable
uci commit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;После этого может потребоваться ещё одна перезагрузка роутера, чтобы он перестал
раздавать вновь подключенным устройствам IPv6-адреса.&lt;/p&gt;
&lt;h1&gt;Заключение&lt;/h1&gt;
&lt;p&gt;Вот как-то так можно при помощи несложных (ладно, сложных) манипуляций настроить
свой роутер на точечный обход блокировок. Все незаблокированные сайты работают как обычно,
а заблокированные — через VPN. С такой конфигурацией можно полностью забыть
про мелкие пакости от Роскомнадзора и начать, наконец, жить :)&lt;/p&gt;
&lt;p&gt;&lt;img alt="Добро пожаловать в Интернет" src="https://semakin.dev/static/welcome_to_the_internet_meme.jpg"&gt;&lt;/p&gt;</content><category term="blog"></category><category term="network"></category><category term="vpn"></category><category term="wiregurad"></category><category term="openwrt"></category></entry><entry><title>Эффективный обход словарей в цикле в Python</title><link href="https://semakin.dev/2020/07/iterate_over_dict/" rel="alternate"></link><published>2020-07-06T13:46:41+05:00</published><updated>2020-07-06T13:46:41+05:00</updated><author><name>Андрей Семакин</name></author><id>tag:semakin.dev,2020-07-06:/2020/07/iterate_over_dict/</id><summary type="html">&lt;p&gt;Расскажу как правильно обойти словарь (dict) в цикле, чтобы получить ключи, значения или и ключи, и значения одновременно, при этом не получив ничего лишнего.&lt;/p&gt;</summary><content type="html">&lt;h1&gt;Эффективный обход словарей в цикле в Python&lt;/h1&gt;
&lt;p&gt;Словарь (dictionary, dict) — это  ассоциативный массив, который позволяет
сохранять значения по ключам.&lt;/p&gt;
&lt;p&gt;Это очень важная, даже можно сказать основополагающая структура данных,
которая используется в Python под капотом буквально повсюду:
модули, классы, объекты, &lt;code&gt;locals()&lt;/code&gt;, &lt;code&gt;globals()&lt;/code&gt; — все это так или иначе
работает лишь благодаря словарям.&lt;/p&gt;
&lt;p&gt;Кроме того, словарь отлично подходит для решения множества прикладных
задач, обладает хорошей
&lt;a href="https://wiki.python.org/moin/TimeComplexity#dict"&gt;вычислительной сложностью операций&lt;/a&gt;,
так что и в вашем коде, наверняка, словари будут встречаться достаточно часто.&lt;/p&gt;
&lt;p&gt;В Python большое внимание уделяется циклам.
Правильно написанный заголовок цикла содержит много ценной информации:
по чему итерируемся и какие данные будут использоваться в теле цикла.
Это помогает читателю понять (или хотя бы предположить), что именно будет
производиться в теле цикла, даже не смотря в него.
Неправильно написанный цикл, который не выражает напрямую задумку автора,
наоборот, сбивает читателя с толку и заставляет читать код целиком,
возможно, даже не один раз.&lt;/p&gt;
&lt;p&gt;Есть несколько способов обойти словарь в цикле.
Очень важно научиться выбирать наиболее подходящий.&lt;/p&gt;
&lt;h1&gt;Что будет если просто попытаться обойти словарь в цикле?&lt;/h1&gt;
&lt;p&gt;Объявим словарь с отношением различных валют к российскому рублю,
который нам по какой-то причине нужно обойти:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;currencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rub&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;usd&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;69.78&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;eur&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;78.28&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Самый очевидный вариант обхода словаря — это попытаться напрямую запустить
цикл &lt;code&gt;for&lt;/code&gt; по объекту словаря, так же как мы делаем это со списками,
кортежами, строками и любыми другими итерируемыми объектами.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;something&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;currencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;something&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Словарь и правда поддерживает протокол итераций, но словарь не так прост,
как другие объекты, которые мы упомянули выше. Словарь состоит из нескольких
частей, ведь словарь — это отношение между ключами и значениями.
Получается, что теоретически цикл по словарю может получать либо ключи,
либо значения, либо пары (ключ, значение). Попробуете угадать, что же именно
выведет код выше?&lt;/p&gt;
&lt;p&gt;А выведет он следующее:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;rub
usd
eur
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;То есть обход словаря в цикле будет возвращать только &lt;strong&gt;ключи&lt;/strong&gt; этого словаря.&lt;/p&gt;
&lt;p&gt;Пожалуй, задать такое поведение по умолчанию — это очень логичное решение со
стороны разработчиков Python. Было бы намного внезапнее, если бы цикл по
словарю получал значения. Вариант с кортежами (ключ, значение) в качестве
поведения по умолчанию мне кажется не таким уж плохим, но имеем то, что имеем.&lt;/p&gt;
&lt;p&gt;Есть куча задач, в которых нужно обойти лишь ключи словаря, и это отличное
решение для таких задач. У этого способа есть один крупный недостаток: нужно
знать как работают словари. По коду совершенно неясно, что будет обходиться в
цикле — ключи, значения или пары, а читатель может либо этого не знать,
либо забыть, и в итоге неправильно интерпретировать код.
Поэтому во избежание неоднозначности даже для обхода ключей словаря я
рекомендую использовать следующий способ.&lt;/p&gt;
&lt;h1&gt;Как обойти в цикле ключи словаря?&lt;/h1&gt;
&lt;p&gt;Давайте представим, что нам нужно нарисовать какую-нибудь таблицу с валютами,
и для создания шапки этой таблицы нужно получить список всех валют.
Значения словаря нас не интересуют, только ключи.&lt;/p&gt;
&lt;p&gt;У словаря есть метод &lt;a href="https://docs.python.org/3/library/stdtypes.html#dict.keys"&gt;&lt;code&gt;.keys()&lt;/code&gt;&lt;/a&gt;,
который возвращает представление словаря (dict view), возвращающее ключи.&lt;/p&gt;
&lt;p&gt;Что такое представление словаря? Это некий объект, который предоставляет
доступ к данным в словаре, либо к части этих данных, и работает по следующим
принципам:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;не копирует содержимое словаря, а обращается к нему динамически,
на больших словарях это здорово экономит память и улучшает скорость
работы программы;&lt;/li&gt;
&lt;li&gt;если словарь изменяется, то эти изменения автоматически становятся доступными
и через представление словаря;&lt;/li&gt;
&lt;li&gt;не является списком, не поддерживает извлечение элементов по индексам;&lt;/li&gt;
&lt;li&gt;является итерируемым объектом, можно использовать в циклах сколько угодно раз.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Создадим такое представление словаря по ключам:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;dict_keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currencies&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dict_keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# dict_keys([&amp;#39;rub&amp;#39;, &amp;#39;usd&amp;#39;, &amp;#39;eur&amp;#39;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Давайте добавим новый ключ в словарь:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;currencies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;jpy&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.65&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dict_keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# dict_keys([&amp;#39;rub&amp;#39;, &amp;#39;usd&amp;#39;, &amp;#39;eur&amp;#39;, &amp;#39;jpy&amp;#39;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Как видите, созданное ранее представление словаря обновилось автоматически,
когда обновился его словарь.&lt;/p&gt;
&lt;p&gt;Обратите внимание, что представление словаря — это не список, а совершенно
другой объект. Представление словаря не поддерживает извлечение значений
по индексам:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;dict_keys&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# Traceback (most recent call last):&lt;/span&gt;
&lt;span class="c1"&gt;#   File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 1, in &amp;lt;module&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;# TypeError: &amp;#39;dict_keys&amp;#39; object is not subscriptable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Зато представление словаря является итерируемым объектом и его без проблем
можно обходить при помощи цикла:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;currencies&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# rub&lt;/span&gt;
&lt;span class="c1"&gt;# usd&lt;/span&gt;
&lt;span class="c1"&gt;# eur&lt;/span&gt;
&lt;span class="c1"&gt;# jpy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Результат тот же самый, что и в предыдущем способе обхода словаря, но в
этот раз явно видно, что в цикле будут обрабатываться только ключи словаря.&lt;/p&gt;
&lt;p&gt;Обратите внимание, что если в цикле вам нужны не только ключи словаря,
но и значения, то обходить словарь таким образом — не самое эффективное решение.
Смотрите дальше, как можно обойти словарь, чтобы получать и ключи, и значения.&lt;/p&gt;
&lt;h1&gt;Как обойти в цикле значения словаря?&lt;/h1&gt;
&lt;p&gt;По аналогии с ключами, из словаря можно извлечь только значения, без ключей.
Это делается через метод словарей
&lt;a href="https://docs.python.org/3/library/stdtypes.html#dict.values"&gt;&lt;code&gt;.values()&lt;/code&gt;&lt;/a&gt;,
который возвращает представление словаря, содержащее только значения.&lt;/p&gt;
&lt;p&gt;Это представление работает по тем же правилам,
что и возвращаемое методом &lt;code&gt;.keys()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Вот как можно обойти в цикле только значения словаря, без ключей:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;currencies&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 1&lt;/span&gt;
&lt;span class="c1"&gt;# 69.78&lt;/span&gt;
&lt;span class="c1"&gt;# 78.28&lt;/span&gt;
&lt;span class="c1"&gt;# 0.65&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;По значениям словаря уже невозможно получить ключи
(ну, вообще можно попытаться, но для этого потребуется полный перебор словаря,
и не факт, что ключи будут восстановлены правильно).
Этот способ подойдёт только если в цикле используются исключительно
значения словаря, а ключи не нужны.&lt;/p&gt;
&lt;h1&gt;Как обойти в цикле и ключи, и значения словаря?&lt;/h1&gt;
&lt;p&gt;Пожалуй, это самый распространённый случай.
Во многих задачах, где выполняется обход словаря, в цикле используются и ключи,
и соответствующие им значения.&lt;/p&gt;
&lt;p&gt;Специально для этого у словарей есть метод
&lt;a href="https://docs.python.org/3/library/stdtypes.html#dict.items"&gt;&lt;code&gt;.items()&lt;/code&gt;&lt;/a&gt;,
который возвращает представление словаря, содержащее кортежи из двух элементов,
вида (ключ, значение).&lt;/p&gt;
&lt;p&gt;Это представление работает по точно таким же правилам,
как &lt;code&gt;.keys()&lt;/code&gt; и &lt;code&gt;.values()&lt;/code&gt;. Единственное отличие этого представления от
предыдущих состоит в том, что оно возвращает не единичные значения,
а кортежи из двух значений.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;currencies&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# item — это кортеж (ключ, значение)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="c1"&gt;# rub 1&lt;/span&gt;
&lt;span class="c1"&gt;# usd 69.78&lt;/span&gt;
&lt;span class="c1"&gt;# eur 78.28&lt;/span&gt;
&lt;span class="c1"&gt;# jpy 0.65&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;В Python есть возможность распаковывать итерируемые объекты, такие как кортежи,
в различные переменные. Давайте на примере посмотрим как это работает:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;point&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 1&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 2&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Таким образом можно распаковывать последовательности любого размера.
Это намного проще, чем извлекать значения по индексам и присваивать в
отдельные переменные. Этот приём можно использовать практически в любом месте
программы, в том числе и в заголовке цикла.&lt;/p&gt;
&lt;p&gt;Вот так можно обойти ключи и значения словаря, сохраняя ключ и значение в
разные переменные прямо в заголовке цикла при помощи распаковки кортежа:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;currencies&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# rub 1&lt;/span&gt;
&lt;span class="c1"&gt;# usd 69.78&lt;/span&gt;
&lt;span class="c1"&gt;# eur 78.28&lt;/span&gt;
&lt;span class="c1"&gt;# jpy 0.65&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;Заключение&lt;/h1&gt;
&lt;p&gt;При обходе словаря стоит руководствоваться следующей логикой:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;если в цикле используются и ключи, и значения словаря,
то нужно использовать метод &lt;code&gt;.items()&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;если в цикле используются только значения словаря,
а ключи не важны, то нужно использовать метод &lt;code&gt;.values()&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;если в цикле нужны ключи словаря и ничего больше,
то нужно использовать метод &lt;code&gt;.keys()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Идеоматичный код проще читается и, как правило, работает быстрее.&lt;/p&gt;
&lt;p&gt;Посмотрите запись классического выступления Реймонда Хеттингера,
где он рассказывает про написание идеоматичного код.
Много внимания уделяется циклам и словарям.&lt;/p&gt;
&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/OSGv2VnC0go" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;Обратите внимание, что это запись выступления от 2013 года,
когда ещё вовсю был в ходу Python 2. В выступлении часто сравнивается Python 2
и Python 3. Не запутайтесь.&lt;/p&gt;
&lt;p&gt;Если понравилась статья, то
&lt;a href="https://semakin.dev/pages/subscribe/"&gt;подпишитесь на уведомления&lt;/a&gt;
о новых постах в блоге, чтобы ничего не пропустить!&lt;/p&gt;
&lt;h1&gt;Ссылки&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://realpython.com/iterate-through-dictionary-python/"&gt;очень подробный разбор по обходу словарей на RealPython&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/library/stdtypes.html#dict-views"&gt;документация про представления словарей&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;и, конечно же, посмотрите выступление Реймонда Хеттингера, обожаю этого чувака.&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"></category><category term="python"></category><category term="dict"></category><category term="loop"></category><category term="for"></category><category term="iterate"></category></entry><entry><title>Аннотации типов в Python</title><link href="https://semakin.dev/2020/06/type_hints/" rel="alternate"></link><published>2020-06-22T23:35:05+05:00</published><updated>2020-06-22T23:35:05+05:00</updated><author><name>Андрей Семакин</name></author><id>tag:semakin.dev,2020-06-22:/2020/06/type_hints/</id><summary type="html">&lt;p&gt;Введение в тайп-аннотации и тайп-чекинг в Python.&lt;/p&gt;</summary><content type="html">&lt;h1&gt;Аннотации типов в Python&lt;/h1&gt;
&lt;p&gt;Python — это язык с сильной динамической типизацией.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D0%B8_%D1%81%D0%BB%D0%B0%D0%B1%D0%B0%D1%8F_%D1%82%D0%B8%D0%BF%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F"&gt;Сильная&lt;/a&gt;
    — значит, что язык не допускает неявных приведений типов в неоднозначных
    ситуациях или когда будет утрачена точность, например, нельзя “сложить”
    число и строку.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ru.wikipedia.org/wiki/%D0%94%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D1%82%D0%B8%D0%BF%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F"&gt;Динамическая&lt;/a&gt;
    — значит, что тип переменной определяется во время присваивания ей значения и
    может изменяться по ходу программы.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Такая система типов — это очень удачный компромисс между простотой разработки
и надежностью написанных программ, но она не лишена недостатков.&lt;/p&gt;
&lt;p&gt;Например, объявления переменных с типами в языках со статической типизацией,
кроме своего основного назначения — инструкций компилятору или интерпретатору,
ещё и помогают программисту лучше понимать написанный код, служат своеобразной
документацией. Динамическая типизация не в состоянии этого дать.&lt;/p&gt;
&lt;p&gt;Раньше, когда на Python писали в основном небольшие скрипты, это не было
такой уж острой проблемой, потому что всю программу за разумный промежуток
времени можно было охватить взглядом и понять. В последнее время язык стал
значительно популярнее.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;По данным исследований StackOverflow за
&lt;a href="https://insights.stackoverflow.com/survey/2020#most-popular-technologies"&gt;2020&lt;/a&gt;,
&lt;a href="https://insights.stackoverflow.com/survey/2019#most-popular-technologies"&gt;2019&lt;/a&gt;,
&lt;a href="https://insights.stackoverflow.com/survey/2018#most-popular-technologies"&gt;2018&lt;/a&gt;,
&lt;a href="https://insights.stackoverflow.com/survey/2017#most-popular-technologies"&gt;2017&lt;/a&gt;,
&lt;a href="https://insights.stackoverflow.com/survey/2016#technology-most-popular-technologies"&gt;2016&lt;/a&gt;,
&lt;a href="https://insights.stackoverflow.com/survey/2015#tech-lang"&gt;2015&lt;/a&gt;
(там же можно посмотреть результаты за 2014 и 2013)
годы, Python с каждым годом
растёт в популярности.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Сегодня на Python написано много сложных систем из сотен файлов и сотен тысяч
строк кода. В таких обстоятельствах документирующее свойство системы типов
становится очень полезным. В достаточно крупной кодовой базе при отсутствии
информации о типах очень сложно угадать
(а только гадать и остаётся), какие же именно объекты циркулируют по программе.&lt;/p&gt;
&lt;p&gt;Кроме того, даже если код без информации о типах может быть и вполне
понятен человеку, например, благодаря удачно выбранным именам, то для
автоматики — это в любом случае абсолютно непроницаемый &lt;s&gt;черный&lt;/s&gt;
непрозрачный ящик.
В такой ситуации очень сложно, не выполняя код (мы же говорим про статический
анализ), понять как он будет вести себя в ран-тайме. Аннотации типов позволяют
IDE, &lt;a href="https://semakin.dev/2020/05/python_linters/"&gt;линтерам&lt;/a&gt; и тайп-чекерам лучше понимать
код программы, что дает возможность
рано отлавливать достаточно хитрые ошибки. В конечном итоге это делает
написанные программы надежнее.&lt;/p&gt;
&lt;p&gt;По этим соображениям, в Python 3.5 появился специальный синтаксис для
объявления типов параметров функций и их возвращаемых значений
(&lt;a href="https://www.python.org/dev/peps/pep-0484/"&gt;PEP 484&lt;/a&gt;).
В Python 3.6 эта возможность была расширена — стало можно объявлять типы
переменных вообще в любом месте программы
(&lt;a href="https://www.python.org/dev/peps/pep-0526/"&gt;PEP 526&lt;/a&gt;). С каждой новой версией
языка эта функциональность улучшается, и писать аннотации типов становится
всё проще, удобнее и естественнее, а экосистема вокруг типизированного
Python развивается семимильными шагами.&lt;/p&gt;
&lt;p&gt;Нужно отметить, что тайп-аннотации — это именно возможность, а не обязанность.
У программиста есть выбор — добавлять информацию о типах или нет.
Таким образом Python пытается усидеть на двух стульях — остаться языком с
динамической типизацией и дать возможность для статического анализа написанных
программ. Привнести в хаос немного порядка, так сказать. И, по-моему, у Python 
это неплохо получается.&lt;/p&gt;
&lt;h1&gt;Как это работает?&lt;/h1&gt;
&lt;p&gt;Программист при написании кода расставляет информацию о типах переменных,
параметров и возвращаемых значений функций. Это никак не влияет на выполнение 
программы. Python сам по себе никак не использует эту информацию в ран-тайме,
он лишь перекладывает её в специальные атрибуты функций или переменных,
делая доступной для сторонних утилит. То есть, если указано, что функция
принимает строки, то это никак не помешает вызвать её с целыми числами или
списками — в зависимости от тела функции, она может отработать, а может
завершиться ошибкой, но сама возможность вызова с любыми типами аргументов
никак не ограничивается. &lt;/p&gt;
&lt;p&gt;Зачем же тогда писать тайп-аннотации?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;документация для разработчиков;&lt;/li&gt;
&lt;li&gt;сторонние утилиты и библиотеки могут использовать эту информацию по своему
усмотрению, например, выполняя проверки типов или приводя данные к нужному типу.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Первый пункт достаточно очевидный, а про второй мы поговорим чуть позже в
разделе про тайп-чекеры.&lt;/p&gt;
&lt;h1&gt;Простые типы&lt;/h1&gt;
&lt;p&gt;Вот так, например, можно тайп-аннотировать простую функцию:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;world&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Hello, &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Типы параметров, принимаемых функцией, записываются после имени параметра
через знак двоеточия, но перед значением по умолчанию, если оно присутствует.
Возвращаемое значение функции записывается после знака “стрелки”.&lt;/p&gt;
&lt;p&gt;Теперь читатель просто взглянув на сигнатуру функции может понять, что функция
принимает строку и возвращает строку. Наверное, если передать в неё другой тип,
то она не сможет корректно отработать.&lt;/p&gt;
&lt;p&gt;Точно так же можно использовать для тайп-аннотаций и любые другие базовые
(примитивные, не-контейнерные) типы в Python:
&lt;code&gt;int&lt;/code&gt;, &lt;code&gt;float&lt;/code&gt;, &lt;code&gt;bool&lt;/code&gt;, &lt;code&gt;str&lt;/code&gt;, &lt;code&gt;bytes&lt;/code&gt;, &lt;code&gt;None&lt;/code&gt; и вообще практически что угодно.
Чуть позже посмотрим, как типизировать контейнерные типы данных, такие как списки,
кортежи, словари и множества.&lt;/p&gt;
&lt;p&gt;Вот так можно зааннотировать функцию, которая принимает два числа с плавающей
точкой и возвращает число с плавающей точкой:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;body_mass_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;А вот так функцию, которая принимает строку и булевый аргумент, но ничего не
возвращает:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;print_hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello,&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Вот так можно аннотировать любые переменные в любом месте кода (Python 3.6+):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Andrey&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;
&lt;span class="n"&gt;is_sick_with_covid19&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;  &lt;span class="c1"&gt;# надеюсь&lt;/span&gt;
&lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.1415&lt;/span&gt;

&lt;span class="c1"&gt;# можно даже аннотировать переменные, не назначая им значения&lt;/span&gt;
&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Если мы создадим свой класс, то его тоже можно использовать для аннотаций:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="c1"&gt;# довольно бессмысленно, но для примера пойдет&lt;/span&gt;
&lt;span class="n"&gt;example_instance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Example&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Example&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;Контейнерные типы и дженерики&lt;/h1&gt;
&lt;p&gt;Перейдем к более сложным типам, таким как списки, кортежи, словари и множества.
Можно аннотировать в лоб, используя сами имена классов:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;primes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;
&lt;span class="n"&gt;person_info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;
&lt;span class="n"&gt;stock_prices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;
&lt;span class="n"&gt;valid_answers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Это не слишком информативно, потому что кроме самого типа контейнера было бы
ещё полезно знать, какие данные он в себе содержит. Что такое &lt;code&gt;person_info&lt;/code&gt;?
Кортеж чего?&lt;/p&gt;
&lt;p&gt;В Python до версии 3.9 для этого придётся использовать отдельные классы из
модуля &lt;code&gt;typing&lt;/code&gt;, потому что стандартные классы не представляют такой
функциональности. Делается это при помощи квадратных скобок, как будто мы
извлекаем что-то по индексу:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Set&lt;/span&gt;

&lt;span class="c1"&gt;# тип всех элементов списка&lt;/span&gt;
&lt;span class="n"&gt;primes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# тип каждого элемента кортежа&lt;/span&gt;
&lt;span class="n"&gt;person_info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# тип ключей, тип значений&lt;/span&gt;
&lt;span class="n"&gt;stock_prices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# тип всех элементов множества&lt;/span&gt;
&lt;span class="n"&gt;valid_answers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Начиная с Python 3.9 можно использовать стандартные классы в точно таких же
целях, ничего ниоткуда не импортируя:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# будет работать только начиная с Python 3.9!&lt;/span&gt;

&lt;span class="c1"&gt;# тип всех элементов списка&lt;/span&gt;
&lt;span class="n"&gt;primes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# тип каждого элемента кортежа&lt;/span&gt;
&lt;span class="n"&gt;person_info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# тип ключей, тип значений&lt;/span&gt;
&lt;span class="n"&gt;stock_prices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# тип всех элементов множества&lt;/span&gt;
&lt;span class="n"&gt;valid_answers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Согласитесь, так намного понятнее. Сразу видно, какой тип данных лежит
внутри контейнера. Такие типы называются обобщёнными (generic types).&lt;/p&gt;
&lt;p&gt;Кстати, в типизации можно яснее увидеть разницу между тем как должны
использоваться списки и кортежи (&lt;code&gt;list&lt;/code&gt; vs. &lt;code&gt;tuple&lt;/code&gt;).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Списки содержат однородные данные — они все должны быть одного типа,
иначе с таким списком будет тяжеловато работать.&lt;/li&gt;
&lt;li&gt;Кортеж, напротив, может содержать разнородные данные, которые в зависимости
от позиции могут иметь тот или иной тип.&lt;/li&gt;
&lt;li&gt;Список нужно использовать,
когда длина заранее неизвестна либо она переменна, например,
список пользователей.&lt;/li&gt;
&lt;li&gt;Кортеж нужно использовать, когда длина данных известна
заранее и строго фиксирована, например, как в записи из таблицы в СУБД.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Получается, кортеж — это не просто неизменяемый брат-близнец списка.&lt;/p&gt;
&lt;p&gt;Если сильно хочется использовать кортеж как просто неизменяемую
последовательность однородных данных, то можно зааннотировать его вот так,
используя &lt;code&gt;...&lt;/code&gt; (это специальный объект &lt;code&gt;Ellipsis&lt;/code&gt;, записывается как многоточие,
при чтении исходников вслух в этом месте нужно делать драматическую паузу):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# кортеж из строк, длина неизвестна&lt;/span&gt;
&lt;span class="n"&gt;months&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;Составные типы&lt;/h1&gt;
&lt;p&gt;Часто случаются ситуации, когда нужно объединить несколько типов, например,
для того, чтобы указать, что функция может принимать и строки, и числа.
Этого можно достичь при помощи дженерик-типа &lt;code&gt;Union&lt;/code&gt; из модуля &lt;code&gt;typing&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_or_concatenate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Также очень часто возникает ситуация, когда возможно либо значение определенного
типа, либо &lt;code&gt;None&lt;/code&gt;. Это настолько частая ситуация, что для этого даже сделали
отдельный дженерик-тип &lt;code&gt;Optional&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;

&lt;span class="c1"&gt;# по сути это одно и то же, но первый вариант проще читается&lt;/span&gt;
&lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Также может возникнуть ситуация, когда не получается указать какой-либо
конкретный тип, потому что, например, функция может принимать на вход абсолютно
что угодно. Для этих случаев тоже есть специальный объект &lt;code&gt;typing.Any&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;arg&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Можно считать, что &lt;code&gt;Any&lt;/code&gt; неявно подставляется везде, где не указан более
конкретный тип. Очень соблазнительно везде вставлять этот тип, но
настоятельно рекомендую использовать его только в крайних случаях, потому что
чрезмерное его использование сводит пользу от типизации на нет.&lt;/p&gt;
&lt;p&gt;Вообще советую заглянуть в
&lt;a href="https://docs.python.org/3/library/typing.html"&gt;документацию модуля &lt;code&gt;typing&lt;/code&gt;&lt;/a&gt;,
там есть много интересных классов на все случаи жизни.&lt;/p&gt;
&lt;h1&gt;Проверка типов&lt;/h1&gt;
&lt;p&gt;Допустим, что тайп-аннотации написаны. Как начать получать от этого пользу?&lt;/p&gt;
&lt;p&gt;В экосистеме Python есть несколько конкурирующих между собой тайп-чекеров,
например, &lt;a href="http://mypy-lang.org/"&gt;&lt;code&gt;mypy&lt;/code&gt;&lt;/a&gt;,
&lt;a href="https://pyre-check.org/"&gt;&lt;code&gt;pyre&lt;/code&gt;&lt;/a&gt;,
&lt;a href="https://github.com/google/pytype"&gt;&lt;code&gt;pytype&lt;/code&gt;&lt;/a&gt;,
&lt;a href="https://github.com/Microsoft/pyright"&gt;&lt;code&gt;pyright&lt;/code&gt;&lt;/a&gt;.
Самым популярным среди них является &lt;code&gt;mypy&lt;/code&gt;, наверное, потому что одним из
ключевых его разработчиков является сам
&lt;a href="https://ru.wikipedia.org/wiki/%D0%92%D0%B0%D0%BD_%D0%A0%D0%BE%D1%81%D1%81%D1%83%D0%BC,_%D0%93%D0%B2%D0%B8%D0%B4%D0%BE"&gt;Гвидо ван Россум&lt;/a&gt;.
Давайте на &lt;code&gt;mypy&lt;/code&gt; и остановимся.&lt;/p&gt;
&lt;p&gt;Установим &lt;code&gt;mypy&lt;/code&gt; в проект. Внутри виртуального окружения проекта нужно выполнить:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pip install mypy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Для &lt;code&gt;pipenv&lt;/code&gt; и &lt;code&gt;poetry&lt;/code&gt; соответственно вот так:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv install --dev mypy
$ poetry add --dev mypy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Давайте напишем самый тривиальный пример программы с ошибкой:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;qwerty&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;При выполнении, очевидно, программа завершится ошибкой:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ python example.py
Traceback &lt;span class="o"&gt;(&lt;/span&gt;most recent call last&lt;span class="o"&gt;)&lt;/span&gt;:
  File &lt;span class="s2"&gt;&amp;quot;example.py&amp;quot;&lt;/span&gt;, line &lt;span class="m"&gt;1&lt;/span&gt;, in &amp;lt;module&amp;gt;
    print&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;qwerty&amp;quot;&lt;/span&gt; + &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
TypeError: can only concatenate str &lt;span class="o"&gt;(&lt;/span&gt;not &lt;span class="s2"&gt;&amp;quot;int&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; to str
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Давайте посмотрим, сможет ли тайп-чекер обнаружить эту проблему:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ mypy example.py
example.py:1: error: Unsupported operand types &lt;span class="k"&gt;for&lt;/span&gt; + &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;str&amp;quot;&lt;/span&gt; and &lt;span class="s2"&gt;&amp;quot;int&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
Found &lt;span class="m"&gt;1&lt;/span&gt; error in &lt;span class="m"&gt;1&lt;/span&gt; file &lt;span class="o"&gt;(&lt;/span&gt;checked &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; file&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Отлично! Не исполняя программу, &lt;code&gt;mypy&lt;/code&gt; смог понять, что в ней присутствует ошибка.
Давайте запрячем эту же самую ошибку чуть глубже, используя функцию:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello, &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# правильный вызов&lt;/span&gt;
&lt;span class="n"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;world!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# а вот тут будет ошибка&lt;/span&gt;
&lt;span class="n"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Проверим типы в этой программе:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ mypy example2.py
example2.py:9: error: Argument &lt;span class="m"&gt;1&lt;/span&gt; to &lt;span class="s2"&gt;&amp;quot;greet&amp;quot;&lt;/span&gt; has incompatible &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;int&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; expected &lt;span class="s2"&gt;&amp;quot;str&amp;quot;&lt;/span&gt;
Found &lt;span class="m"&gt;1&lt;/span&gt; error in &lt;span class="m"&gt;1&lt;/span&gt; file &lt;span class="o"&gt;(&lt;/span&gt;checked &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; file&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Тайп-чекер пропустил правильный вызов функции, но обнаружил вызов функции
с ошибкой.&lt;/p&gt;
&lt;h1&gt;Заключение&lt;/h1&gt;
&lt;p&gt;Тайп-аннотации — это настолько круто и удобно, что, честно говоря, я уже плохо
представляю, как раньше (до Python 3.5) без этого люди вообще программировали.
Для меня это самый веский аргумент в пользу Python 3 и против Python 2.
Это незаменимый инструмент при разработке насколько-нибудь крупной программы.&lt;/p&gt;
&lt;p&gt;Обязательно нужно интегрировать тайп-чекинг в свой редактор/IDE, чтобы ошибки
подсвечивались ещё на этапе написания кода. Можно интегрировать тайп-чекинг в
Git-хуки и CI.&lt;/p&gt;
&lt;p&gt;На странице &lt;a href="https://github.com/typeddjango/awesome-python-typing"&gt;"Awesome Python Typing"&lt;/a&gt;
можно найти ещё много полезных инструментов, которые пользуются тайп-аннотациями.&lt;/p&gt;
&lt;p&gt;Если понравилась статья, то
&lt;a href="https://semakin.dev/pages/subscribe/"&gt;подпишитесь на уведомления&lt;/a&gt;
о новых постах в блоге, чтобы ничего не пропустить!&lt;/p&gt;
&lt;h1&gt;Дополнительное чтение:&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;введение в аннотации типов &lt;a href="https://habr.com/ru/company/lamoda/blog/432656/"&gt;часть 1&lt;/a&gt; и &lt;a href="https://habr.com/ru/company/lamoda/blog/435988/"&gt;часть 2&lt;/a&gt; на Хабре;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/library/typing.html"&gt;документация к модулю &lt;code&gt;typing&lt;/code&gt;&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mypy.readthedocs.io/en/stable/"&gt;документация &lt;code&gt;mypy&lt;/code&gt;&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/python/mypy"&gt;репозиторий &lt;code&gt;mypy&lt;/code&gt; на GitHub&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://mypy-lang.org/"&gt;сайт &lt;code&gt;mypy&lt;/code&gt;&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pyre-check.org/"&gt;сайт &lt;code&gt;pyre&lt;/code&gt;&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google/pytype"&gt;репозиторий &lt;code&gt;pytype&lt;/code&gt; на GitHub&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Microsoft/pyright"&gt;репозиторий &lt;code&gt;pyright&lt;/code&gt; на GitHub&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/typeddjango/awesome-python-typing"&gt;Awesome Python Typing&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://habr.com/ru/post/161205/"&gt;ликбез по типизации в языках программирования&lt;/a&gt; на Хабре.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Обложка: &lt;a href="https://flic.kr/p/4hABNH"&gt;Windell Oskay, Technic Bits&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="blog"></category><category term="python"></category><category term="typing"></category><category term="mypy"></category></entry><entry><title>Почему важно всегда ставить символ переноса строки в конце текстовых файлов?</title><link href="https://semakin.dev/2020/05/no_newline_at_end_of_file/" rel="alternate"></link><published>2020-05-31T19:30:24+05:00</published><updated>2020-05-31T19:31:20+05:00</updated><author><name>Андрей Семакин</name></author><id>tag:semakin.dev,2020-05-31:/2020/05/no_newline_at_end_of_file/</id><summary type="html">&lt;p&gt;Так легко упустить эту маленькую, но важную деталь.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Иногда при просмотре диффов коммитов через &lt;code&gt;git log&lt;/code&gt; или &lt;code&gt;git diff&lt;/code&gt;
можно заметить следующий вывод:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;\ No newline at end of file
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Или на GitHub в интерфейсе для просмотра диффов:&lt;/p&gt;
&lt;p&gt;&lt;img alt="GitHub &amp;quot;now newline at end of file&amp;quot; warning" src="https://semakin.dev/static/github_no_newline_at_end_of_file.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Почему это так важно, что Git и GitHub предупреждают нас об этом?
Давайте разберемся.&lt;/p&gt;
&lt;h1&gt;Что такое символ переноса строки?&lt;/h1&gt;
&lt;p&gt;Что может быть проще, чем текстовый файл? Просто текстовые данные — как
хранятся на диске, так и отображаются. На самом деле
&lt;s&gt;правительство нам врёт&lt;/s&gt; всё немного сложнее.&lt;/p&gt;
&lt;details&gt;

&lt;summary&gt;Оффтопик про управляющие символы ASCII&lt;/summary&gt;

&lt;p&gt;Не все символы, которые содержатся в текстовых файлах, имеют визуальное
представление. Такие символы ещё называют "управляющими", и к ним относятся,
например:&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;
        нулевой символ (&lt;code&gt;x00&lt;/code&gt;, &lt;code&gt;\0&lt;/code&gt;) — часто используется для кодирования конца строки в памяти;
        т.е. программа считывает символы из памяти по одному до тех пор, пока не встретит
        нулевой символ, и тогда строка считается завершённой;
    &lt;/li&gt;
    &lt;li&gt;
        табуляция (&lt;code&gt;\x09&lt;/code&gt;, &lt;code&gt;\t&lt;/code&gt;) — используется для выравнивания данных по границе
        столбца, так что это выглядит как таблица;
    &lt;/li&gt; 
    &lt;li&gt;
        перевод строки (&lt;code&gt;\x0a&lt;/code&gt;, &lt;code&gt;\n&lt;/code&gt;) — используется для разделения текстовых данных на отдельные строки;
    &lt;/li&gt;
    &lt;li&gt;
        возврат каретки (&lt;code&gt;\x0d&lt;/code&gt;, &lt;code&gt;\r&lt;/code&gt;) — переместить курсор в начало строки;
    &lt;/li&gt; 
    &lt;li&gt;
        возврат на один символ (&lt;code&gt;\x08&lt;/code&gt;, &lt;code&gt;\b&lt;/code&gt;) — переместить курсор на один символ назад;
    &lt;/li&gt;
    &lt;li&gt;
        звонок (&lt;code&gt;\x07&lt;/code&gt;, &lt;code&gt;\a&lt;/code&gt;) — если набрать этот символ в терминале, то будет бибикающий символ; именно так консольные программы, типа &lt;code&gt;vim&lt;/code&gt;, бибикают на пользователей;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href="https://ru.wikipedia.org/wiki/ASCII#%D0%A3%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D1%8F%D1%8E%D1%89%D0%B8%D0%B5_%D1%81%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB%D1%8B"&gt;и другие&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Многие эти символы пришли к нам из эпохи печатных машинок, поэтому у них
такие странные названия. И действительно, в контексте печатной машинки или принтера
такие операции, как перевод строки (сместить лист бумаги вверх так, чтобы печатающая
головка попала на следующую строку),
возврат каретки (переместить печатающую головку в крайнее левое положение) и
возврат на один символ назад, обретают смысл. При помощи возврата на один символ
назад создавались жирные символы (печатаешь символ, возвращаешься назад и
печатаешь его ещё раз) и буквы с диакритическими знаками, такие как à или ã
(печатаешь символ, возвращаешься назад и печатаешь апостроф или тильду).
&lt;b&gt;Но зачем печатной машинке бибикалка?&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;Сегодня многие из этих символов потеряли смысл, но некоторые
до сих пор выполняют функцию, схожую с исходной.&lt;/p&gt;

&lt;hr/&gt;

&lt;/details&gt;

&lt;p&gt;Текстовые редакторы отображают текстовые файлы
в некоем адаптированном виде, преобразуя непечатаемые
символы, например, переносы строк и табуляции преобразуются в настоящие
отдельные строки или выравнивающие отступы.&lt;/p&gt;
&lt;p&gt;Для набора символа переноса строки достаточно нажать клавишу "Enter", но
на разных платформах этот символ закодируется по-разному:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;в Unix-совместимых системах (включая современные версии macOS)
используется один символ перевода строки (&lt;code&gt;LF&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;в Windows используется сразу два символа — возврат каретки (&lt;code&gt;CR&lt;/code&gt;) и перевод строки (&lt;code&gt;LF&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;в очень старых версиях Mac OS (до 2001 года) использовался один символ &lt;code&gt;CR&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Как видите, Windows точнее всего эмулирует поведение печатной машинки.&lt;/p&gt;
&lt;p&gt;В языках программирования символ новой строки часто кодируют при помощи
бэкслэш-последовательностей, таких как &lt;code&gt;\n&lt;/code&gt; или &lt;code&gt;\r\n&lt;/code&gt;. Нужно понимать разницу
между такой последовательностью и настоящим символом переноса строки. Если
в редакторе в файле &lt;code&gt;*.txt&lt;/code&gt; просто набрать &lt;code&gt;\n&lt;/code&gt; и сохранить, то вы получите
ровно то, что написали.
Символом переноса строки оно не станет. Нужно что-то, что заменит эти
бэкслэш-последовательности на настоящие символы переноса строки (например,
компилятор или интерпретатор языка программирования).  &lt;/p&gt;
&lt;h1&gt;Почему перенос строки в конце файла важен?&lt;/h1&gt;
&lt;p&gt;Согласно &lt;a href="https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206"&gt;определению из стандарта POSIX&lt;/a&gt;,
который тоже пришёл к нам из эпохи печатных машинок:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Строка — это последовательность из нуля или более символов, не являющихся
символом новой строки, и терминирующего символа новой строки. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Почему важен этот стандарт? Возможен миллиард способов реализовать одно и то же,
и только благодаря стандартам, таким как POSIX, мы имеем сейчас огромное
количество качественного ПО, которое не конфликтует друг с другом.&lt;/p&gt;
&lt;p&gt;Т.е. если вы не ставите символ переноса строки в конце строки, то формально
по стандарту такая строка не является валидной. Множество утилит из Unix,
которыми я пользуюсь каждый день, написано в согласии с этим стандартом,
и они просто не могут правильно обрабатывать такие "сломанные" строки.&lt;/p&gt;
&lt;p&gt;Давайте, например, через Python создадим такой файл со сломанными строками:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;broken.txt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;w&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;qwe&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;asd&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;zxc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Сколько по-вашему в этом файле строк? Три? Давайте посмотрим, что об этом файле
думает утилита &lt;code&gt;wc&lt;/code&gt;, которая с флагом &lt;code&gt;-l&lt;/code&gt; умеет считать количество строк в файле:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ wc -l broken.txt 
&lt;span class="m"&gt;2&lt;/span&gt; broken.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Упс! &lt;code&gt;wc&lt;/code&gt; нашла только 2 строки!&lt;/p&gt;
&lt;p&gt;Давайте создадим еще один файл:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;broken2.txt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;w&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rty&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;fgh&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;vbn&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;И попробуем теперь склеить два созданных файла при помощи утилиты &lt;code&gt;cat&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ cat broken.txt broken2.txt 
qwe
asd
zxcrty
fgh
vbn
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Название &lt;code&gt;cat&lt;/code&gt; — это сокращение от "конкатенация", и никак не связано
с котиками. А жаль.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;И опять какой-то странный результат! В большинстве случаев это не то, чего вы
бы ожидали, но вполне возможны ситуации, когда вам нужен именно такой результат.
Именно поэтому утилита &lt;code&gt;cat&lt;/code&gt; не может самостоятельно вставлять отсутствующие
символы переноса строки, иначе это сделало бы её поведение неконсистентным.&lt;/p&gt;
&lt;p&gt;Это только пара примеров, но многие другие утилиты, которые работают с текстом
(например, &lt;code&gt;diff&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;sed&lt;/code&gt;), имеют такие же проблемы. Собственно говоря,
это даже не проблемы, а их задокументированное поведение.&lt;/p&gt;
&lt;h2&gt;Ещё доводы:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;при дозаписи содержимого в конец файла без переноса строки получится некрасивый
дифф — будет изменена последняя строка (хотя на ней всего лишь добавился
символ переноса);&lt;/li&gt;
&lt;li&gt;файл с переносом строки и без переноса строки — это два разных файла; для &lt;code&gt;diff&lt;/code&gt;
и &lt;code&gt;git diff&lt;/code&gt; единственный способ отобразить разницу между ними —
это напечатать сообщение об отсутствии символа переноса строки;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/a/729725/10650942"&gt;согласно стандарту языка C (до 2014 года), непустые файлы с исходным кодом
должны заканчиваться символом переноса строки&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Настраиваем редактор&lt;/h1&gt;
&lt;p&gt;Самый простой способ перестать думать о пустых строках &lt;s&gt;и начать жить&lt;/s&gt; —
это настроить свой текстовый редактор или IDE на автоматическое добавление
символа переноса строки в конец файлов:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PyCharm и другие IDE JetBrains:
&lt;code&gt;Settings &amp;gt; Editor &amp;gt; General &amp;gt; Ensure an empty line at the end of a file on Save&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;VS Code: &lt;code&gt;"files.insertFinalNewline": true&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Для других редакторов смотрите настройку &lt;a href="https://thoughtbot.com/blog/no-newline-at-end-of-file#following-the-rules-in-your-editor"&gt;здесь&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Кстати, если вы пользуетесь &lt;a href="https://semakin.dev/2020/05/black/"&gt;форматтером &lt;code&gt;black&lt;/code&gt;&lt;/a&gt;,
то у меня хорошие новости —
он всегда добавляет перенос строки в конец всех файлов &lt;code&gt;*.py&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;Заключение&lt;/h1&gt;
&lt;p&gt;Возможно, такая маленькая деталь, как перенос строки в конце файла и не кажется
очень важной, а тема вообще кажется спорной, но боюсь, что у нас нет другого
выбора, кроме как принять это
правило за данность и просто выработать привычку (или настроить инструментарий)
всегда ставить символ новой строки в любых текстовых файлах, даже если этого не
требуется явно.
Это считается распространённой хорошей практикой, и
как минимум убережёт вас и ваших коллег от всяких неожиданных эффектов при
работе с утилитами Unix.&lt;/p&gt;
&lt;p&gt;В текстовом редакторе это выглядит как лишняя пустая строка в конце файла:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Пример того, как это должно выглядеть в текстовом редакторе" src="https://semakin.dev/static/newline_editor_example.jpg"&gt;&lt;/p&gt;
&lt;h1&gt;Дополнительное чтение&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/5813311/no-newline-at-end-of-file/5813359#5813359"&gt;StackOverflow: No newline at end of file&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/729692/why-should-text-files-end-with-a-newline/729795#729795"&gt;StackOverflow: Why should text files end with a newline?&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thoughtbot.com/blog/no-newline-at-end-of-file"&gt;thoughtbot — No Newline at End of File&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"></category><category term="good practice"></category></entry><entry><title>Запуск Git-хуков при помощи pre-commit</title><link href="https://semakin.dev/2020/05/pre-commit/" rel="alternate"></link><published>2020-05-31T15:54:18+05:00</published><updated>2020-05-31T16:14:03+05:00</updated><author><name>Андрей Семакин</name></author><id>tag:semakin.dev,2020-05-31:/2020/05/pre-commit/</id><summary type="html">&lt;p&gt;Git-хуки позволяют автоматизировать запуск различных проверок (линтеры, форматтеры, тесты) при работе с системой контроля версий.&lt;/p&gt;</summary><content type="html">&lt;h1&gt;Git-хуки&lt;/h1&gt;
&lt;p&gt;Умение работать с системой контроля версий Git — базовый навык для выживания
разработчика (на любом языке программирования) в современных реалиях.
Система контроля версий — это этакая машина времени для вашего проекта:
всегда можно вернуться на любое прошлое состояние проекта, понять
когда, как, что и кем менялось.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Интересный факт:&lt;/em&gt; согласно
&lt;a href="https://trends.google.com/trends/explore?date=all&amp;amp;q=%2Fm%2F05vqwg,%2Fm%2F012ct9,%2Fm%2F08441_,%2Fm%2F08w6d6,%2Fm%2F09d6g&amp;amp;hl=en-US&amp;amp;tz=&amp;amp;tz="&gt;Google Trends&lt;/a&gt;,
во всём мире Git фактически вытеснил другие системы контроля версий.
Только в Китае почему-то до сих пор популярен Subversion (46% рынка).
К чему бы это?&lt;/p&gt;
&lt;p&gt;Git имеет крайне полезную фичу — возможность исполнять произвольный код
на многих этапах работы через так называемые хуки.
Вот примеры доступных хуков:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pre-commit&lt;/code&gt; — выполняется перед созданием коммита;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;commit-msg&lt;/code&gt; — выполняется после добавления сообщения коммита;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;post-checkout&lt;/code&gt; — выполняется после переключения ветки;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pre-push&lt;/code&gt; — выполняется перед загрузкой локальной истории на удалённый сервер.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Полный список хуков можно посмотреть
&lt;a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks"&gt;в документации&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Как это работает? Рассмотрим, например, схему работы хука &lt;code&gt;pre-commit&lt;/code&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;пользователь пишет в терминале &lt;code&gt;git commit -v&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git&lt;/code&gt; пытается выполнить хук &lt;code&gt;pre-commit&lt;/code&gt; локально, на машине разработчика;&lt;/li&gt;
&lt;li&gt;если хук завершается ошибкой, то операция коммита прерывается;&lt;/li&gt;
&lt;li&gt;если хук выполнился без ошибок, то операция коммита продолжается,
открывается текстовый редактор для ввода сообщения.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Фишка в том, что хуки могут прерывать операции, за которые они отвечают,
если что-то идёт не так. Это идеальное место, чтобы запускать какие-нибудь
проверки качества кода, например, линтеры, форматтеры или тесты
(если они работают быстро, конечно), или проверять сообщение коммита на
соответствие конвенциям или на грамматические ошибки.&lt;/p&gt;
&lt;h1&gt;Пишем Git-хук на &lt;code&gt;bash&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;Давайте для понимания напишем простой скрипт, который будет использоваться в
качестве хука.&lt;/p&gt;
&lt;p&gt;В этом примере я использую Linux.
Если вы пользуетесь Windows, то большинство описанный вещей будут работать
без изменений через Git Bash, который устанавливается вместе с Git for Windows.
Возможно, вам только придётся поменять путь до вашего &lt;code&gt;bash&lt;/code&gt; в
&lt;a href="https://ru.wikipedia.org/wiki/%D0%A8%D0%B5%D0%B1%D0%B0%D0%BD%D0%B3_(Unix)"&gt;шебанге&lt;/a&gt;,
как описано &lt;a href="https://www.tygertec.com/git-hooks-practical-uses-windows/"&gt;в конце этой статьи&lt;/a&gt;.
Если же вы соберётесь писать в хуках что-то сложное и требующее интеграции с ОС,
то, возможно, стоит вместо &lt;code&gt;bash&lt;/code&gt; использовать PowerShell. &lt;/p&gt;
&lt;p&gt;Создадим пустой репозиторий:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ git init git_hooks_example
$ &lt;span class="nb"&gt;cd&lt;/span&gt; git_hooks_example/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Git уже заботливо создал для нас шаблоны для написания хуков,
которые лежат в специальной служебной директории:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ ls -l .git/hooks/
total &lt;span class="m"&gt;56&lt;/span&gt;
-rwxr-xr-x. &lt;span class="m"&gt;1&lt;/span&gt; br0ke br0ke  &lt;span class="m"&gt;482&lt;/span&gt; May &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="m"&gt;21&lt;/span&gt;:36 applypatch-msg.sample*
-rwxr-xr-x. &lt;span class="m"&gt;1&lt;/span&gt; br0ke br0ke  &lt;span class="m"&gt;900&lt;/span&gt; May &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="m"&gt;21&lt;/span&gt;:36 commit-msg.sample*
-rwxr-xr-x. &lt;span class="m"&gt;1&lt;/span&gt; br0ke br0ke &lt;span class="m"&gt;4655&lt;/span&gt; May &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="m"&gt;21&lt;/span&gt;:36 fsmonitor-watchman.sample*
-rwxr-xr-x. &lt;span class="m"&gt;1&lt;/span&gt; br0ke br0ke  &lt;span class="m"&gt;193&lt;/span&gt; May &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="m"&gt;21&lt;/span&gt;:36 post-update.sample*
-rwxr-xr-x. &lt;span class="m"&gt;1&lt;/span&gt; br0ke br0ke  &lt;span class="m"&gt;428&lt;/span&gt; May &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="m"&gt;21&lt;/span&gt;:36 pre-applypatch.sample*
-rwxr-xr-x. &lt;span class="m"&gt;1&lt;/span&gt; br0ke br0ke &lt;span class="m"&gt;1647&lt;/span&gt; May &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="m"&gt;21&lt;/span&gt;:36 pre-commit.sample*
-rwxr-xr-x. &lt;span class="m"&gt;1&lt;/span&gt; br0ke br0ke  &lt;span class="m"&gt;420&lt;/span&gt; May &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="m"&gt;21&lt;/span&gt;:36 pre-merge-commit.sample*
-rwxr-xr-x. &lt;span class="m"&gt;1&lt;/span&gt; br0ke br0ke &lt;span class="m"&gt;1496&lt;/span&gt; May &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="m"&gt;21&lt;/span&gt;:36 prepare-commit-msg.sample*
-rwxr-xr-x. &lt;span class="m"&gt;1&lt;/span&gt; br0ke br0ke &lt;span class="m"&gt;1352&lt;/span&gt; May &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="m"&gt;21&lt;/span&gt;:36 pre-push.sample*
-rwxr-xr-x. &lt;span class="m"&gt;1&lt;/span&gt; br0ke br0ke &lt;span class="m"&gt;4902&lt;/span&gt; May &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="m"&gt;21&lt;/span&gt;:36 pre-rebase.sample*
-rwxr-xr-x. &lt;span class="m"&gt;1&lt;/span&gt; br0ke br0ke  &lt;span class="m"&gt;548&lt;/span&gt; May &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="m"&gt;21&lt;/span&gt;:36 pre-receive.sample*
-rwxr-xr-x. &lt;span class="m"&gt;1&lt;/span&gt; br0ke br0ke &lt;span class="m"&gt;3639&lt;/span&gt; May &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="m"&gt;21&lt;/span&gt;:36 update.sample*
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Можно, например, просто переименовать файл &lt;code&gt;pre-commit.sample&lt;/code&gt; в &lt;code&gt;pre-commit&lt;/code&gt;,
и этот хук вступит в силу. В моём случае там на &lt;code&gt;bash&lt;/code&gt; реализована
проверка имён файлов, которая не допустит добавление файлов с именами,
содержащими не-ASCII символы.&lt;/p&gt;
&lt;p&gt;Давайте не будем этого делать, а взамен напишем простой скрипт,
который будет запускать все нужные нам &lt;a href="https://semakin.dev/2020/05/python_linters/"&gt;линтеры&lt;/a&gt;
и &lt;a href="https://semakin.dev/2020/05/black/"&gt;форматтеры&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="c1"&gt;# Получаем список файлов, которые пользователь пытается закоммитить,&lt;/span&gt;
&lt;span class="c1"&gt;# и выбираем из них те, которые заканчиваются на `.py`.&lt;/span&gt;
&lt;span class="c1"&gt;# Взято отсюда: https://stackoverflow.com/a/3068990/10650942.&lt;/span&gt;
&lt;span class="nv"&gt;CHANGED_PYTHON_FILES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;git diff --cached --name-only --diff-filter&lt;span class="o"&gt;=&lt;/span&gt;ACMR &lt;span class="p"&gt;|&lt;/span&gt; grep &lt;span class="s2"&gt;&amp;quot;.py\$&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -z &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$CHANGED_PYTHON_FILES&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;then&lt;/span&gt;
   &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;No Python files found. No reason to run checks.&amp;quot;&lt;/span&gt;
   &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c1"&gt;# Включаем режим, в котором любая ошибка сразу же завершит весь скрипт ошибкой.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; -e

&lt;span class="c1"&gt;# Запускаем проверки.&lt;/span&gt;
&lt;span class="c1"&gt;# Если хотя бы одна завершится ошибкой, то операция будет прервана.&lt;/span&gt;
flake8 &lt;span class="nv"&gt;$CHANGED_PYTHON_FILES&lt;/span&gt;
black --check &lt;span class="nv"&gt;$CHANGED_PYTHON_FILES&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;All checks successfully passed.&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Этот файл должен быть сохранён по пути &lt;code&gt;.git/hooks/pre-commit&lt;/code&gt;.
Файл нужно сделать исполняемым, иначе Git не сможет его запустить:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ chmod a+x .git/hooks/pre-commit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Теперь давайте создадим какой-нибудь файл, который точно не пройдет этих
проверок. Обратите внимание на лишние пробелы рядом со скобками,
которые не понравятся &lt;code&gt;flake8&lt;/code&gt;, и на одинарные кавычки, которые
не понравятся &lt;code&gt;black&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Hello world!&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Попытаемся его закоммитить:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ git add example.py
$ git commit -v
example.py:1:7: E201 whitespace after &lt;span class="s1"&gt;&amp;#39;(&amp;#39;&lt;/span&gt;
example.py:1:22: E202 whitespace before &lt;span class="s1"&gt;&amp;#39;)&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Наш умный хук выполнился и запустил &lt;code&gt;flake8&lt;/code&gt;, который нашел ошибки.
Давайте удалим лишние пробелы и попытаемся закоммитить ещё раз:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ git add example.py
$ git commit -v
would reformat example.py
Oh no! 💥 💔 💥
&lt;span class="m"&gt;1&lt;/span&gt; file would be reformatted.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;В этот раз &lt;code&gt;flake8&lt;/code&gt; не нашёл ошибок, но операция всё равно завершилась
ошибкой из-за &lt;code&gt;black&lt;/code&gt;. Давайте отформатируем файл и наконец закоммитим его:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ black .
$ git add example.py
$ git commit -v
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;На этот раз всё срабатывает успешно.&lt;/p&gt;
&lt;p&gt;Чтобы закоммитить, игнорируя хуки, можно было передать в
команду флаг &lt;code&gt;--no-verify&lt;/code&gt; или &lt;code&gt;-n&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git commit -v -n
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Как видите, писать git-хуки самостоятельно не так уж и сложно.
Не обязательно использовать &lt;code&gt;bash&lt;/code&gt;, хуки можно писать на чём угодно —
любой исполняемый файл подойдет, хоть на интерпретируемом языке,
хоть на компилируемом. Если бы нужно было писать какую-то более сложную логику,
то я бы, например, предпочёл написать хук на Python.&lt;/p&gt;
&lt;p&gt;У такого ручного способа есть несколько недостатков:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;хук хранится в директории &lt;code&gt;.git&lt;/code&gt;, которая является служебной для локальной
копии репозитория и сама не сохраняется в систему контроля версий;
это значит, что если вы захотите поделиться своим хуком с коллегами,
то вы можете лишь каким-либо образом скинуть им исходный код,
а им придётся самостоятельно класть его в правильное место и назначать права;&lt;/li&gt;
&lt;li&gt;первая команда, завершившаяся ошибкой, прерывает выполнение хука и
последующие команды не выполняются; это можно исправить,
но получится сильно сложнее;&lt;/li&gt;
&lt;li&gt;логика хука может быстро стать сложной;&lt;/li&gt;
&lt;li&gt;всё нужно имплементить самостоятельно;&lt;/li&gt;
&lt;li&gt;наш хук полагается на наличие команд &lt;code&gt;flake8&lt;/code&gt; и &lt;code&gt;black&lt;/code&gt;, так что они
либо должны быть установлены глобально, либо перед коммитом нужно активировать
виртуальное окружение проекта.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Используем готовый инструмент — &lt;code&gt;pre-commit&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;Как обычно, для всего уже есть решения. Представляю вашему вниманию
&lt;a href="https://pre-commit.com/"&gt;&lt;code&gt;pre-commit&lt;/code&gt;&lt;/a&gt; — умный инструмент для управления
Git-хуками.&lt;/p&gt;
&lt;h2&gt;Установка&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;pre-commit&lt;/code&gt; написан на Python (ещё бы, иначе б я не был его фанатом 😁),
поэтому установить его можно через &lt;code&gt;pip&lt;/code&gt;. Он должен быть установлен глобально,
а не в виртуальном окружении проекта, где вы собираетесь его использовать.
Рекомендую использовать метод установки в домашнюю директорию пользователя
(см. &lt;a href="https://semakin.dev/2020/04/python_virtualenv/"&gt;заметку про виртуальные окружения&lt;/a&gt;
за подробностями):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pip install --user pre-commit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Проверим установку:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pre-commit --version
pre-commit &lt;span class="m"&gt;2&lt;/span&gt;.4.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Настройка&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;pre-commit&lt;/code&gt; спроектирован с прицелом на удобное использование сторонних
переиспользуемых хуков, но может исполнять и вообще любые команды.
&lt;a href="https://pre-commit.com/hooks.html"&gt;Уже написаны сотни полезных хуков&lt;/a&gt;
для разных языков и задач, из которых, как из конструктора, можно собрать
практически любой нужный вам вокрфлоу. Выбирайте те, которые вам понравятся.
Для примера я возьму всё те же &lt;code&gt;flake8&lt;/code&gt; и &lt;code&gt;black&lt;/code&gt;, и ещё несколько других
хуков сверху (а что, бесплатно же).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pre-commit&lt;/code&gt; конфигурируется на уровне репозитория при помощи
&lt;a href="https://ru.wikipedia.org/wiki/YAML"&gt;YAML-файла&lt;/a&gt;.
Файл должен называться &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; и находиться в корне репозитория.
Давайте сгенерируем базовый конфиг:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pre-commit sample-config &amp;gt; .pre-commit-config.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;И допилим примерно до такого состояния:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# See https://pre-commit.com for more information&lt;/span&gt;
&lt;span class="c1"&gt;# See https://pre-commit.com/hooks.html for more hooks&lt;/span&gt;
&lt;span class="nt"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;https://github.com/pre-commit/pre-commit-hooks&amp;#39;&lt;/span&gt;
    &lt;span class="nt"&gt;rev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;v2.4.0&lt;/span&gt;
    &lt;span class="nt"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# проверяет наличие переноса строки в конце всех текстовых файлов&lt;/span&gt;
      &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;end-of-file-fixer&lt;/span&gt;
      &lt;span class="c1"&gt;# предупреждает о добавлении больших файлов в Git&lt;/span&gt;
      &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;check-added-large-files&lt;/span&gt;
      &lt;span class="c1"&gt;# предупреждает о сохранении файлов с UTF-8 BOM&lt;/span&gt;
      &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;check-byte-order-marker&lt;/span&gt;
      &lt;span class="c1"&gt;# предотвращает сохранение приватных ключей&lt;/span&gt;
      &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;detect-private-key&lt;/span&gt;
      &lt;span class="c1"&gt;# проверяет, что файлы, которые мы собираемся сохранять, как минимум валидный Python&lt;/span&gt;
      &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;check-ast&lt;/span&gt;
  &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;https://gitlab.com/pycqa/flake8&amp;#39;&lt;/span&gt;
    &lt;span class="nt"&gt;rev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;3.8.2&lt;/span&gt;
    &lt;span class="nt"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;flake8&lt;/span&gt;
  &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;https://github.com/psf/black&amp;#39;&lt;/span&gt;
    &lt;span class="nt"&gt;rev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;19.10b0&lt;/span&gt;
    &lt;span class="nt"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;black&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Теперь включим &lt;code&gt;pre-commit&lt;/code&gt; в текущем репозитории.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pre-commit install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Рекомендую добавить эту команду в документацию для разработчиков. Это всё,
что нужно будет сделать вашим коллегам, чтобы хуки заработали и у них тоже.&lt;/p&gt;
&lt;p&gt;Можно убедиться, что &lt;code&gt;pre-commit&lt;/code&gt; заменил наш старый хук на свой:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ cat .git/hooks/pre-commit
&lt;span class="c1"&gt;#!/usr/bin/env python3.8&lt;/span&gt;
&lt;span class="c1"&gt;# File generated by pre-commit: https://pre-commit.com&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;При этом старый хук был переименован в &lt;code&gt;pre-commit.legacy&lt;/code&gt;.
Умный &lt;code&gt;pre-commit&lt;/code&gt; будет запускать и его тоже, чтобы не сломать текущее поведение.
Если это не желательно, то проще всего удалить этот старый файл:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ rm .git/hooks/pre-commit.legacy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Проверим, что конфигурационный файл валиден, а заодно и что всё нынешнее
содержимое репозитория удовлетворяет описанными правилам:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pre-commit run --all-files
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;При первом запуске &lt;code&gt;pre-commit&lt;/code&gt; скачает и закэширует все файлы, необходимые
для выполнения проверок. Для проверок, которые написаны на Python,
будут созданы изолированные виртуальные окружения, так что они никак не
будут зависеть от виртуального окружения проекта. Первый раз из-за скачивания
зависимостей эта команда может выполняться секунд 30 (в зависимости от скорости
интернета), но перезапустите её ещё раз и она завершится за секунду.&lt;/p&gt;
&lt;p&gt;Возможно, &lt;code&gt;pre-commit&lt;/code&gt; найдёт проблемы или даже исправит некоторые файлы.&lt;/p&gt;
&lt;p&gt;Если проверка отработала без ошибок, то конфиг нужно добавить в Git:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ git add .pre-commit-config.yaml
$ git commit -m “Add pre-commit configuration”
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Плагины/зависимости для проверок&lt;/h2&gt;
&lt;p&gt;Из-за того, что для проверок, написанных на Python, создаются отдельные
виртуальные окружения, может быть не совсем понятно, как устанавливать
плагины для таких программ, как, например, &lt;code&gt;flake8&lt;/code&gt;.
Для &lt;code&gt;flake8&lt;/code&gt; важно, чтобы все его плагины были установлены с ним в одно
виртуальное окружение, иначе он просто не сможет их найти.
Специально для этого у &lt;code&gt;pre-commit&lt;/code&gt; предусмотрена настройка
&lt;code&gt;additional_dependencies&lt;/code&gt;, которая используется вот таким образом:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;  &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;https://gitlab.com/pycqa/flake8&amp;#39;&lt;/span&gt;
    &lt;span class="nt"&gt;rev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;3.8.2&lt;/span&gt;
    &lt;span class="nt"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;flake8&lt;/span&gt;
        &lt;span class="nt"&gt;additional_dependencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;flake8-bugbear&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;При следующем запуске &lt;code&gt;pre-commit&lt;/code&gt; обнаружит новую зависимость и установит её
в то же виртуальное окружение, что и &lt;code&gt;flake8&lt;/code&gt;. Теперь перед коммитом будет
выполняться не просто голый &lt;code&gt;flake8&lt;/code&gt;, но ещё и с дополнительным плагином.
Таких зависимостей может быть сколько угодно.&lt;/p&gt;
&lt;h2&gt;Использование&lt;/h2&gt;
&lt;p&gt;Теперь можно вообще забыть про существование &lt;code&gt;pre-commit&lt;/code&gt; и просто пользоваться
Git как обычно, а &lt;code&gt;pre-commit&lt;/code&gt; будет выполнять все описанные проверки, изредка
беспокоя вас прерванными операциями, если будут найдены какие-нибудь проблемы.
Давайте снова попробуем закоммитить тот сломанный файл с пробелами и кавычками:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ git add example.py
$ git commit -v
Fix End of Files..................................Passed
Check &lt;span class="k"&gt;for&lt;/span&gt; added large files.......................Passed
Check &lt;span class="k"&gt;for&lt;/span&gt; byte-order marker.......................Passed
Detect Private Key................................Passed
Check python ast..................................Passed
flake8............................................Failed
- hook id: flake8
- &lt;span class="nb"&gt;exit&lt;/span&gt; code: &lt;span class="m"&gt;1&lt;/span&gt;
example.py:1:7: E201 whitespace after &lt;span class="s1"&gt;&amp;#39;(&amp;#39;&lt;/span&gt;
example.py:1:22: E202 whitespace before &lt;span class="s1"&gt;&amp;#39;)&amp;#39;&lt;/span&gt;
black.............................................Failed
- hook id: black
- files were modified by this hook
reformatted example.py
All &lt;span class="k"&gt;done&lt;/span&gt;! ✨ 🍰 ✨
&lt;span class="m"&gt;1&lt;/span&gt; file reformatted.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ожидаемо, операция прервалась, но в этот раз мы получили все нужные
сообщения об ошибках. Кроме того, файл даже был автоматически отформатирован
при помощи &lt;code&gt;black&lt;/code&gt;, так что ничего даже вручную делать не нужно.
Просто ещё раз запускаем те же команды, и в этот раз проверки должны пройти.&lt;/p&gt;
&lt;p&gt;На YAML программировать намного приятнее, чем на &lt;code&gt;bash&lt;/code&gt;! Да, честно говоря,
практически на чём угодно писать приятнее, чем на &lt;code&gt;bash&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;Альтернативы &lt;code&gt;pre-commit&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;Мне проще всего использовать инструменты, написанные на знакомом мне языке,
но вообще &lt;code&gt;pre-commit&lt;/code&gt; далеко не уникальный инструмент и имеет множество
альтернатив. Если вы пишете не на Python, то может быть вам будут ближе другие
инструменты, хотя все они имеют примерно схожий функционал:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/typicode/husky"&gt;&lt;code&gt;husky&lt;/code&gt; на JS&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Arkweid/lefthook"&gt;&lt;code&gt;lefthook&lt;/code&gt; на Go&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sds/overcommit"&gt;&lt;code&gt;overcommit&lt;/code&gt; на Ruby&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Возможно, вы также найдете для себя что-то полезное на странице
&lt;a href="https://github.com/aitemr/awesome-git-hooks"&gt;“Awesome Git hooks”&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Заключение&lt;/h1&gt;
&lt;p&gt;Я всегда стараюсь использовать Git-хуки для запуска всех быстрых проверок.
Это не дает мне забывать о проверках, и позволяет быстрее получать обратную связь.&lt;/p&gt;
&lt;p&gt;Представьте ситуацию, когда сидишь и ждёшь результатов проверок от CI,
которые могут быть достаточно долгими
(на проекте, где я сейчас работаю, тесты выполняются 8-10 минут),
видишь красный крестик, идёшь посмотреть, что же там сломалось,
а там всё &lt;em&gt;почти&lt;/em&gt; отлично — тесты прошли, но только &lt;code&gt;flake8&lt;/code&gt; нашёл лишний пробел.
Чинишь лишний пробел и снова ждёшь 10 минут, чтобы получить свою зелёную галочку.
Дак вот хуки спасают от таких ситуаций, потому что все тривиальные проблемы
обнаруживаются за несколько секунд локально на моей машине и
никогда не попадают в историю Git.&lt;/p&gt;
&lt;p&gt;Настоятельно рекомендую пользоваться Git-хуками. Это позволит вам не тратить
время на ерунду, и в итоге быть более эффективным и довольным разработчиком.&lt;/p&gt;
&lt;p&gt;Примеры из поста можно найти
&lt;a href="https://github.com/and-semakin/git_hooks_example"&gt;здесь&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Если понравилась статья, то
&lt;a href="https://semakin.dev/pages/subscribe/"&gt;подпишитесь на уведомления&lt;/a&gt;
о новых постах в блоге, чтобы ничего не пропустить!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Ссылки&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pre-commit.com/"&gt;документация &lt;code&gt;pre-commit&lt;/code&gt;&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pre-commit.com/hooks.html"&gt;реестр хуков для &lt;code&gt;pre-commit&lt;/code&gt;&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pre-commit/pre-commit"&gt;репозиторий &lt;code&gt;pre-commit&lt;/code&gt; на GitHub&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks"&gt;документация Git про хуки&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"></category><category term="python"></category><category term="pre-commit"></category><category term="git"></category></entry><entry><title>Форматируем код при помощи black</title><link href="https://semakin.dev/2020/05/black/" rel="alternate"></link><published>2020-05-26T13:54:45+05:00</published><updated>2020-05-26T21:54:31+05:00</updated><author><name>Андрей Семакин</name></author><id>tag:semakin.dev,2020-05-26:/2020/05/black/</id><summary type="html">&lt;p&gt;Автоматическое форматирование кода избавляет от бесконечных споров про пробелы, запятые и скобки, экономя нервы и часы код-ревью.&lt;/p&gt;</summary><content type="html">&lt;h1&gt;Почему полезно форматировать код&lt;/h1&gt;
&lt;p&gt;В сообществе Python ещё лет 20 назад осознали ценность форматирования кода, когда
на свет появился &lt;a href="https://www.python.org/dev/peps/pep-0008/"&gt;PEP8&lt;/a&gt; —
документ, который призван сделать весь код, написанный на Python,
оформленным одинаково и поставить точку в бесконечных спорах о стиле оформления
кода. PEP8 и правда глубоко вошёл в идеологию Python. Писать код, который
нарушает PEP8 — это считается очень плохой практикой. Это одна из первых
заповедей, про которые говорят начинающим питонистам.&lt;/p&gt;
&lt;p&gt;Почему же консистентный стиль оформления кода так важен, что этому посвящен
целый PEP? Самая главная мысль — читаемость важна. Вот какие ещё мысли у меня
возникают по поводу стиля оформления кода.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Код быстро меняется.&lt;/strong&gt;
Реалии разработки ПО сегодня очень отличаются от
того, что было 50 или 30 лет назад. Тогда код мог быть написан единожды
программистом-одиночкой, и работать десятилетиями без изменений.
Сегодня код чаще разрабатывается командами программистов, и меняться он
должен постоянно, реагируя на изменчивые обстоятельства внешней среды.
К консистентно оформленному коду намного проще вернуться в будущем,
понять его, найти ошибку и исправить, или допилить новую фичу.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Код пишется один раз, а читается множество раз.&lt;/strong&gt;
Сэкономленная сегодня минута на консистентном оформлении кода может вылиться
в часы чтения и попыток понять этот же код завтра (или через полгода).
Когда код оформлен одинаково, мозгу не нужно отвлекаться на незначащие
детали оформления. Так намного проще вникнуть в суть написанного.
В командной разработке просто необходим общий стиль,
которого будут придерживаться все.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Код пишется для людей, а не для компьютеров.&lt;/strong&gt; Машинам абсолютно
индифферентно, сколько вы там ставите пробелов, пустых строк, какую
длину строки используете или где вы ставите запятые. Если код синтаксически
верен, то компьютер отлично вас поймёт. Языки высокого уровня
(такие как Python) были специально разработаны для того,
чтобы как можно дальше уйти от написания непонятного людям
&lt;a href="https://ru.wikipedia.org/wiki/%D0%9C%D0%B0%D1%88%D0%B8%D0%BD%D0%BD%D1%8B%D0%B9_%D0%BA%D0%BE%D0%B4"&gt;машинного кода&lt;/a&gt;.
Мы пишем код для людей. Но людям очень важны такие незначащие детали,
как отступы, запятые и скобки.
С их помощью можно сделать код как понятным с первого взгляда,
так и сложным и запутанным.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Время программиста стоит дорого.&lt;/strong&gt;
Писать хорошо оформленный код просто дешевле на долгих дистанциях.
Допустим, что оформление кода поможет ускорить его понимание пусть даже
всего на 1 минуту. Умножьте на количество человек, которые будут читать
этот код, и на (потенциально бесконечное) количество раз, которое
они будут к нему возвращаться, и на стоимость работы одного программиста
за минуту — получаете кучу сэкономленных деняк.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Консистентный код сокращает количество дискуссий на ревью&lt;/strong&gt;,
потому что коллегам уже не приходится напоминать про скобки и запятые.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Консистентный код порождает меньшие &lt;a href="https://ru.wikipedia.org/wiki/Diff"&gt;диффы&lt;/a&gt;&lt;/strong&gt;,
так что его элементарно проще ревьювить.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Следование PEP8 можно автоматически контролировать при помощи таких инструментов,
как &lt;a href="https://semakin.dev/2020/05/python_linters/"&gt;flake8 или pylint&lt;/a&gt;, но тогда форматировать
код придётся вручную. Как мы уже выяснили, время разработчика стоит дорого.
Можно ли как-то автоматизировать этот процесс?&lt;/p&gt;
&lt;p&gt;Кроме того, PEP8 описывает лишь основные правила оформления кода, но оставляет
свободу интерпретации во множестве краевых случаев, из-за чего может
появиться неконсистентность оформления. Есть ли какие-нибудь более
строгие конвенции, чем PEP8?&lt;/p&gt;
&lt;h1&gt;&lt;code&gt;black&lt;/code&gt; — бескомпромиссный форматтер кода&lt;/h1&gt;
&lt;p&gt;Почему бескомпромиссный? Потому что &lt;code&gt;black&lt;/code&gt; навязывает свой стиль кода,
и его практически нельзя конфигурировать. Возможно, вам пока что так не кажется,
но это огромный плюс &lt;code&gt;black&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Попробуйте в команде хотя бы из нескольких человек прийти к согласию по поводу
того, как код выглядит лучше всего и читается проще всего.&lt;/p&gt;
&lt;p&gt;Например, оба варианта абсолютно валидны с точки зрения PEP8, но у
каждого стиля есть свои поклонники и противники.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# так?&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;arg1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="s2"&gt;&amp;quot;arg2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;kwarg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;value&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# или так?&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;arg1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;arg2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;kwarg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;value&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;У меня был опыт попытки выработать в команде конвенцию, и это не так просто.
Не потому что люди плохие или упрямые, просто у каждого своё понимание
красоты и удобства. Наверняка, начнётся борьба вкусов и мнений.
&lt;code&gt;black&lt;/code&gt; избавляет от всех этих обсуждений — есть готовый стиль оформления кода,
где все решения уже приняты за вас.&lt;/p&gt;
&lt;p&gt;Да, иногда &lt;code&gt;black&lt;/code&gt; выдаёт не самый красивый код, но, если подумать,
красота кода не так важна, как консистентность, последовательность, одинаковость.
&lt;code&gt;black&lt;/code&gt; осознанно приносит красоту в жертву консистентности.&lt;/p&gt;
&lt;p&gt;Скажу по собственному опыту, что к отформатированному &lt;code&gt;black&lt;/code&gt; коду привыкаешь
очень быстро, и буквально через несколько дней просто перестаешь
замечать форматирование вообще. Просто читаешь код.&lt;/p&gt;
&lt;h2&gt;Установка&lt;/h2&gt;
&lt;p&gt;К сожалению, на момент написания этого поста,
&lt;a href="https://github.com/psf/black/issues/517"&gt;&lt;code&gt;black&lt;/code&gt; не имеет стабильных релизов&lt;/a&gt;,
пока что есть только бета-версии. Смею вас заверить, что &lt;code&gt;black&lt;/code&gt; даже в
бета-версии уже достаточно стабилен и используется в куче серьезных проектов.
Правда, отсутствие стабильных релизов немного усложняет установку.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;black&lt;/code&gt; устанавливается из PyPI. Давайте выясним, какая на данный момент
последняя доступная версия при помощи следующего трюка (или можно просто её
посмотреть на &lt;a href="https://pypi.org/project/black/#history"&gt;странице проекта на PyPI&lt;/a&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pip install &lt;span class="nv"&gt;black&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;
Collecting &lt;span class="nv"&gt;black&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;
  ERROR: Could not find a version that satisfies the requirement &lt;span class="nv"&gt;black&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;from versions: &lt;span class="m"&gt;18&lt;/span&gt;.3a0, &lt;span class="m"&gt;18&lt;/span&gt;.3a1, &lt;span class="m"&gt;18&lt;/span&gt;.3a2, &lt;span class="m"&gt;18&lt;/span&gt;.3a3, &lt;span class="m"&gt;18&lt;/span&gt;.3a4, &lt;span class="m"&gt;18&lt;/span&gt;.4a0, &lt;span class="m"&gt;18&lt;/span&gt;.4a1, &lt;span class="m"&gt;18&lt;/span&gt;.4a2, &lt;span class="m"&gt;18&lt;/span&gt;.4a3, &lt;span class="m"&gt;18&lt;/span&gt;.4a4, &lt;span class="m"&gt;18&lt;/span&gt;.5b0, &lt;span class="m"&gt;18&lt;/span&gt;.5b1, &lt;span class="m"&gt;18&lt;/span&gt;.6b0, &lt;span class="m"&gt;18&lt;/span&gt;.6b1, &lt;span class="m"&gt;18&lt;/span&gt;.6b2, &lt;span class="m"&gt;18&lt;/span&gt;.6b3, &lt;span class="m"&gt;18&lt;/span&gt;.6b4, &lt;span class="m"&gt;18&lt;/span&gt;.9b0, &lt;span class="m"&gt;19&lt;/span&gt;.3b0, &lt;span class="m"&gt;19&lt;/span&gt;.10b0&lt;span class="o"&gt;)&lt;/span&gt;
ERROR: No matching distribution found &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;black&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Команда завершится ошибкой, но выведет список доступных версий.
Найдем последнюю доступную версию и запомним её.&lt;/p&gt;
&lt;p&gt;Внутри виртуального окружения нужно выполнить, заменив версию на последнюю:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pip install &lt;span class="nv"&gt;black&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;.10b0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;А если вы используете &lt;code&gt;pipenv&lt;/code&gt; или &lt;code&gt;poetry&lt;/code&gt;, то вот так:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv install --dev &lt;span class="nv"&gt;black&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;.10b0
$ poetry add --dev &lt;span class="nv"&gt;black&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;.10b0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Обратите внимание, что при установке &lt;code&gt;black&lt;/code&gt; через &lt;code&gt;pipenv&lt;/code&gt; обязательно нужно
указывать конкретную версию. Я описал, что произойдёт, если этого не сделать,
а взамен разрешить &lt;code&gt;pipenv&lt;/code&gt; устанавливать пре-релизные версии в 
&lt;a href="https://semakin.dev/2020/04/pipenv/"&gt;посте про &lt;code&gt;pipenv&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Использование&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;black&lt;/code&gt; имеет очень интуитивный интерфейс командной строки.&lt;/p&gt;
&lt;p&gt;Вот так можно отформатировать все файлы в текущей директории (и
рекурсивно в поддиректориях):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ black .
reformatted /home/br0ke/git/pipenv/pipenv/cli/__init__.py
reformatted /home/br0ke/git/pipenv/pipenv/__init__.py
...
All &lt;span class="k"&gt;done&lt;/span&gt;! ✨ 🍰 ✨
&lt;span class="m"&gt;50&lt;/span&gt; files reformatted, &lt;span class="m"&gt;11&lt;/span&gt; files left unchanged.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;И это практически единственная команда, которую вам нужно запомнить.&lt;/p&gt;
&lt;p&gt;А вот так можно отформатировать один конкретный файл:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ black setup.py 
reformatted setup.py
All &lt;span class="k"&gt;done&lt;/span&gt;! ✨ 🍰 ✨
&lt;span class="m"&gt;1&lt;/span&gt; file reformatted.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Интеграция с редактором/IDE&lt;/h2&gt;
&lt;p&gt;Очень удобно, когда форматтер запускается прямо из редактора кода автоматически,
например, при сохранении файла.
&lt;code&gt;black&lt;/code&gt; так или иначе можно интегрировать со всеми известными науке редакторами.
Процесс подробно описан &lt;a href="https://github.com/psf/black/blob/master/docs/editor_integration.md"&gt;здесь&lt;/a&gt;.
Обязательно это сделайте, иначе не ощутите всей прелести
автоматического форматирования.&lt;/p&gt;
&lt;h2&gt;Использование в CI&lt;/h2&gt;
&lt;p&gt;А ещё нужно настроить запуск &lt;code&gt;black&lt;/code&gt; в сервисе для непрерывной интеграции (CI),
например, GitHub Actions, GitLab CI или Travis CI.
Так &lt;code&gt;black&lt;/code&gt; сможет блокировать пулл-реквесты (или мердж-реквесты),
в которых содержится неотформатированный код.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ black --check .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;В режиме “проверки” &lt;code&gt;black&lt;/code&gt; не будет форматировать файлы,
а просто напечатает список неотформатированных файлов и завершится кодом ошибки,
что должно “уронить” всю проверку целиком.&lt;/p&gt;
&lt;h2&gt;Конфигурация&lt;/h2&gt;
&lt;p&gt;Кое-какую минимальную возможность настройки &lt;code&gt;black&lt;/code&gt; все-таки предоставляет.
Стоит отметить, что в большинстве случаев этого делать не придётся,
потому что у &lt;code&gt;black&lt;/code&gt; достаточно разумные настройки по умолчанию.&lt;/p&gt;
&lt;p&gt;Вот так можно настроить максимальную длину строки и файлы,
которые форматировать не нужно. В &lt;code&gt;pyproject.toml&lt;/code&gt; в корне проекта добавьте:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[tool.black]&lt;/span&gt;
&lt;span class="n"&gt;line-length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt;
&lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;\.pyi?$&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;exclude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s"&gt;(&lt;/span&gt;
&lt;span class="s"&gt;  /(&lt;/span&gt;
&lt;span class="s"&gt;      \.eggs         # exclude a few common directories in the&lt;/span&gt;
&lt;span class="s"&gt;    | \.git          # root of the project&lt;/span&gt;
&lt;span class="s"&gt;    | \.hg&lt;/span&gt;
&lt;span class="s"&gt;    | \.mypy_cache&lt;/span&gt;
&lt;span class="s"&gt;    | \.tox&lt;/span&gt;
&lt;span class="s"&gt;    | \.venv&lt;/span&gt;
&lt;span class="s"&gt;    | _build&lt;/span&gt;
&lt;span class="s"&gt;    | buck-out&lt;/span&gt;
&lt;span class="s"&gt;    | build&lt;/span&gt;
&lt;span class="s"&gt;    | dist&lt;/span&gt;
&lt;span class="s"&gt;  )/&lt;/span&gt;
&lt;span class="s"&gt;  | foo.py           # also separately exclude a file named foo.py in&lt;/span&gt;
&lt;span class="s"&gt;                     # the root of the project&lt;/span&gt;
&lt;span class="s"&gt;)&lt;/span&gt;
&lt;span class="s"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Настройка &lt;code&gt;flake8&lt;/code&gt;, чтобы он не противоречил &lt;code&gt;black&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;У &lt;code&gt;flake8&lt;/code&gt; своё мнение по поводу того, как должен быть отформатирован код,
которое не всегда совпадает с мнением &lt;code&gt;black&lt;/code&gt;.
Чтобы не возникало конфликтов, рекомендуется выключить некоторые
проверки &lt;code&gt;flake8&lt;/code&gt;, по примеру того, как это сделано
&lt;a href="https://github.com/psf/black/blob/master/.flake8"&gt;в репозитории &lt;code&gt;black&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Что меня бесит в стиле &lt;code&gt;black&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Есть некоторые моменты, с которыми я категорически несогласен.
Думаю, рассказать про них тоже нужно.&lt;/p&gt;
&lt;p&gt;Рассмотрим пример исходного кода:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;What a wonderful day!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;I don&amp;#39;t know you. Are you new here?&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user_name&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Andrey&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Nice to meet you again!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;&amp;quot;Today is&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Я специально сделал побольше аргументов в функцию &lt;code&gt;print()&lt;/code&gt;,
чтобы вызов функции стал достаточно длинным, чтобы &lt;code&gt;black&lt;/code&gt;
разнёс его на несколько строк. Обратите внимание на
тернарный оператор. Теперь отформатируем и посмотрим
на результат:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;What a wonderful day!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;I don&amp;#39;t know you. Are you new here?&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user_name&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Andrey&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Nice to meet you again!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;Today is&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Тернарный оператор затерялся среди других аргументов функции, его
теперь очень трудно заметить. Чтобы исправить, давайте возьмём тернарный
оператор в скобки и ещё раз отформатируем:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;What a wonderful day!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;I don&amp;#39;t know you. Are you new here?&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user_name&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Andrey&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Nice to meet you again!&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;Today is&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Теперь намного понятнее, не правда ли? Такое может произойти не только
с тернарными операторами, но и с длинными арифметическими выражениями,
и с длинными строками, которые разбиты на несколько частей:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="c1"&gt;# Обратите внимание, что между следующими &amp;quot;строками&amp;quot; нет&lt;/span&gt;
     &lt;span class="c1"&gt;# запятой, так что фактически это один строковый литерал.&lt;/span&gt;
     &lt;span class="c1"&gt;# Хороший приём для объединения длинных строк внутри скобок.&lt;/span&gt;
     &lt;span class="s2"&gt;&amp;quot;Lorem ipsum dolor sit amet, consectetur adipiscing elit. &amp;quot;&lt;/span&gt;
     &lt;span class="s2"&gt;&amp;quot;Sed dolor massa, mollis a commodo in, molestie in risus.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Эта функция имеет два аргумента — булевый и строковый. Отформатируем:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;Lorem ipsum dolor sit amet, consectetur adipiscing elit. &amp;quot;&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;Sed dolor massa, mollis a commodo in, molestie in risus.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;black&lt;/code&gt; опять не помог сделать код читаемее. Выглядит так, будто функция
имеет три аргумента, хотя на самом деле их только два. Нужно хорошо
присмотреться, чтобы заметить отсутствие запятой.
Поставим скобки вручную и отформатируем ещё раз:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Lorem ipsum dolor sit amet, consectetur adipiscing elit. &amp;quot;&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Sed dolor massa, mollis a commodo in, molestie in risus.&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Стало в сто раз лучше. Теперь всё очевидно.&lt;/p&gt;
&lt;p&gt;Надеюсь, что когда-нибудь эту &lt;a href="https://github.com/psf/black/issues/620"&gt;шероховатость&lt;/a&gt;
починят, а до тех пор я просто вручную ставлю скобки вокруг
вот такого некрасивого кода. Такое случается не так уж часто.
В целом, даже почти не больно.&lt;/p&gt;
&lt;h1&gt;Заключение&lt;/h1&gt;
&lt;p&gt;Консистентное форматирование кода — это невероятно важно,
потому что упрощает восприятие кода другими людьми
(или самим же автором кода, но через полгода). Автоматическое форматирование
кода (почти) не требует вообще никаких усилий со стороны автора.&lt;/p&gt;
&lt;p&gt;Хоть &lt;code&gt;black&lt;/code&gt; и имеет свои недостатки, он всё равно явно окупает
усилия, затраченные на ручную расстановку скобок, потому что случается
это довольно редко. Как и любой инструмент, &lt;code&gt;black&lt;/code&gt; дорабатывается,
и будем верить, что все проблемы рано или поздно исправят.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;black&lt;/code&gt;, наряду с &lt;code&gt;flake8&lt;/code&gt; и &lt;code&gt;pytest&lt;/code&gt;, попал в мой набор незаменимых
инструментов, которые я пытаюсь использовать во всех своих проектах.
И вам рекомендую.&lt;/p&gt;
&lt;p&gt;Если понравилась статья, то
&lt;a href="https://semakin.dev/pages/subscribe/"&gt;подпишитесь на уведомления&lt;/a&gt;
о новых постах в блоге, чтобы ничего не пропустить!&lt;/p&gt;
&lt;h1&gt;Дополнительное чтение&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://black.readthedocs.io/en/stable/"&gt;документация &lt;code&gt;black&lt;/code&gt;&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/psf/black"&gt;репозиторий проекта на GitHub&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google/yapf"&gt;&lt;code&gt;yapf&lt;/code&gt; — ещё один форматтер кода, который можно гибко конфигурировать&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/timothycrosley/isort"&gt;&lt;code&gt;isort&lt;/code&gt; — сортировка импортов с разбиением на секции, удобно использовать вместе с &lt;code&gt;black&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"></category><category term="python"></category><category term="pep8"></category><category term="black"></category><category term="formatter"></category></entry><entry><title>Почему print("Hello") работает в Python 2</title><link href="https://semakin.dev/2020/05/python2_print/" rel="alternate"></link><published>2020-05-24T11:45:37+05:00</published><updated>2020-05-24T11:59:14+05:00</updated><author><name>Андрей Семакин</name></author><id>tag:semakin.dev,2020-05-24:/2020/05/python2_print/</id><summary type="html">&lt;p&gt;Объяснение синтаксического трюка, который позволяет использовать print() как функцию в Python 2 и 3.&lt;/p&gt;</summary><content type="html">&lt;h1&gt;Почему print("Hello") работает в Python 2&lt;/h1&gt;
&lt;p&gt;Одним из самых заметных отличий Python 3 от Python 2 является разжалование
&lt;code&gt;print&lt;/code&gt; из инструкции языка (statement) до обычной функции.&lt;/p&gt;
&lt;p&gt;Python 3:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# простое использование&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# выводит: Hello&lt;/span&gt;

&lt;span class="c1"&gt;# переданные аргументы по умолчанию объединяются через пробел&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;world!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# выводит: Hello world!&lt;/span&gt;

&lt;span class="c1"&gt;# объединяющий символ можно заменить на свой&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Pythonic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;attacks!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;_&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# выводит: Pythonic_attacks!&lt;/span&gt;

&lt;span class="c1"&gt;# можно заменить символ конца строки на пустую строку,&lt;/span&gt;
&lt;span class="c1"&gt;# чтобы продолжить выводить что-нибудь в той же строке&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;world!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# выводит: Hello world!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Python 2:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# простое использование&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;# выводит: Hello&lt;/span&gt;

&lt;span class="c1"&gt;# перечисленные аргументы объединяются через пробел&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;world!&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;# выводит: Hello world!&lt;/span&gt;

&lt;span class="c1"&gt;# если нужен не пробел, а другой символ для объединения,&lt;/span&gt;
&lt;span class="c1"&gt;# то остаётся делать только вот так:&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;_&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;world!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="c1"&gt;# выводит: Hello_world!&lt;/span&gt;

&lt;span class="c1"&gt;# можно при помощи запятой в конце избежать вывода \n (символа конца строки)&lt;/span&gt;
&lt;span class="c1"&gt;# и продолжить печатать что-нибудь на той же строке:&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;world!&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;# выводит: Hello world!&lt;/span&gt;

&lt;span class="c1"&gt;# если сделать специальный импорт из “будущего”, то&lt;/span&gt;
&lt;span class="c1"&gt;# в Python 2.6+ `print` начинает работать как функция:&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;print_function&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;world!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# выводит: Hello world!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Обычно инструкциями в Python являются только очень базовые вещи,
такие как условия (&lt;code&gt;if&lt;/code&gt;), циклы (&lt;code&gt;for&lt;/code&gt;, &lt;code&gt;while&lt;/code&gt;),
возврат из функций (&lt;code&gt;return&lt;/code&gt;) и т.д.&lt;/p&gt;
&lt;p&gt;Преобразование &lt;code&gt;print&lt;/code&gt; в функцию было сделано
&lt;a href="https://snarky.ca/why-print-became-a-function-in-python-3/"&gt;во имя упрощения синтаксиса языка&lt;/a&gt;.
Инструкция &lt;code&gt;print&lt;/code&gt; для работы требовала значительного количества синтаксических
конструкций, например, для отсутствия символа переноса строки (&lt;code&gt;,&lt;/code&gt;) или
перенаправления вывода в файл (&lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt;).
Всё это можно было заставить играть по общим правилам
(и даже значительно расширить функционал) через позиционные и именованные
аргументы функций, вообще без какого-либо расширения синтаксиса языка.
Кроме того, &lt;code&gt;print&lt;/code&gt; как функцию можно использовать гораздо гибче — например,
фиксируя часть аргументов
(&lt;a href="https://ru.wikipedia.org/wiki/%D0%9A%D0%B0%D1%80%D1%80%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5"&gt;каррирование&lt;/a&gt;)
через
&lt;a href="https://docs.python.org/3/library/functools.html#functools.partial"&gt;&lt;code&gt;functools.partial&lt;/code&gt;&lt;/a&gt;
или использовать её в других функциях (передавать саму функцию как аргумент), типа
&lt;a href="https://docs.python.org/3/library/functions.html#map"&gt;&lt;code&gt;map()&lt;/code&gt;&lt;/a&gt; или
&lt;a href="https://docs.python.org/3/library/functions.html#filter"&gt;&lt;code&gt;filter()&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Недавно в исходниках библиотеки
&lt;a href="https://github.com/brandon-rhodes/logging_tree"&gt;&lt;code&gt;logging_tree&lt;/code&gt;&lt;/a&gt;
я увидел, что
&lt;a href="https://github.com/brandon-rhodes/logging_tree/blob/e3743d223d465a1ecbc5d01a91d54c237e04ccec/logging_tree/format.py#L17"&gt;&lt;code&gt;print&lt;/code&gt; используется как функция&lt;/a&gt;
без каких-либо импортов из будущего, при этом у библиотеки
&lt;a href="https://github.com/brandon-rhodes/logging_tree/blob/e3743d223d465a1ecbc5d01a91d54c237e04ccec/setup.py#L15"&gt;заявлена поддержка версий Python&lt;/a&gt;,
начиная аж с 2.3:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;build_description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Меня это удивило. Я даже
&lt;a href="https://github.com/brandon-rhodes/logging_tree/pull/24"&gt;создал пулл-реквест&lt;/a&gt;,
будучи уверен, что нашёл ошибку. После того, как автор возразил,
что к нему не поступало никаких жалоб по поводу такого использования &lt;code&gt;print&lt;/code&gt;,
я решил всё-таки проверить, как это работает.
И к ещё большему моему удивлению, это и правда работало на всех версиях Python
2, до которых я смог дотянуться, включая 2.7, 2.6, 2.5, 2.4 и даже 2.3:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# как??&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# выводит: Hello&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Немного поразмыслив и почитав, я осознал, где тут собака зарыта.
Хоть это и выглядит как вызов функции &lt;code&gt;print()&lt;/code&gt;, на самом деле это всё та же
старая добрая инструкция &lt;code&gt;print&lt;/code&gt;. Давайте разложим всё по полочкам:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# сначала отодвинем скобки от print, чтобы было нагляднее, что это инструкция:&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# выводит: Hello&lt;/span&gt;

&lt;span class="c1"&gt;# в Python можно засунуть в скобки любое выражение, но пока оно там одно,&lt;/span&gt;
&lt;span class="c1"&gt;# то скобки не оказывают никакого влияния:&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# получается просто строка: &amp;#39;Hello&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# а если выражений несколько, или там есть запятая, то тогда уже&lt;/span&gt;
&lt;span class="c1"&gt;# создаётся кортеж:&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;  &lt;span class="c1"&gt;# кортеж из одного элемента&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;world&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# кортеж из двух элементов&lt;/span&gt;

&lt;span class="c1"&gt;# в нашем случае выражение только одно, поэтому скобки можно отбросить:&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;# выводит: Hello&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Как выяснилось, в Python 2 &lt;code&gt;print&lt;/code&gt; и правда можно так, что со стороны это будет
выглядеть как вызов функции, но для очень ограниченного множества случаев,
а именно — должен быть только один позиционный аргумент.
Такой код будет успешно выполняться и на Python 2 (благодаря описанному трюку),
и на Python 3 (по-настоящему).&lt;/p&gt;
&lt;p&gt;Разница становится очевидной, если попытаться узнать тип:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# у инструкций нельзя запросить тип, это синтаксически невозможно&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="o"&gt;^&lt;/span&gt;
&lt;span class="ne"&gt;SyntaxError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;invalid&lt;/span&gt; &lt;span class="n"&gt;syntax&lt;/span&gt;

&lt;span class="c1"&gt;# а у функций можно&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;print_function&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;builtin_function_or_method&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Трюки из той же серии:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# условие со скобками как в C:&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Equal!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;

&lt;span class="c1"&gt;# return как функция:&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Это код синтаксически верен, но просто обычно так не пишут. И правильно.&lt;/p&gt;
&lt;h1&gt;Заключение&lt;/h1&gt;
&lt;p&gt;Python 2.7 недавно
&lt;a href="https://mail.python.org/archives/list/python-dev@python.org/message/OFCIETIXLX34X7FVK5B5WPZH22HXV342/"&gt;получил свой последний релиз&lt;/a&gt;
с исправлениями ошибок, и больше никогда не будет обновляться —
даже новые найденные баги, связанные с безопасностью, не будут пофикшены.
Хочется верить, что все заинтересованные уже обновились до Python 3,
и никто больше активно не использует Python 2, в 2020-то году.
К сожалению, вряд ли это так.
Боюсь, что предсмертная агония Python 2 продлится ещё как минимум несколько лет.
Поэтому знать про него хотя бы что-то всё равно полезно.&lt;/p&gt;
&lt;p&gt;Благодаря этому случаю я узнал про странный трюк с &lt;code&gt;print&lt;/code&gt; и извлёк урок,
что нужно как минимум проверять свой фикс, прежде чем засылать куда-то
пулл-реквест.&lt;/p&gt;
&lt;p&gt;Если понравилась статья, то
&lt;a href="https://semakin.dev/pages/subscribe/"&gt;подпишитесь на уведомления&lt;/a&gt;
о новых постах в блоге, чтобы ничего не пропустить!&lt;/p&gt;
&lt;h1&gt;Дополнительное чтение&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://realpython.com/python-print/"&gt;Большой гайд про print на RealPython&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://snarky.ca/why-print-became-a-function-in-python-3/"&gt;Брэтт Кэннон, "Why &lt;code&gt;print&lt;/code&gt; became a function in Python 3"&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Обложка: &lt;a href="https://www.pexels.com/photo/vintage-letters-typo-vintage-typewriter-101710/"&gt;Photo by mali maeder from Pexels&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="blog"></category><category term="python"></category><category term="print"></category></entry><entry><title>Chocolatey — пакетный менеджер для Windows</title><link href="https://semakin.dev/2020/05/chocolatey/" rel="alternate"></link><published>2020-05-18T13:22:50+05:00</published><updated>2020-05-19T21:39:59+05:00</updated><author><name>Андрей Семакин</name></author><id>tag:semakin.dev,2020-05-18:/2020/05/chocolatey/</id><summary type="html">&lt;p&gt;Самая важная программа для любителей окошек.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Как сказал один мудрец (и по совместительству просто мой друг):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“нет плохих или хороших операционных систем, есть подходящие и
не подходящие для решения конкретных пользовательских задач”.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Глубокая мысль, я с этим полностью согласен.
Хотя для решения конкретно моих рабочих задач намного лучше подходят
другие ОС, у Windows тоже есть своя ниша.
Я уже давно привык пользоваться в повседневной деятельности
Unix-подобными операционными системами, такими как Fedora Linux и macOS.
Но недавно я решил совершить очередной набег в мир Windows
— попробовать новые классные фичи &lt;a href="https://docs.microsoft.com/ru-ru/windows/wsl/"&gt;WSL2&lt;/a&gt;
да в игры поиграть.&lt;/p&gt;
&lt;p&gt;Только в этот раз я решил сразу сделать всё по-правильному, и
устанавливать весь софт так, чтобы им потом было удобно управлять и обновлять.
В поисках решения этой задачи я открыл для себя
&lt;a href="https://chocolatey.org/"&gt;замечательный инструмент Chocolatey&lt;/a&gt;,
который теперь считаю жизненно необходимым для комфортного пользования
Windows.&lt;/p&gt;
&lt;p&gt;Chocolatey — это пакетный менеджер для Windows,
примерно как &lt;a href="https://ru.wikipedia.org/wiki/Advanced_Packaging_Tool"&gt;&lt;code&gt;apt&lt;/code&gt;&lt;/a&gt;
в мире Debian/Ubuntu или
&lt;a href="https://ru.wikipedia.org/wiki/DNF_(%D0%BC%D0%B5%D0%BD%D0%B5%D0%B4%D0%B6%D0%B5%D1%80_%D0%BF%D0%B0%D0%BA%D0%B5%D1%82%D0%BE%D0%B2)"&gt;&lt;code&gt;dnf&lt;/code&gt;&lt;/a&gt;
в мире Fedora/CentOS.
Пакетный менеджер занимается установкой, удалением и обновлением программ.
Если вам, как и мне, надоело ставить галочки под текстом лицензии
(хоть раз вообще читали?)
и безразлично нажимать кнопку “далее”, то Chocolatey вам поможет.
Он имеет интерфейс командной строки — то, что надо для такого гика, как я!
У Chocolatey &lt;a href="https://chocolatey.org/packages"&gt;большая библиотека пакетов&lt;/a&gt;
— больше 7500 штук, всё популярное там точно есть.&lt;/p&gt;
&lt;p&gt;Хоть я привёл в пример &lt;code&gt;apt&lt;/code&gt; и &lt;code&gt;dnf&lt;/code&gt;, на самом деле,
Chocolatey имеет намного больше общего с
&lt;a href="https://ru.wikipedia.org/wiki/Homebrew_(%D0%BC%D0%B5%D0%BD%D0%B5%D0%B4%D0%B6%D0%B5%D1%80_%D0%BF%D0%B0%D0%BA%D0%B5%D1%82%D0%BE%D0%B2_%D0%B2_Mac_OS)"&gt;Homebrew — пакетным менеджером для macOS&lt;/a&gt;.
В Linux пакетные менеджеры уже давно стали насущной необходимостью
— там этим никого не удивить, а Chocolatey и Homebrew работают в окружениях,
где изначально пакетные менеджеры не предусмотрены.
При этом оба они отлично справляются со своими задачами.&lt;/p&gt;
&lt;p&gt;Chocolatey написан на C# и PowerShell, имеет
&lt;a href="https://github.com/chocolatey/choco"&gt;открытый исходный код&lt;/a&gt;.
Для работы требует Windows 7 или новее.&lt;/p&gt;
&lt;h2&gt;Установка&lt;/h2&gt;
&lt;p&gt;Чтобы установить Chocolatey, нужно запустить командную строку
с правами администратора. Сделать это в Windows 10 можно так:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Нажимаем на клавиатуре кнопку Win или просто открываем меню "Пуск";&lt;/li&gt;
&lt;li&gt;Набираем &lt;code&gt;cmd&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;На найденной программе нажимаем правой кнопкой мыши
и выбираем пункт “Run as administrator” или, в русской локализации,
“Запуск от имени администратора”.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="Run as administrator" src="https://semakin.dev/static/cmd_run_as_administrator.jpg"&gt;&lt;/p&gt;
&lt;p&gt;В открывшееся окно терминала нужно вставить следующую команду:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;@&lt;span class="s2"&gt;&amp;quot;%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe&amp;quot;&lt;/span&gt; -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command &lt;span class="s2"&gt;&amp;quot; [System.Net.ServicePointManager]::SecurityProtocol = 3072; iex ((New-Object System.Net.WebClient).DownloadString(&amp;#39;https://chocolatey.org/install.ps1&amp;#39;))&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; SET &lt;span class="s2"&gt;&amp;quot;PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;На всякий случай, если эта команда устареет и перестанет работать,
или вы предпочитаете вставлять в администраторский терминал команды
только из официальных источников (и правильно делаете), то документация
по установке находится
&lt;a href="https://chocolatey.org/docs/installation#install-with-cmdexe"&gt;вот здесь&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;После того, как команда отработает (у меня установка заняла примерно минуту),
нужно перезапустить терминал. Вместо &lt;code&gt;cmd&lt;/code&gt;
можно запустить PowerShell (тоже от имени администратора) — он немного
удобнее. Можно проверить установку:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;choco -?
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Если вы видите справку по команде, то установка прошла успешно.&lt;/p&gt;
&lt;h2&gt;Установка программ&lt;/h2&gt;
&lt;p&gt;Давайте установим через &lt;code&gt;choco&lt;/code&gt; первые программы.
Все эти программы найдены в &lt;a href="https://chocolatey.org/packages"&gt;реестре пакетов&lt;/a&gt;
и проверены мной — работают.
При установке вы можете заметить, что все программы скачиваются с
официальных сайтов разработчиков.
Если вы переживаете по поводу вирусов, то рекомендую почитать,
&lt;a href="https://chocolatey.org/docs/security"&gt;какие меры предпринимают мейнтейнеры Chocolatey&lt;/a&gt;,
чтобы обеспечить безопасность пакетов (там всё серьезно).&lt;/p&gt;
&lt;p&gt;Во всех командах я добавил флаг &lt;code&gt;-y&lt;/code&gt;, чтобы установщик не задавал
вообще никаких вопросов. Эта команда автоматически соглашается с
лицензиями и разрешает запуск скриптов установки. Ради интереса
можете попробовать убрать этот флаг из команд, и посмотреть, что будет.&lt;/p&gt;
&lt;p&gt;Допустим, что вам нужен браузер (удалите из команды ненужные названия):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;choco install -y googlechrome firefox
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Или текстовый редактор/IDE (удалите из команды ненужные названия):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;choco install -y notepadplusplus.install vscode
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Я являюсь заядлым пользователем PyCharm, который лучше всего устанавливать
через &lt;a href="https://www.jetbrains.com/ru-ru/toolbox-app/"&gt;JetBrains Toolbox&lt;/a&gt;
(как и любые другие IDE от JetBrains):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;choco install -y jetbrainstoolbox
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Инструменты для разработки:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;choco install -y git python3 microsoft-windows-terminal postman
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Мессенджеры и видео-конференции:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;choco install -y telegram.install slack zoom 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Игры:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;choco install -y steam epicgameslauncher
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Всякое прочее-разное полезное:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;choco install -y 7zip vlc paint.net teamviewer qbittorrent thunderbird putty.install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;И для установки даже не пришлось кликать мышью по кнопкам!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://chocolatey.org/docs/commands-uninstall"&gt;Тут&lt;/a&gt; можете почитать
про удаление программ при помощи &lt;code&gt;choco&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Обновление&lt;/h2&gt;
&lt;p&gt;Вот так можно обновить все установленные через &lt;code&gt;choco&lt;/code&gt; программы
до актуальных версий:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;choco upgrade all -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;По-моему, это очень круто и удобно!&lt;/p&gt;
&lt;h1&gt;Заключение&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;choco&lt;/code&gt; — теперь для меня это просто маст-хэв на Windows.
Самая первая программа, которую я буду устанавливать.
Благодаря Chocolatey, для меня Windows стала немного дружелюбнее.
Всем рекомендую попробовать!&lt;/p&gt;
&lt;p&gt;Если понравилась статья, то
&lt;a href="https://semakin.dev/pages/subscribe/"&gt;подпишитесь на уведомления&lt;/a&gt;
о новых постах в блоге, чтобы ничего не пропустить!&lt;/p&gt;
&lt;h1&gt;Дополнительное чтение&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://chocolatey.org/"&gt;официальный сайт Chocolatey&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chocolatey.org/packages"&gt;реестр пакетов&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/chocolatey/choco"&gt;исходный код&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=hfgZYpo5moA"&gt;видео про Chocolatey для тех, кто больше любит воспринимать информацию визуально&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Обложка: &lt;a href="https://www.publicdomainpictures.net/ru/view-image.php?image=285902&amp;amp;picture="&gt;Jean Beaufort, Chocolate&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="blog"></category><category term="windows"></category><category term="choco"></category></entry><entry><title>Линтеры в Python</title><link href="https://semakin.dev/2020/05/python_linters/" rel="alternate"></link><published>2020-05-17T22:38:56+05:00</published><updated>2020-05-31T16:04:25+05:00</updated><author><name>Андрей Семакин</name></author><id>tag:semakin.dev,2020-05-17:/2020/05/python_linters/</id><summary type="html">&lt;p&gt;Обзор популярных статических анализаторов для Python-кода и как они экономят разработчику один день жизни в месяц.&lt;/p&gt;</summary><content type="html">&lt;h1&gt;Линтеры&lt;/h1&gt;
&lt;p&gt;В сообществе Python, как и в любой другой группе людей, существует некое
коллективное знание. Множество людей прошлось по всем возможным граблям
и получило опыт через набитые шишки. Затем через какое-то время,
благодаря выступлениям на конференциях, официальным заявлениям,
документам, статьям в блогах, код-ревью и личному общению,
это знание стало коллективным. Теперь мы просто называем его
“хорошими практиками”.&lt;/p&gt;
&lt;p&gt;К таким хорошим практикам можно отнести, например, следующие.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Форматировать код по &lt;a href="https://www.python.org/dev/peps/pep-0008/"&gt;PEP8&lt;/a&gt;
— если этого не делать, то другим людям будет намного сложнее понимать
ваш код; в плохо оформленном коде сложнее увидеть суть,
потому что мозг постоянно отвлекается на не несущие смысловой нагрузки
особенности оформления.&lt;/li&gt;
&lt;li&gt;Не допускать объявленных, но неиспользуемых переменных/функций/импортов
— опять же, это усложняет восприятие кода; читателю потребуется потратить
время на то, чтобы осознать, что вот на эту сущность обращать внимания не
нужно.&lt;/li&gt;
&lt;li&gt;Писать короткие функции — слишком сложные функции с большим
количеством ветвлений и циклов тяжело понимать.&lt;/li&gt;
&lt;li&gt;Не использовать изменяемый объект в качестве значения аргумента
функции по умолчанию — иначе в результате можно получить
&lt;a href="https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments"&gt;очень неожиданные эффекты&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Соблюдать (и даже просто помнить) все хорошие практики — не самая простая
задача. Зачастую люди плохо справляются с тем, чтобы отсчитывать пробелы
и контролировать переменные, и вообще склонны допускать ошибки по
невнимательности. Таковы люди, ничего не поделаешь. Машины, наоборот,
прекрасно справляются с такими хорошо определёнными задачами, поэтому
появились инструменты, которые контролируют следование хорошим практикам.&lt;/p&gt;
&lt;p&gt;В компилируемых языках ещё на этапе компиляции программист может получить
&lt;s&gt;по щщам&lt;/s&gt; первый полезный фидбэк о написанном коде.
Компилятор проверит, что код валиден и может быть скомпилирован, а также может
выдать предупреждения и рекомендации, как сделать код лучше или читаемее.
Т.к. Python является интерпретируемым языком, где этап компиляции как таковой
отсутствует, линтеры особенно полезны. На самом деле, это очень важно и
круто — узнать, что твой код как минимум является валидным Python-кодом,
даже не запуская его.&lt;/p&gt;
&lt;p&gt;В этом посте я рассмотрю два самых популярных линтера для Python:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://flake8.pycqa.org/en/latest/"&gt;flake8&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pylint.org/"&gt;pylint&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Термин “lint” впервые начал использоваться в таком значении в 1979 году.
Так называлась программа для статического анализа кода на C,
которая предупреждала об использовании непортабельных на другие архитектуры
языковых конструкций. С тех пор “линтерами” называют любые статические
анализаторы кода, которые помогают находить распространённые ошибки, делать
его однообразным и более читаемым. А названо оно "lint" в честь вот такой
штуки:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="lint roller" src="https://semakin.dev/static/lint_roller.jpg"&gt;&lt;/p&gt;
&lt;h2&gt;flake8&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;flake8&lt;/code&gt; — это утилита-комбайн, которая органично объединяет в себе несколько
других анализаторов кода (&lt;code&gt;pycodestyle&lt;/code&gt;, &lt;code&gt;pyflakes&lt;/code&gt; и &lt;code&gt;mccabe&lt;/code&gt;), а также
имеет огромную экосистему плагинов, которые могут добавить к стандартной
поставке ещё кучу различных проверок. На данный момент, это самый
популярный линтер для Python-кода. Кроме того, он предельно прост в
настройке и использовании.&lt;/p&gt;
&lt;h3&gt;Установка&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;flake8&lt;/code&gt; устанавливается, как и любой другой Python-пакет,
через &lt;code&gt;pip&lt;/code&gt;. &lt;strong&gt;Внутри виртуального окружения&lt;/strong&gt; проекта выполните:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pip install flake8
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Если вы пользуетесь &lt;code&gt;pipenv&lt;/code&gt;, то &lt;code&gt;flake8&lt;/code&gt; нужно устанавливать
как dev-зависимость (ведь для работы программы линтер не нужен,
он нужен только для разработчика):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv install --dev flake8
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Аналогично с &lt;code&gt;poetry&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ poetry add --dev flake8
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Проверим установку:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ flake8 --version
&lt;span class="m"&gt;3&lt;/span&gt;.8.1 &lt;span class="o"&gt;(&lt;/span&gt;mccabe: &lt;span class="m"&gt;0&lt;/span&gt;.6.1, pycodestyle: &lt;span class="m"&gt;2&lt;/span&gt;.6.0, pyflakes: &lt;span class="m"&gt;2&lt;/span&gt;.2.0&lt;span class="o"&gt;)&lt;/span&gt; CPython &lt;span class="m"&gt;3&lt;/span&gt;.8.2 on Linux
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Использование&lt;/h3&gt;
&lt;p&gt;Для работы &lt;code&gt;flake8&lt;/code&gt; нужно просто указать файл или директорию, которые
нужно проверять, например:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# проверить один файл&lt;/span&gt;
$ flake8 file.py

&lt;span class="c1"&gt;# проверить директорию рекурсивно &lt;/span&gt;
$ flake8 src/

&lt;span class="c1"&gt;# проверить текущую директорию рекурсивно&lt;/span&gt;
$ flake8 .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Давайте для демонстрации попытаемся написать программу с как можно большим
количеством “плохих практик”:&lt;/p&gt;
&lt;script src="https://gist.github.com/and-semakin/6080c25e106dedcecb0326aa4514b738.js?file=bad_code.py"&gt;&lt;/script&gt;

&lt;p&gt;Возможно, вам не видно всего, но в этом коде точно есть следующие "запахи кода":&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;import *&lt;/code&gt; — импортирование всех имен из модуля, хотя используется
из них только одно;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;import itertools&lt;/code&gt; — ненужный импорт;&lt;/li&gt;
&lt;li&gt;во множестве мест стоят лишние или отсутствующие пробелы;&lt;/li&gt;
&lt;li&gt;название функции написано в стиле PascalCase;&lt;/li&gt;
&lt;li&gt;в некоторых местах используются табы для отступов;&lt;/li&gt;
&lt;li&gt;используется список (изменяемый объект) в качестве значения аргумента
функции по умолчанию;&lt;/li&gt;
&lt;li&gt;используется слишком “широкое” выражение &lt;code&gt;except:&lt;/code&gt; без указания
конкретного исключения.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Давайте посмотрим, что &lt;code&gt;flake8&lt;/code&gt; скажет по поводу этого файла:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ flake8 bad_code.py
bad_code.py:1:1: F403 &lt;span class="s1"&gt;&amp;#39;from math import *&amp;#39;&lt;/span&gt; used&lt;span class="p"&gt;;&lt;/span&gt; unable to detect undefined names
bad_code.py:2:1: F401 &lt;span class="s1"&gt;&amp;#39;itertools&amp;#39;&lt;/span&gt; imported but unused
bad_code.py:4:1: E302 expected &lt;span class="m"&gt;2&lt;/span&gt; blank lines, found &lt;span class="m"&gt;1&lt;/span&gt;
bad_code.py:4:4: E271 multiple spaces after keyword
bad_code.py:4:25: E211 whitespace before &lt;span class="s1"&gt;&amp;#39;(&amp;#39;&lt;/span&gt;
bad_code.py:4:33: E202 whitespace before &lt;span class="s1"&gt;&amp;#39;)&amp;#39;&lt;/span&gt;
bad_code.py:5:1: W191 indentation contains tabs
bad_code.py:5:8: E271 multiple spaces after keyword
bad_code.py:5:10: F405 &lt;span class="s1"&gt;&amp;#39;sqrt&amp;#39;&lt;/span&gt; may be undefined, or defined from star imports: math
bad_code.py:5:21: E202 whitespace before &lt;span class="s1"&gt;&amp;#39;)&amp;#39;&lt;/span&gt;
bad_code.py:7:1: E302 expected &lt;span class="m"&gt;2&lt;/span&gt; blank lines, found &lt;span class="m"&gt;1&lt;/span&gt;
bad_code.py:7:23: E741 ambiguous variable name &lt;span class="s1"&gt;&amp;#39;l&amp;#39;&lt;/span&gt;
bad_code.py:8:1: E101 indentation contains mixed spaces and tabs
bad_code.py:9:1: E101 indentation contains mixed spaces and tabs
bad_code.py:11:1: E305 expected &lt;span class="m"&gt;2&lt;/span&gt; blank lines after class or &lt;span class="k"&gt;function&lt;/span&gt; definition, found &lt;span class="m"&gt;1&lt;/span&gt;
bad_code.py:12:1: E101 indentation contains mixed spaces and tabs
bad_code.py:13:1: E101 indentation contains mixed spaces and tabs
bad_code.py:13:20: E225 missing whitespace around operator
bad_code.py:14:1: E101 indentation contains mixed spaces and tabs
bad_code.py:14:67: W291 trailing whitespace
bad_code.py:15:1: E101 indentation contains mixed spaces and tabs
bad_code.py:15:14: W291 trailing whitespace
bad_code.py:16:1: E101 indentation contains mixed spaces and tabs
bad_code.py:16:5: E722 &lt;span class="k"&gt;do&lt;/span&gt; not use bare &lt;span class="s1"&gt;&amp;#39;except&amp;#39;&lt;/span&gt;
bad_code.py:17:1: E101 indentation contains mixed spaces and tabs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Как видите, &lt;code&gt;flake8&lt;/code&gt; нашёл кучу ошибок. Для каждой ошибки указана строка
и номер символа в строке (не всегда точный), где произошла ошибка.
Также у каждой категории ошибок есть свой код: &lt;code&gt;E101&lt;/code&gt;, &lt;code&gt;W291&lt;/code&gt; и т.д.
Эти коды ошибок могут использоваться для включения/отключения правил.
Тем не менее, не все ошибки были найдены. Давайте установим пару плагинов,
чтобы добавить ещё правил!&lt;/p&gt;
&lt;h3&gt;Плагины&lt;/h3&gt;
&lt;p&gt;Как я уже говорил, для &lt;code&gt;flake8&lt;/code&gt; написано множество плагинов.
Обычно плагины легко гуглятся или находятся в &lt;a href="https://github.com/DmytroLitvinov/awesome-flake8-extensions"&gt;списках плагинов&lt;/a&gt;.
Есть плагины для всех популярных фреймворков и библиотек — пользуйтесь ими!
Давайте для нашего простого примера установим
&lt;a href="https://github.com/PyCQA/flake8-bugbear"&gt;&lt;code&gt;flake8-bugbear&lt;/code&gt;&lt;/a&gt;
(находит распространённые логические ошибки) и 
&lt;a href="https://github.com/PyCQA/pep8-naming"&gt;&lt;code&gt;pep8-naming&lt;/code&gt;&lt;/a&gt;
(проверяет имена на соответствие PEP8).&lt;/p&gt;
&lt;p&gt;Плагины устанавливаются так же, как и сам &lt;code&gt;flake8&lt;/code&gt; (для краткости я
не буду писать примеры для &lt;code&gt;pipenv&lt;/code&gt; и &lt;code&gt;poetry&lt;/code&gt; — сами сможете обобщить):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pip install flake8-bugbear pep8-naming
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Давайте убедимся, что плагины действительно установились
и &lt;code&gt;flake8&lt;/code&gt; может их найти:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ flake8 --version
&lt;span class="m"&gt;3&lt;/span&gt;.8.1 &lt;span class="o"&gt;(&lt;/span&gt;flake8-bugbear: &lt;span class="m"&gt;20&lt;/span&gt;.1.4, mccabe: &lt;span class="m"&gt;0&lt;/span&gt;.6.1, naming: &lt;span class="m"&gt;0&lt;/span&gt;.10.0, pycodestyle: &lt;span class="m"&gt;2&lt;/span&gt;.6.0, pyflakes: &lt;span class="m"&gt;2&lt;/span&gt;.2.0&lt;span class="o"&gt;)&lt;/span&gt; CPython &lt;span class="m"&gt;3&lt;/span&gt;.8.2 on Linux
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Если вы видите в списке в скобках названия ваших плагинов, то всё хорошо.&lt;/p&gt;
&lt;p&gt;Теперь снова проверим наш файл:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ flake8 bad_code.py
bad_code.py:1:1: F403 &lt;span class="s1"&gt;&amp;#39;from math import *&amp;#39;&lt;/span&gt; used&lt;span class="p"&gt;;&lt;/span&gt; unable to detect undefined names
bad_code.py:2:1: F401 &lt;span class="s1"&gt;&amp;#39;itertools&amp;#39;&lt;/span&gt; imported but unused
bad_code.py:4:1: E302 expected &lt;span class="m"&gt;2&lt;/span&gt; blank lines, found &lt;span class="m"&gt;1&lt;/span&gt;
bad_code.py:4:4: E271 multiple spaces after keyword
bad_code.py:4:6: N802 &lt;span class="k"&gt;function&lt;/span&gt; name &lt;span class="s1"&gt;&amp;#39;CalculateSquareRoot&amp;#39;&lt;/span&gt; should be lowercase
bad_code.py:4:25: E211 whitespace before &lt;span class="s1"&gt;&amp;#39;(&amp;#39;&lt;/span&gt;
bad_code.py:4:28: N803 argument name &lt;span class="s1"&gt;&amp;#39;Number&amp;#39;&lt;/span&gt; should be lowercase
bad_code.py:4:33: E202 whitespace before &lt;span class="s1"&gt;&amp;#39;)&amp;#39;&lt;/span&gt;
bad_code.py:5:1: W191 indentation contains tabs
bad_code.py:5:8: E271 multiple spaces after keyword
bad_code.py:5:10: F405 &lt;span class="s1"&gt;&amp;#39;sqrt&amp;#39;&lt;/span&gt; may be undefined, or defined from star imports: math
bad_code.py:5:21: E202 whitespace before &lt;span class="s1"&gt;&amp;#39;)&amp;#39;&lt;/span&gt;
bad_code.py:7:1: E302 expected &lt;span class="m"&gt;2&lt;/span&gt; blank lines, found &lt;span class="m"&gt;1&lt;/span&gt;
bad_code.py:7:23: E741 ambiguous variable name &lt;span class="s1"&gt;&amp;#39;l&amp;#39;&lt;/span&gt;
bad_code.py:7:25: B006 Do not use mutable data structures &lt;span class="k"&gt;for&lt;/span&gt; argument defaults.  They are created during &lt;span class="k"&gt;function&lt;/span&gt; definition time. All calls to the &lt;span class="k"&gt;function&lt;/span&gt; reuse this one instance of that data structure, persisting changes between them.
bad_code.py:8:1: E101 indentation contains mixed spaces and tabs
bad_code.py:9:1: E101 indentation contains mixed spaces and tabs
bad_code.py:11:1: E305 expected &lt;span class="m"&gt;2&lt;/span&gt; blank lines after class or &lt;span class="k"&gt;function&lt;/span&gt; definition, found &lt;span class="m"&gt;1&lt;/span&gt;
bad_code.py:12:1: E101 indentation contains mixed spaces and tabs
bad_code.py:13:1: E101 indentation contains mixed spaces and tabs
bad_code.py:13:20: E225 missing whitespace around operator
bad_code.py:14:1: E101 indentation contains mixed spaces and tabs
bad_code.py:14:67: W291 trailing whitespace
bad_code.py:15:1: E101 indentation contains mixed spaces and tabs
bad_code.py:15:14: W291 trailing whitespace
bad_code.py:16:1: E101 indentation contains mixed spaces and tabs
bad_code.py:16:5: E722 &lt;span class="k"&gt;do&lt;/span&gt; not use bare &lt;span class="s1"&gt;&amp;#39;except&amp;#39;&lt;/span&gt;
bad_code.py:16:5: B001 Do not use bare &lt;span class="sb"&gt;`&lt;/span&gt;except:&lt;span class="sb"&gt;`&lt;/span&gt;, it also catches unexpected events like memory errors, interrupts, system exit, and so on.  Prefer &lt;span class="sb"&gt;`&lt;/span&gt;except Exception:&lt;span class="sb"&gt;`&lt;/span&gt;.  If you&lt;span class="s1"&gt;&amp;#39;re sure what you&amp;#39;&lt;/span&gt;re doing, be explicit and write &lt;span class="sb"&gt;`&lt;/span&gt;except BaseException:&lt;span class="sb"&gt;`&lt;/span&gt;.
bad_code.py:17:1: E101 indentation contains mixed spaces and tabs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;В выводе появились новые категории ошибок (&lt;code&gt;N802&lt;/code&gt;, &lt;code&gt;B006&lt;/code&gt;)
— они как раз добавлены плагинами. На этот раз, как мне кажется,
найдены все ошибки. К сожалению, &lt;code&gt;flake8&lt;/code&gt; не умеет сам чинить
найденные ошибки, поэтому давайте сделаем это вручную:&lt;/p&gt;
&lt;script src="https://gist.github.com/and-semakin/6080c25e106dedcecb0326aa4514b738.js?file=not_so_bad_code.py"&gt;&lt;/script&gt;

&lt;p&gt;Обратите внимание на строки 8 и 10, там содержится комментарии &lt;code&gt;# noqa&lt;/code&gt;.
При помощи этих комментариев можно заставить &lt;code&gt;flake8&lt;/code&gt; игнорировать ошибки.
Это бывает полезно, когда по какой-то причине код должен остаться именно
таким, например:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;он автоматически сгенерирован и исправление в нём ошибок не имеет смысла;&lt;/li&gt;
&lt;li&gt;исправление этой ошибки породит куда более уродливый код,
чем комментарий &lt;code&gt;# noqa&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;у вас просто сейчас нет времени, чтобы исправлять эту ошибку
(плохая отмазка, серьёзно).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Если не указать код ошибки, то будут проигнорированы все ошибки в строке
— я не рекомендую так делать, потому что так можно пропустить
и на самом деле плохие ошибки. Если указать номер правила, то
&lt;code&gt;flake8&lt;/code&gt; будет игнорировать только указанную категорию,
а о других ошибках в этой же строке доложит.
Вообще, комментариями &lt;code&gt;# noqa&lt;/code&gt; нужно пользоваться с большой осторожностью.
Считайте, что каждый раз, когда вы это делаете, вы берёте на
себя ответственность за эту строку кода. Если программа сломается
в этом месте, то пеняйте на себя — &lt;s&gt;минздрав&lt;/s&gt; линтер вас предупреждал.&lt;/p&gt;
&lt;h3&gt;Конфигурация&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;flake8&lt;/code&gt; для работы не требует никакой конфигурации.
Он имеет достаточно (но не слишком) строгие настройки по умолчанию,
которые подойдут большинству пользователей, но иногда бывает нужно
отключить (или наоборот включить) определённые правила на уровне всего проекта.
Сделать это можно через файлы &lt;code&gt;.flake8&lt;/code&gt; или &lt;code&gt;setup.cfg&lt;/code&gt; в корне проекта.
Если у вас в проекте уже есть файл &lt;code&gt;setup.cfg&lt;/code&gt;, то можно добавить конфигурацию
&lt;code&gt;flake8&lt;/code&gt; в него. Если вы предпочитаете для каждой утилиты держать
отдельный файл конфигурации, то используйте &lt;code&gt;.flake8&lt;/code&gt;. В любом случае,
формат для обоих этих файлов совпадает:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[flake8]&lt;/span&gt;
&lt;span class="na"&gt;ignore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;D203,E741&lt;/span&gt;
&lt;span class="na"&gt;exclude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&lt;/span&gt;
&lt;span class="s"&gt;    # No need to traverse our git directory&lt;/span&gt;
&lt;span class="s"&gt;    .git,&lt;/span&gt;
&lt;span class="s"&gt;    # There&amp;#39;s no value in checking cache directories&lt;/span&gt;
&lt;span class="s"&gt;    __pycache__,&lt;/span&gt;
&lt;span class="s"&gt;    # The conf file is mostly autogenerated, ignore it&lt;/span&gt;
&lt;span class="s"&gt;    docs/source/conf.py,&lt;/span&gt;
&lt;span class="s"&gt;    # The old directory contains Flake8 2.0&lt;/span&gt;
&lt;span class="s"&gt;    old,&lt;/span&gt;
&lt;span class="s"&gt;    # This contains our built documentation&lt;/span&gt;
&lt;span class="s"&gt;    build,&lt;/span&gt;
&lt;span class="s"&gt;    # This contains builds of flake8 that we don&amp;#39;t want to check&lt;/span&gt;
&lt;span class="s"&gt;    dist&lt;/span&gt;
&lt;span class="na"&gt;max-complexity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;В конфигурации можно перечислить игнорируемые правила и директории,
в которые &lt;code&gt;flake8&lt;/code&gt; заглядывать не будет, а также максимальную
&lt;a href="https://ru.wikipedia.org/wiki/%D0%A6%D0%B8%D0%BA%D0%BB%D0%BE%D0%BC%D0%B0%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C"&gt;цикломатическую сложность&lt;/a&gt;
для функций. Все эти настройки будут автоматически применяться
к запускам &lt;code&gt;flake8&lt;/code&gt; во всех поддиректориях проекта.&lt;/p&gt;
&lt;p&gt;Если же вам не хватает какого-нибудь правила, и его нет даже в уже
готовых плагинах, то &lt;a href="https://flake8.pycqa.org/en/latest/plugin-development/"&gt;написание собственного плагина&lt;/a&gt;
— не такая уж и сложная задача.
&lt;a href="https://github.com/and-semakin/flake8-pytestrail/"&gt;Я попробовал&lt;/a&gt;,
у меня на это ушло 2-3 часа.&lt;/p&gt;
&lt;h2&gt;pylint&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;pylint&lt;/code&gt; — это ещё один популярный линтер для Python.
Этот линтер значительно умнее и продвинутее &lt;code&gt;flake8&lt;/code&gt;.
В &lt;code&gt;pylint&lt;/code&gt; из коробки заложено очень много правил и рекомендаций,
и по умолчанию они все включены, так что он достаточно строгий и придирчивый.
Чтобы интегрировать его в существующий большой проект придётся потратить
некоторое время, чтобы выбрать те правила, которые для вас важны.
Так же как и &lt;code&gt;flake8&lt;/code&gt;, &lt;code&gt;pylint&lt;/code&gt; поддерживает плагины для расширения
базовой функциональности, но насколько я вижу, экосистема плагинов у &lt;code&gt;pylint&lt;/code&gt;
значительно беднее.&lt;/p&gt;
&lt;p&gt;Также при каждом запуске &lt;code&gt;pylint&lt;/code&gt; выводит оценку качества кода
по десятибалльной шкале, а также следит, как эта оценка меняется
с течением времени. Достичь десятки очень сложно, но это благородная цель,
к которой нужно стремиться.&lt;/p&gt;
&lt;h3&gt;Установка&lt;/h3&gt;
&lt;p&gt;Установка &lt;code&gt;pylint&lt;/code&gt; принципиально ничем не отличается от установки &lt;code&gt;flake8&lt;/code&gt;.
Выполнить внутри виртуального окружения проекта:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pip install pylint
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Для &lt;code&gt;pipenv&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv install --dev pylint
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Для &lt;code&gt;poetry&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ poetry add --dev pylint
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Использование&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;pylint&lt;/code&gt; можно натравить на определённый файл:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pylint file.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;С директориями у &lt;code&gt;pylint&lt;/code&gt; дела обстоят чуть сложнее. Все директории он
обрабатывает как питоновские модули, поэтому если в директории нет хотя бы
пустого файла &lt;code&gt;__init__.py&lt;/code&gt;, то работать с ней &lt;code&gt;pylint&lt;/code&gt; не сможет. Имейте
это ввиду.&lt;/p&gt;
&lt;p&gt;Давайте попросим &lt;code&gt;pylint&lt;/code&gt; прокомментировать файл с плохими практиками
из предыдущего примера:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pylint bad_code.py
************* Module bad_code
bad_code.py:4:25: C0326: No space allowed before bracket
def  CalculateSquareRoot &lt;span class="o"&gt;(&lt;/span&gt;Number &lt;span class="o"&gt;)&lt;/span&gt;:
                         ^ &lt;span class="o"&gt;(&lt;/span&gt;bad-whitespace&lt;span class="o"&gt;)&lt;/span&gt;
bad_code.py:4:33: C0326: No space allowed before bracket
def  CalculateSquareRoot &lt;span class="o"&gt;(&lt;/span&gt;Number &lt;span class="o"&gt;)&lt;/span&gt;:
                                 ^ &lt;span class="o"&gt;(&lt;/span&gt;bad-whitespace&lt;span class="o"&gt;)&lt;/span&gt;
bad_code.py:5:0: W0312: Found indentation with tabs instead of spaces &lt;span class="o"&gt;(&lt;/span&gt;mixed-indentation&lt;span class="o"&gt;)&lt;/span&gt;
bad_code.py:5:21: C0326: No space allowed before bracket
    &lt;span class="k"&gt;return&lt;/span&gt;  sqrt&lt;span class="o"&gt;(&lt;/span&gt;Number &lt;span class="o"&gt;)&lt;/span&gt;
                     ^ &lt;span class="o"&gt;(&lt;/span&gt;bad-whitespace&lt;span class="o"&gt;)&lt;/span&gt;
bad_code.py:13:19: C0326: Exactly one space required around assignment
        &lt;span class="nv"&gt;your_number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;float&lt;span class="o"&gt;(&lt;/span&gt;input&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Enter your number: &amp;#39;&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                   ^ &lt;span class="o"&gt;(&lt;/span&gt;bad-whitespace&lt;span class="o"&gt;)&lt;/span&gt;
bad_code.py:14:66: C0303: Trailing whitespace &lt;span class="o"&gt;(&lt;/span&gt;trailing-whitespace&lt;span class="o"&gt;)&lt;/span&gt;
bad_code.py:15:13: C0303: Trailing whitespace &lt;span class="o"&gt;(&lt;/span&gt;trailing-whitespace&lt;span class="o"&gt;)&lt;/span&gt;
bad_code.py:1:0: W0622: Redefining built-in &lt;span class="s1"&gt;&amp;#39;pow&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;redefined-builtin&lt;span class="o"&gt;)&lt;/span&gt;
bad_code.py:1:0: C0114: Missing module docstring &lt;span class="o"&gt;(&lt;/span&gt;missing-module-docstring&lt;span class="o"&gt;)&lt;/span&gt;
bad_code.py:1:0: W0401: Wildcard import math &lt;span class="o"&gt;(&lt;/span&gt;wildcard-import&lt;span class="o"&gt;)&lt;/span&gt;
bad_code.py:4:0: C0103: Function name &lt;span class="s2"&gt;&amp;quot;CalculateSquareRoot&amp;quot;&lt;/span&gt; doesn&lt;span class="s1"&gt;&amp;#39;t conform to snake_case naming style (invalid-name)&lt;/span&gt;
&lt;span class="s1"&gt;bad_code.py:4:0: C0103: Argument name &amp;quot;Number&amp;quot; doesn&amp;#39;&lt;/span&gt;t conform to snake_case naming style &lt;span class="o"&gt;(&lt;/span&gt;invalid-name&lt;span class="o"&gt;)&lt;/span&gt;
bad_code.py:4:0: C0116: Missing &lt;span class="k"&gt;function&lt;/span&gt; or method docstring &lt;span class="o"&gt;(&lt;/span&gt;missing-function-docstring&lt;span class="o"&gt;)&lt;/span&gt;
bad_code.py:7:0: W0102: Dangerous default value &lt;span class="o"&gt;[]&lt;/span&gt; as argument &lt;span class="o"&gt;(&lt;/span&gt;dangerous-default-value&lt;span class="o"&gt;)&lt;/span&gt;
bad_code.py:7:0: C0103: Argument name &lt;span class="s2"&gt;&amp;quot;l&amp;quot;&lt;/span&gt; doesn&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t conform to snake_case naming style &lt;span class="o"&gt;(&lt;/span&gt;invalid-name&lt;span class="o"&gt;)&lt;/span&gt;
bad_code.py:7:0: C0116: Missing &lt;span class="k"&gt;function&lt;/span&gt; or method docstring &lt;span class="o"&gt;(&lt;/span&gt;missing-function-docstring&lt;span class="o"&gt;)&lt;/span&gt;
bad_code.py:16:4: W0702: No exception type&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; specified &lt;span class="o"&gt;(&lt;/span&gt;bare-except&lt;span class="o"&gt;)&lt;/span&gt;
bad_code.py:1:0: W0614: Unused import acos from wildcard import &lt;span class="o"&gt;(&lt;/span&gt;unused-wildcard-import&lt;span class="o"&gt;)&lt;/span&gt;
bad_code.py:1:0: W0614: Unused import acosh from wildcard import &lt;span class="o"&gt;(&lt;/span&gt;unused-wildcard-import&lt;span class="o"&gt;)&lt;/span&gt;
bad_code.py:1:0: W0614: Unused import asin from wildcard import &lt;span class="o"&gt;(&lt;/span&gt;unused-wildcard-import&lt;span class="o"&gt;)&lt;/span&gt;
bad_code.py:1:0: W0614: Unused import asinh from wildcard import &lt;span class="o"&gt;(&lt;/span&gt;unused-wildcard-import&lt;span class="o"&gt;)&lt;/span&gt;
...
bad_code.py:2:0: W0611: Unused import itertools &lt;span class="o"&gt;(&lt;/span&gt;unused-import&lt;span class="o"&gt;)&lt;/span&gt;
-------------------------------------
Your code has been rated at -41.43/10
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Я немного сократил вывод. Как видите, даже без плагинов &lt;code&gt;pylint&lt;/code&gt; нашёл
все ожидаемые ошибки, и даже больше — например, он даже предлагает написать
документацию.&lt;/p&gt;
&lt;p&gt;По каждой ошибке можно запросить более подробную справку, используя
название правила из конца строки с ошибкой или код:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pylint --help-msg&lt;span class="o"&gt;=&lt;/span&gt;missing-docstring
$ pylint --help-msg&lt;span class="o"&gt;=&lt;/span&gt;R0902
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Вот какие ошибки &lt;code&gt;pylint&lt;/code&gt; находит для файла, который с точки зрения &lt;code&gt;flake8&lt;/code&gt;
не содержит никаких ошибок:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pylint not_so_bad_code.py 
************* Module not_so_bad_code
not_so_bad_code.py:1:0: C0114: Missing module docstring &lt;span class="o"&gt;(&lt;/span&gt;missing-module-docstring&lt;span class="o"&gt;)&lt;/span&gt;
not_so_bad_code.py:4:0: C0116: Missing &lt;span class="k"&gt;function&lt;/span&gt; or method docstring &lt;span class="o"&gt;(&lt;/span&gt;missing-function-docstring&lt;span class="o"&gt;)&lt;/span&gt;
not_so_bad_code.py:8:0: C0103: Argument name &lt;span class="s2"&gt;&amp;quot;l&amp;quot;&lt;/span&gt; doesn&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t conform to snake_case naming style &lt;span class="o"&gt;(&lt;/span&gt;invalid-name&lt;span class="o"&gt;)&lt;/span&gt;
not_so_bad_code.py:8:0: C0116: Missing &lt;span class="k"&gt;function&lt;/span&gt; or method docstring &lt;span class="o"&gt;(&lt;/span&gt;missing-function-docstring&lt;span class="o"&gt;)&lt;/span&gt;
not_so_bad_code.py:20:11: W0703: Catching too general exception Exception &lt;span class="o"&gt;(&lt;/span&gt;broad-except&lt;span class="o"&gt;)&lt;/span&gt;
-----------------------------------
Your code has been rated at &lt;span class="m"&gt;6&lt;/span&gt;.67/10
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;А вот так в &lt;code&gt;pylint&lt;/code&gt; можно игнорировать отдельную ошибку на строке прямо в файлах
с кодом:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;append_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# pylint: disable=C0103&lt;/span&gt;
   &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ещё &lt;code&gt;pylint&lt;/code&gt; умеет игнорировать ошибки в блоках кода:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Disable all the no-member violations in this function&lt;/span&gt;
    &lt;span class="c1"&gt;# pylint: disable=no-member&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;И для файлов целиком. Вот так можно отключить все ошибки из категорий
Warning, Convention и Refactor:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# pylint: disable=W,C,R&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;А можно не проверять файл вообще:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# pylint: skip-file&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Подробнее о правилах управления сообщениями
&lt;a href="http://pylint.pycqa.org/en/latest/user_guide/message-control.html"&gt;смотрите в документации&lt;/a&gt;.
Для более сложной настройки правил, придётся по-настоящему сконфигурировать
&lt;code&gt;pylint&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Конфигурация&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;pylint&lt;/code&gt; настраивается через файл &lt;code&gt;.pylintrc&lt;/code&gt; в корне проекта. Чтобы создать
дефолтный файл конфигурации, нужно выполнить следующую команду:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pylint --generate-rcfile &amp;gt; .pylintrc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Созданный файл содержит все поддерживаемые &lt;code&gt;pylint&lt;/code&gt; опции с довольно
подробными комментариями, так что углубляться я не буду.&lt;/p&gt;
&lt;h3&gt;Плагины&lt;/h3&gt;
&lt;p&gt;Давайте установим какой-нибудь популярный плагин, например,
&lt;a href="https://pypi.org/project/pylint-django/"&gt;&lt;code&gt;pylint-django&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pip install pylint-django
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Теперь запускать &lt;code&gt;pylint&lt;/code&gt; нужно вот так:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pylint --load-plugins pylint_django &lt;span class="o"&gt;[&lt;/span&gt;..other options..&lt;span class="o"&gt;]&lt;/span&gt; &amp;lt;path_to_your_sources&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;либо в &lt;code&gt;.pylintrc&lt;/code&gt; нужно исправить директиву &lt;code&gt;load-plugins&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;load-plugins=pylint_django
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Интеграция линтера в проект&lt;/h2&gt;
&lt;p&gt;Интегрировать линтер в проект можно на трёх уровнях.
Я рекомендую по возможности использовать все три, но обязательным
является как минимум один (лучше всего, чтобы это была CI система).&lt;/p&gt;
&lt;h3&gt;Редактор кода или IDE&lt;/h3&gt;
&lt;p&gt;Популярные IDE для Python умеют легко интегрировать с линтерами и
подсвечивать ошибки линтера прямо в редактируемом файле.
Это удобно, потому что позволяет не выходя из редактора получить
полезную обратную связь.&lt;/p&gt;
&lt;p&gt;PyCharm автоматически находить установленные &lt;code&gt;flake8&lt;/code&gt; и &lt;code&gt;pylint&lt;/code&gt; внутри
&lt;a href="https://www.jetbrains.com/help/pycharm/configuring-python-interpreter.html"&gt;интерпретатора проекта&lt;/a&gt;
и подключается к ним.&lt;/p&gt;
&lt;p&gt;VS Code требует небольшой настройки, которая
&lt;a href="https://code.visualstudio.com/docs/python/linting"&gt;описана здесь&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Git-хуки&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Также читайте &lt;a href="https://semakin.dev/2020/05/pre-commit/"&gt;пост про Git-хуки и &lt;code&gt;pre-commit&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;В git есть &lt;a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks"&gt;возможность запрограммировать&lt;/a&gt;
определенные скрипты (хуки) в ответ на действия пользователя.
Например, можно запускать
какие-нибудь проверки перед коммитом, заново скачивать зависимости проекта
при переключении веток, высылать сообщение в рабочий чат
после пуша в удалённый репозиторий и вообще что угодно.&lt;/p&gt;
&lt;p&gt;&lt;img alt="я запушель" src="https://semakin.dev/static/i_have_pushed_meme.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Нас интересует возможность запускать линтер перед коммитом так,
чтобы если линтер найдёт какие-нибудь проблемы, операция коммита прерывалась.
Git-хуки можно настроить, написав несложный shell-скрипт,
но я рекомендую использовать для этого специальные утилиты,
такие как &lt;a href="https://pre-commit.com/"&gt;pre-commit&lt;/a&gt;.
&lt;a href="https://ljvmiranda921.github.io/notebook/2018/06/21/precommits-using-black-and-flake8/"&gt;Вот здесь&lt;/a&gt;
можно найти описание процесса настройки запуска &lt;code&gt;flake8&lt;/code&gt; через &lt;code&gt;pre-commit&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Обратите внимание, что Git-хуки нужно будет настроить на машине каждого
разработчика в проекте.&lt;/p&gt;
&lt;h3&gt;Continuous Integration (CI)&lt;/h3&gt;
&lt;p&gt;Последний эшелоном защиты от попадания “сломанного” кода в основную ветку
репозитория является система непрерывной интеграции (CI) — такая, как:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://help.github.com/en/actions"&gt;GitHub Actions&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.gitlab.com/ce/ci/"&gt;GitLab CI&lt;/a&gt;
(а ещё читайте пост в блоге моего хорошего товарища про
&lt;a href="https://alse-code.ru/gitlab-ci-intro/"&gt;основы GitLab CI&lt;/a&gt;);&lt;/li&gt;
&lt;li&gt;&lt;a href="https://travis-ci.org/"&gt;Travis CI&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;или &lt;a href="https://github.com/ligurio/awesome-ci"&gt;другая&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;На каждый пуш в репозиторий система непрерывной интеграции должна
запускать проверки (включая все линтеры и тесты), и если что-то идёт
не так, рядом с коммитом должен появиться красный крестик.
Ветку с таким коммитом на конце нельзя будет слить с основной
веткой проекта через пулл-реквест на GitHub (или мёрдж-реквест на GitLab).
&lt;a href="https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#starting-with-the-python-workflow-template"&gt;Пример того, как настроить GitHub Actions&lt;/a&gt;
для запуска &lt;code&gt;flake8&lt;/code&gt; и других питоновских проверок.&lt;/p&gt;
&lt;p&gt;CI — это единственный надёжный способ обеспечить качество кода.
Предыдущие способы нужны скорее для удобства разработчика, чтобы он
как можно скорее получал обратную связь, но разработчик вправе проигнорировать
или отключить эти предупреждения.&lt;/p&gt;
&lt;h1&gt;Заключение&lt;/h1&gt;
&lt;p&gt;В подзаголовке этой статьи я написал фразу, что линтер способен
сэкономить разработчику один день жизни в месяц. Фраза может показаться
кликбейтной, но, поверьте мне, это так это и работает.
Возможно, я даже преуменьшил.
Чем раньше найдена ошибка, тем быстрее идёт разработка.
Иногда линтер предотвращает баги, иногда спасает от мучительного
траблшутинга. Линтеры абсолютно точно значительно сокращают время,
потраченное коллегами на код-ревью, потому что все тривиальные
ошибки будут отловлены автоматикой.&lt;/p&gt;
&lt;p&gt;Не стоит недооценивать линтеры. Это те инструменты,
которые делают из “кодера” настоящего “software engineer”,
из мальчика — мужчину. Если вы до сих пор не пользуетесь каким-нибудь
линтером, то рекомендую всерьез задуматься над внедрением!&lt;/p&gt;
&lt;p&gt;Я предпочитаю использовать &lt;code&gt;flake8&lt;/code&gt;, потому что он простой
и понятный, как топор. С ним легко работать, его легко настроить
под свои нужды, а почти любые недостающие правила можно получить
через уже готовые плагины.&lt;/p&gt;
&lt;p&gt;У &lt;code&gt;pylint&lt;/code&gt; тоже есть свои последователи. Его ценят за подробный вывод
и большое количество правил в стандартной поставке.
Мне же &lt;code&gt;pylint&lt;/code&gt; всегда казался слишком сложным в эксплуатации.&lt;/p&gt;
&lt;p&gt;А кто-то вообще рекомендует устанавливать &lt;code&gt;flake8&lt;/code&gt; и &lt;code&gt;pylint&lt;/code&gt; параллельно.&lt;/p&gt;
&lt;p&gt;Если понравилась статья, то
&lt;a href="https://semakin.dev/pages/subscribe/"&gt;подпишитесь на уведомления&lt;/a&gt;
о новых постах в блоге, чтобы ничего не пропустить!&lt;/p&gt;
&lt;h1&gt;Дополнительное чтение&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://flake8.pycqa.org/en/latest/"&gt;документация &lt;code&gt;flake8&lt;/code&gt;&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/pycqa/flake8"&gt;исходный код &lt;code&gt;flake8&lt;/code&gt;&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/DmytroLitvinov/awesome-flake8-extensions"&gt;список плагинов &lt;code&gt;flake8&lt;/code&gt;&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lintlyci.github.io/Flake8Rules/"&gt;сайт, где можно посмотреть правила &lt;code&gt;flake8&lt;/code&gt;&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://pylint.pycqa.org/en/latest/"&gt;документация &lt;code&gt;pylint&lt;/code&gt;&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/PyCQA/pylint"&gt;исходный код &lt;code&gt;pylint&lt;/code&gt;&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/Python/comments/82hgzm/any_advantages_of_flake8_over_pylint/"&gt;обсуждение “flake8 vs pylint” на Reddit&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://realpython.com/python-code-quality/"&gt;пост на RealPython про качество кода&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://habr.com/ru/company/dataart/blog/318776/"&gt;статья на Хабре про линтеры&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Обложка: &lt;a href="https://flic.kr/p/DQTiBS"&gt;Sa Mu, Traffic Light&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="blog"></category><category term="python"></category><category term="flake8"></category><category term="pylint"></category></entry><entry><title>Управление версиями чего угодно при помощи asdf</title><link href="https://semakin.dev/2020/05/asdf/" rel="alternate"></link><published>2020-05-13T11:05:32+05:00</published><updated>2020-05-13T11:05:32+05:00</updated><author><name>Андрей Семакин</name></author><id>tag:semakin.dev,2020-05-13:/2020/05/asdf/</id><summary type="html">&lt;p&gt;Простое управление версиями кучи языков программирования и инструментов: Python, Node.js, Ruby, Rust и т.д.&lt;/p&gt;</summary><content type="html">&lt;h1&gt;asdf&lt;/h1&gt;
&lt;p&gt;Нет, это не просто кто-то уронил четыре пальца на клавиатуру!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://semakin.dev/2020/05/pyenv/"&gt;В предыдущей статье&lt;/a&gt; я писал про
&lt;a href="https://github.com/pyenv/pyenv"&gt;&lt;code&gt;pyenv&lt;/code&gt;&lt;/a&gt; —
отличный инструмент для установки различных версий интерпретатора.
Для питонистов &lt;code&gt;pyenv&lt;/code&gt; вполне достаточно, но люди,
пишущие на нескольких языках, могут обнаружить себя в ситуации,
когда им приходится держать установленными
&lt;a href="https://github.com/pyenv/pyenv"&gt;&lt;code&gt;pyenv&lt;/code&gt;&lt;/a&gt;,
&lt;a href="https://rvm.io/"&gt;&lt;code&gt;rvm&lt;/code&gt;&lt;/a&gt; / &lt;a href="https://github.com/rbenv/rbenv"&gt;&lt;code&gt;rbenv&lt;/code&gt;&lt;/a&gt;,
&lt;a href="https://github.com/nvm-sh/nvm"&gt;&lt;code&gt;nvm&lt;/code&gt;&lt;/a&gt;,
&lt;a href="https://rustup.rs/"&gt;&lt;code&gt;rustup&lt;/code&gt;&lt;/a&gt;,
&lt;a href="https://github.com/moovweb/gvm"&gt;&lt;code&gt;gvm&lt;/code&gt;&lt;/a&gt;
или ещё какие-нибудь менеджеры версий для разных языков программирования.
При этом, конечно же, у каждого инструмента свой синтаксис команд,
свои тонкости по работе с ним, свои зависимости. Если установить это всё
сразу в свой шелл, то боюсь представить, что будет с переменной &lt;code&gt;$PATH&lt;/code&gt;
(она будет очень толстой). Потенциально это может замедлить работу шелла вообще.
Но все эти проблемы остались в прошлом
— встречайте &lt;code&gt;asdf&lt;/code&gt;, универсальный менеджер версий!&lt;/p&gt;
&lt;p&gt;&lt;img alt="asdf meme" src="https://semakin.dev/static/asdf_meme.jpg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;asdf&lt;/code&gt;, так же как и &lt;code&gt;pyenv&lt;/code&gt;, написан на чистом &lt;code&gt;bash&lt;/code&gt;.
Это означает, что инструмент отлично работает под Linux и MacOS, но,
к сожалению, никак не работает на Windows (ну, разве что в
&lt;a href="https://ru.wikipedia.org/wiki/Windows_Subsystem_for_Linux"&gt;WSL&lt;/a&gt;).
&lt;code&gt;asdf&lt;/code&gt; сам по себе не требует никаких внешних зависимостей, кроме
некоторых базовых утилит, типа &lt;code&gt;git&lt;/code&gt; и &lt;code&gt;curl&lt;/code&gt;, которые и так наверняка
у вас есть. Эти утилиты нужны для установки плагинов.&lt;/p&gt;
&lt;p&gt;Вокруг &lt;code&gt;asdf&lt;/code&gt; выстроилась огромная экосистема плагинов.
Именно благодаря плагинам &lt;code&gt;asdf&lt;/code&gt; и именует себя универсальным менеджером версий.&lt;/p&gt;
&lt;p&gt;Преимущества:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;не нужно учить диалекты команд для каждой отдельной утилиты,
чтобы делать типовые действия;&lt;/li&gt;
&lt;li&gt;умеет управлять версиями всех мыслимых и немыслимых инструментов и
языков программирования через плагины;&lt;/li&gt;
&lt;li&gt;можно настроить версии локально для определённой директории и
&lt;code&gt;asdf&lt;/code&gt; автоматически переключит версии инструментов при входе в неё;&lt;/li&gt;
&lt;li&gt;классное название — удобно печатать.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Недостатки:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;у каждого плагина могут быть свои тонкости в плане настройки и свои
зависимости, так что придётся читать инструкции, но, как правило, это
единоразовые действия.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Установка&lt;/h2&gt;
&lt;p&gt;Установка во всех подробностях
&lt;a href="https://asdf-vm.com/#/core-manage-asdf-vm"&gt;описана на официальном сайте&lt;/a&gt;.
Глупо было бы пытаться описать установку лучше, чем на оф.сайте
— лучше и не напишешь. Документация у &lt;code&gt;asdf&lt;/code&gt; просто бомбическая.
Есть варианты установки на Linux или MacOS (через &lt;code&gt;brew&lt;/code&gt;),
описана настройка для всех популярных шеллов — &lt;code&gt;bash&lt;/code&gt;, &lt;code&gt;zsh&lt;/code&gt; и &lt;code&gt;fish&lt;/code&gt;.
Всё просто — выбираете вашу систему и шелл,
и получаете команды по установке и настройке.&lt;/p&gt;
&lt;p&gt;Обратите внимание, что для корректной работы &lt;code&gt;asdf&lt;/code&gt; требует
наличия &lt;code&gt;git&lt;/code&gt; и &lt;code&gt;curl&lt;/code&gt;. Если вдруг у вас в системе нет таких утилит,
то инструкции по установке опять-таки можно
&lt;a href="https://asdf-vm.com/#/core-manage-asdf-vm?id=dependencies"&gt;найти на сайте &lt;code&gt;asdf&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;После установки перезапустите ваш шелл и проверьте установку:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ asdf
...
&lt;span class="s2"&gt;&amp;quot;Late but latest&amp;quot;&lt;/span&gt;
-- Rajinikanth
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Если вы видите этот девиз &lt;code&gt;asdf&lt;/code&gt; после справки по командам,
то установка удалась, можно продолжать.
Девиз в моём вольном переводе означает что-то типа
"да, поздновато, но после нас подобных утилит уже не будет".
Амбициозно, но я думаю, что &lt;code&gt;asdf&lt;/code&gt; и правда в силах вытеснить с рынка все
другие подобные утилиты, которые специфичны для одного языка.&lt;/p&gt;
&lt;h2&gt;Плагины&lt;/h2&gt;
&lt;p&gt;По умолчанию &lt;code&gt;asdf&lt;/code&gt; поставляется без плагинов и содержит только базовую
функциональность, такую как управление плагинами. &lt;code&gt;asdf&lt;/code&gt; без плагинов по сути
не делает ничего полезного. Чтобы управлять версиями чего-либо,
нужно установить соответствующий плагин.
Давайте запросим список доступных к установке плагинов:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ asdf plugin list all
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;У меня вывелось аж 194 штуки.
Список плагинов можно посмотреть и на сайте,
&lt;a href="https://asdf-vm.com/#/plugins-all"&gt;вот здесь&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Есть плагины для всех популярных языков программирования:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;python&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nodejs&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rust&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;java&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kotlin&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;golang&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;php&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ruby&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;и, конечно же, для вашего любимого языка плагин тоже наверняка есть.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Кроме того, через &lt;code&gt;asdf&lt;/code&gt; можно управлять версиями различных утилит, например:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;github-cli&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jq&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;poetry&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;И сервисов, например:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;postgres&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mysql&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mongodb&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;redis&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Установка интересующего плагина выполняется следующим образом:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ asdf plugin add &amp;lt;имя плагина&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Рассмотрим работу с теми плагинами, с которыми мне доводилось работать.&lt;/p&gt;
&lt;h2&gt;Плагин для Python&lt;/h2&gt;
&lt;p&gt;Установим плагин для Python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ asdf plugin add python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;asdf&lt;/code&gt; имеет очень логично организованный интерфейс командной строки.
Запомните одно правило: если хотите просмотреть только установленные
плагины или версии плагина, то делайте &lt;code&gt;list&lt;/code&gt;,
а если вообще все, то &lt;code&gt;list all&lt;/code&gt;.&lt;/p&gt;
&lt;table&gt;
    &lt;thead&gt;
        &lt;tr&gt;
            &lt;th&gt;&lt;/th&gt;
            &lt;th style="text-align:center"&gt;плагин&lt;/th&gt;
            &lt;th style="text-align:center"&gt;версия&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td style="text-align:center"&gt;все&lt;/td&gt;
            &lt;td&gt;&lt;pre&gt;asdf plugin list all&lt;/pre&gt;&lt;/td&gt;
            &lt;td&gt;&lt;pre&gt;asdf list all python&lt;/pre&gt;&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td style="text-align:center"&gt;установленные&lt;/td&gt;
            &lt;td&gt;&lt;pre&gt;asdf plugin list&lt;/pre&gt;&lt;/td&gt;
            &lt;td&gt;&lt;pre&gt;asdf list python&lt;/pre&gt;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Выше мы уже смотрели список всех плагинов (через &lt;code&gt;list all&lt;/code&gt;), а теперь
давайте просмотрим список &lt;em&gt;установленных&lt;/em&gt; плагинов. Там должен быть &lt;code&gt;python&lt;/code&gt;,
мы ведь его только что установили:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ asdf plugin list
python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Отлично, плагин установлен! Давайте попросим &lt;code&gt;asdf&lt;/code&gt; показать
список доступных версий (хотим вообще все, поэтому &lt;code&gt;list all&lt;/code&gt;)
для плагина &lt;code&gt;python&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ asdf list all python
…
&lt;span class="m"&gt;3&lt;/span&gt;.6-dev
&lt;span class="m"&gt;3&lt;/span&gt;.6.1
&lt;span class="m"&gt;3&lt;/span&gt;.6.2
&lt;span class="m"&gt;3&lt;/span&gt;.6.3
&lt;span class="m"&gt;3&lt;/span&gt;.6.4
&lt;span class="m"&gt;3&lt;/span&gt;.6.5
&lt;span class="m"&gt;3&lt;/span&gt;.6.6
&lt;span class="m"&gt;3&lt;/span&gt;.6.7
&lt;span class="m"&gt;3&lt;/span&gt;.6.8
&lt;span class="m"&gt;3&lt;/span&gt;.6.9
&lt;span class="m"&gt;3&lt;/span&gt;.6.10
&lt;span class="m"&gt;3&lt;/span&gt;.7.0
&lt;span class="m"&gt;3&lt;/span&gt;.7-dev
&lt;span class="m"&gt;3&lt;/span&gt;.7.1
&lt;span class="m"&gt;3&lt;/span&gt;.7.2
&lt;span class="m"&gt;3&lt;/span&gt;.7.3
&lt;span class="m"&gt;3&lt;/span&gt;.7.4
&lt;span class="m"&gt;3&lt;/span&gt;.7.5
&lt;span class="m"&gt;3&lt;/span&gt;.7.6
&lt;span class="m"&gt;3&lt;/span&gt;.7.7
&lt;span class="m"&gt;3&lt;/span&gt;.8.0
&lt;span class="m"&gt;3&lt;/span&gt;.8-dev
&lt;span class="m"&gt;3&lt;/span&gt;.8.1
&lt;span class="m"&gt;3&lt;/span&gt;.8.2
&lt;span class="m"&gt;3&lt;/span&gt;.9.0a6
&lt;span class="m"&gt;3&lt;/span&gt;.9-dev
…
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Список я подсократил, но поверьте — он длинный.
Там есть как все версии эталонного интерпретатора CPython,
начиная с 2.1.3, так и альтернативные реализации Python
— PyPy, IronPython, Jython, MicroPython и другие.
Этот список очень напоминает подобный список из &lt;code&gt;pyenv&lt;/code&gt;, и это не просто
совпадение. Плагин &lt;code&gt;asdf-python&lt;/code&gt; действительно под капотом использует &lt;code&gt;pyenv&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;В процессе установки &lt;code&gt;asdf&lt;/code&gt; скачивает исходники Python и
собирает интерпретатор из них. Это значит, что на машине придётся иметь
установленные инструменты для сборки кода на C/C++ и все нужные
библиотеки-зависимости. Список зависимостей для всех ОС и дистрибутивов можно
&lt;a href="https://github.com/pyenv/pyenv/wiki/common-build-problems"&gt;посмотреть здесь&lt;/a&gt;.
Обязательно установите эти зависимости прежде чем пытаться устанавливать
какой-либо интерпретатор.&lt;/p&gt;
&lt;p&gt;Установка конкретной версии интерпретатора происходит вот так:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ asdf install python &lt;span class="m"&gt;3&lt;/span&gt;.7.7
$ asdf install python &lt;span class="m"&gt;3&lt;/span&gt;.8.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Давайте запросим список &lt;em&gt;установленных&lt;/em&gt; версий (&lt;code&gt;list&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ asdf list python
  &lt;span class="m"&gt;3&lt;/span&gt;.7.7
  &lt;span class="m"&gt;3&lt;/span&gt;.8.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Давайте назначим глобальной ту версию Python, которую собираемся
использовать чаще всего. Она будет использоваться по умолчанию, когда
не найдена никакая локальная версия. Здесь всё работает так же, как и в
&lt;code&gt;pyenv&lt;/code&gt;, но даже если вы никогда им не пользовались, то я надеюсь, что дальше
по примеру станет понятно как работают глобальные и локальные версии:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ asdf global python &lt;span class="m"&gt;3&lt;/span&gt;.8.2
$ python -V
Python &lt;span class="m"&gt;3&lt;/span&gt;.8.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Если вам интересно, как работает &lt;code&gt;asdf&lt;/code&gt;, то:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ which python
/home/br0ke/.asdf/shims/python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Опять же, он использует тот же принцип, что и &lt;code&gt;pyenv&lt;/code&gt;
— создает небольшие исполняемые файлы (shims) для всех команд,
которыми собирается управлять.
Эти шимы вызывают &lt;code&gt;asdf&lt;/code&gt; и он принимает решение о том,
какую версию программы нужно вызвать.&lt;/p&gt;
&lt;p&gt;Давайте для демонстрации работы локальных версий создадим директорию,
и представим, что в ней мы собираемся разрабатывать проект под
определенной версией Python, отличной от глобальной:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ mkdir my_project
$ &lt;span class="nb"&gt;cd&lt;/span&gt; my_project
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Установим локальную версию:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ asdf &lt;span class="nb"&gt;local&lt;/span&gt; python &lt;span class="m"&gt;3&lt;/span&gt;.7.7
$ python -V
Python &lt;span class="m"&gt;3&lt;/span&gt;.7.7
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;В этой директории &lt;code&gt;asdf&lt;/code&gt; создал специальный файл &lt;code&gt;.tool-versions&lt;/code&gt;
(аналог &lt;code&gt;.python-version&lt;/code&gt; из &lt;code&gt;pyenv&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ cat .tool-versions
python &lt;span class="m"&gt;3&lt;/span&gt;.7.7
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Этот файл содержит версии всех утилит, которые мы настроили для директории,
т.е. потенциально тут может быть не только Python.
Как правило, этот файл не нужно добавлять в систему контроля версий (git),
особенно если &lt;code&gt;asdf&lt;/code&gt; в рамках проекта пользуетесь только вы.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;asdf&lt;/code&gt; также вместо универсального &lt;code&gt;.tool-versions&lt;/code&gt; умеет использовать
файлы, специфичные для отдельных языков или инструментов, такие как
&lt;code&gt;.python-version&lt;/code&gt; (&lt;code&gt;pyenv&lt;/code&gt;), &lt;code&gt;.ruby-version&lt;/code&gt; (&lt;code&gt;rbenv&lt;/code&gt;),
&lt;code&gt;.nvmrc&lt;/code&gt; и &lt;code&gt;.node-version&lt;/code&gt; (&lt;code&gt;nodejs&lt;/code&gt;) и другие. Это сделано для того, чтобы
облегчить миграцию на &lt;code&gt;asdf&lt;/code&gt; с других менеджеров версий. По умолчанию эта
функциональность выключена, а включатся созданием файла &lt;code&gt;~/.asdfrc&lt;/code&gt; со следующим
содержимым:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;legacy_version_file = yes
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;При выходе из директории локально настроенная версия перестанет действовать
и версия вернётся на глобальную:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ &lt;span class="nb"&gt;cd&lt;/span&gt; ..
$ python -V
Python &lt;span class="m"&gt;3&lt;/span&gt;.8.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;По-моему, это очень удобно.
В целом &lt;code&gt;asdf&lt;/code&gt; работает так же как &lt;code&gt;pyenv&lt;/code&gt; и многие другие менеджеры версий
— в конце концов, они вдохновлены одной идеей.
Только &lt;code&gt;asdf&lt;/code&gt; вдохновился сильнее.&lt;/p&gt;
&lt;p&gt;Допустим, что нужда прижала вас установить IPython:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pip install ipython
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Чтобы IPython можно было запускать, нужно попросить &lt;code&gt;asdf&lt;/code&gt; пересоздать шимы:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ asdf reshim
$ ipython
Python &lt;span class="m"&gt;3&lt;/span&gt;.8.2 &lt;span class="o"&gt;(&lt;/span&gt;default, May &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="m"&gt;2020&lt;/span&gt;, &lt;span class="m"&gt;22&lt;/span&gt;:17:35&lt;span class="o"&gt;)&lt;/span&gt; 
Type &lt;span class="s1"&gt;&amp;#39;copyright&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;credits&amp;#39;&lt;/span&gt; or &lt;span class="s1"&gt;&amp;#39;license&amp;#39;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; more information
IPython &lt;span class="m"&gt;7&lt;/span&gt;.14.0 -- An enhanced Interactive Python. Type &lt;span class="s1"&gt;&amp;#39;?&amp;#39;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; help.

In &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;: print&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;asdf is awesome!&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Плагин для Node.js&lt;/h2&gt;
&lt;p&gt;Установим плагин для Node.js:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ asdf plugin add nodejs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Запросим список доступных версий:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ asdf list all nodejs
…
&lt;span class="m"&gt;12&lt;/span&gt;.16.3
&lt;span class="m"&gt;13&lt;/span&gt;.0.0
&lt;span class="m"&gt;13&lt;/span&gt;.0.1
&lt;span class="m"&gt;13&lt;/span&gt;.1.0
&lt;span class="m"&gt;13&lt;/span&gt;.2.0
&lt;span class="m"&gt;13&lt;/span&gt;.3.0
&lt;span class="m"&gt;13&lt;/span&gt;.4.0
&lt;span class="m"&gt;13&lt;/span&gt;.5.0
&lt;span class="m"&gt;13&lt;/span&gt;.6.0
&lt;span class="m"&gt;13&lt;/span&gt;.7.0
&lt;span class="m"&gt;13&lt;/span&gt;.8.0
&lt;span class="m"&gt;13&lt;/span&gt;.9.0
&lt;span class="m"&gt;13&lt;/span&gt;.10.0
&lt;span class="m"&gt;13&lt;/span&gt;.10.1
&lt;span class="m"&gt;13&lt;/span&gt;.11.0
&lt;span class="m"&gt;13&lt;/span&gt;.12.0
&lt;span class="m"&gt;13&lt;/span&gt;.13.0
&lt;span class="m"&gt;13&lt;/span&gt;.14.0
&lt;span class="m"&gt;14&lt;/span&gt;.0.0
&lt;span class="m"&gt;14&lt;/span&gt;.1.0
&lt;span class="m"&gt;14&lt;/span&gt;.2.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Все инструменты устанавливаются по-разному, и плагины это учитывают. Например,
Python собирался из исходников, а Node.js устанавливается простым
скачиванием бинарника, так что никакие зависимости для сборки не нужны.&lt;/p&gt;
&lt;p&gt;Но у плагина Node.js тоже есть свои тонкости. Чтобы установить версию
интерпретатора Node.js, нужно добавить ключи мейнтейнеров проекта Node.js
в систему. Это нужно, чтобы при установке плагин &lt;code&gt;asdf-nodejs&lt;/code&gt; проверил
цифровую подпись бинарника (в системе должна быть установлена
утилита установлена &lt;code&gt;gpg&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ bash ~/.asdf/plugins/nodejs/bin/import-release-team-keyring
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Теперь установим последнюю на данный момент версию Node.js:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ asdf install nodejs &lt;span class="m"&gt;14&lt;/span&gt;.2.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Назначим её глобальной и проверим работу:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ asdf global nodejs &lt;span class="m"&gt;14&lt;/span&gt;.2.0
$ node
Welcome to Node.js v14.2.0.
Type &lt;span class="s2"&gt;&amp;quot;.help&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; more information.
&amp;gt; console.log&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;asdf is super awesome!&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
asdf is super awesome!
undefined
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Давайте вернемся в директорию из предыдущего примера и
добавим там ещё и локальную версию Node.js:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ &lt;span class="nb"&gt;cd&lt;/span&gt; my_project
$ asdf &lt;span class="nb"&gt;local&lt;/span&gt; nodejs &lt;span class="m"&gt;14&lt;/span&gt;.2.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Теперь в &lt;code&gt;.tool-versions&lt;/code&gt; лежит следующее содержимое:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ cat .tool-versions 
python &lt;span class="m"&gt;3&lt;/span&gt;.7.7
nodejs &lt;span class="m"&gt;14&lt;/span&gt;.2.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;Заключение&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;asdf&lt;/code&gt; незаменим, если вы программируете более чем на одном языке.
Крайне рекомендую присмотреться к нему, даже если сейчас вы
программируете только на одном языке. Лучше выбирать инструменты на вырост.&lt;/p&gt;
&lt;p&gt;Не бойтесь пробовать устанавливать разные плагины,
только обязательно читайте инструкции к ним.&lt;/p&gt;
&lt;p&gt;Обязательно
&lt;a href="https://semakin.dev/pages/subscribe/"&gt;подпишитесь на уведомления&lt;/a&gt;
о новых постах в блоге, чтобы ничего не пропустить!&lt;/p&gt;
&lt;h1&gt;Дополнительное чтение&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://asdf-vm.com/#/"&gt;официальный сайт&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/asdf-vm/asdf"&gt;репозиторий проекта на GitHub&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/danhper/asdf-python"&gt;Python-плагин&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/asdf-vm/asdf-nodejs"&gt;Node.js-плагин&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Обложка: &lt;a href="https://www.flickr.com/photos/purple-lover/13583362554"&gt;idreamlikecrazy, One Ring to rule them all&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="blog"></category><category term="python"></category><category term="node.js"></category><category term="pyenv"></category><category term="asdf"></category></entry><entry><title>Установка нескольких версий Python параллельно при помощи pyenv</title><link href="https://semakin.dev/2020/05/pyenv/" rel="alternate"></link><published>2020-05-04T11:17:51+05:00</published><updated>2020-05-19T21:51:55+05:00</updated><author><name>Андрей Семакин</name></author><id>tag:semakin.dev,2020-05-04:/2020/05/pyenv/</id><summary type="html">&lt;p&gt;Простое управление версиями интерпретатора.&lt;/p&gt;</summary><content type="html">&lt;p&gt;В большинстве операционных систем Python предустановлен
(ну, кроме Windows, но даже там теперь есть
&lt;a href="https://devblogs.microsoft.com/python/python-in-the-windows-10-may-2019-update/"&gt;команда &lt;code&gt;python&lt;/code&gt;, которая предложит установить интерпретатор из магазина приложений&lt;/a&gt;).
В Unix-подобных операционных системах, таких как Linux и MacOS, Python
пустил корни очень глубоко. Множество компонентов ОС рассчитывают, что Python
установлен и работает стабильно. Это и хорошо, и плохо.&lt;/p&gt;
&lt;p&gt;Это хорошо, потому что хотя бы какой-то Python в большинстве систем доступен из
коробки — бери и пользуйся. Иногда доступно сразу несколько версий
интерпретатора, например, &lt;code&gt;python2&lt;/code&gt; указывает на устаревшую версию 2.7,
&lt;code&gt;python3&lt;/code&gt; — на какую-нибудь стабильную версию Python 3, типа 3.6 или 3.7, а
просто &lt;code&gt;python&lt;/code&gt; указывает либо на одно, либо на другое (в последнее время
предпочтение чаще отдаётся третьей версии). Для обучения или для
тестирования этого может быть вполне достаточно.&lt;/p&gt;
&lt;p&gt;С другой стороны, это плохо, потому что, как правило, предустановленный Python
настолько стабилен, что уже успел зарасти мхом.
В некоторых системах до сих пор предустановлен только Python 2,
но даже если вам повезёт получить Python третьей версии,
то наверняка он будет отставать от последней версии на пару минорных релизов.
Не факт, что вам это подойдёт.&lt;/p&gt;
&lt;p&gt;Иногда нужно иметь сразу несколько версий Python для работы над
разными проектами, например, 3.7 и 3.8. В некоторых ОС нужные версии можно
установить через пакетный менеджер (например, в &lt;a href="https://developer.fedoraproject.org/tech/languages/python/multiple-pythons.html"&gt;Fedora через dnf&lt;/a&gt;)
— из основных репозиториев или из сторонних.
Но зачастую такие репозитории содержат не все релизы
интерпретаторов, а лишь выбранное мейнтейнерами репозиториев подмножество.&lt;/p&gt;
&lt;p&gt;Решение у всех этих проблем одно — нужно установить недостающие версии
интерпретатора, какими бы они ни были. Этому и посвящён пост.&lt;/p&gt;
&lt;h1&gt;pyenv&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://github.com/pyenv/pyenv"&gt;&lt;code&gt;pyenv&lt;/code&gt;&lt;/a&gt; — утилита, которая позволяет
легко переключаться между несколькими версиями интерпретатора Python, а
также устанавливать новые. Позволяет устанавливать, наверное, вообще
все известные науке версии интерпретаторов Python. Работает просто и незаметно. &lt;/p&gt;
&lt;p&gt;&lt;code&gt;pyenv&lt;/code&gt; — это всего лишь один из последователей аналогичного инструмента
из мира Ruby — &lt;a href="https://github.com/rbenv/rbenv"&gt;&lt;code&gt;rbenv&lt;/code&gt;&lt;/a&gt;.
Есть ещё и &lt;a href="https://github.com/nodenv/nodenv"&gt;&lt;code&gt;nodenv&lt;/code&gt;&lt;/a&gt; для Node.js,
который тоже вдохновился &lt;code&gt;rbenv&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Проект написан целиком на &lt;code&gt;bash&lt;/code&gt;. Это значит, что он никак не зависит
от Python — было бы забавно, если бы для установки Python нужен был бы Python.
Также это означает, что на Windows &lt;code&gt;pyenv&lt;/code&gt; работать не будет
(&lt;a href="https://github.com/pyenv/pyenv/issues/62"&gt;тред с обсуждением&lt;/a&gt;).
Следует отметить, что в Windows проблема установки нескольких версий
и не возникает — там всегда можно скачать и установить
сколько угодно интерпретаторов &lt;a href="https://www.python.org/downloads/windows/"&gt;с официального сайта&lt;/a&gt;,
а &lt;a href="https://www.python.org/dev/peps/pep-0397/"&gt;&lt;code&gt;pylauncher&lt;/code&gt;&lt;/a&gt; поможет выбрать
из них нужную версию. 
Кроме того, пользователи современных версий
Windows могут использовать &lt;code&gt;pyenv&lt;/code&gt; внутри WSL (Windows Subsystem for Linux).
Ещё это означает, что у авторов много отваги — я бы не решился писать
на &lt;code&gt;bash&lt;/code&gt; что-то настолько сложное. Как же хорошо, что всё уже написано.&lt;/p&gt;
&lt;h2&gt;Установка&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Скачаем &lt;code&gt;pyenv&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Установка &lt;code&gt;pyenv&lt;/code&gt; производится простым клонированием git-репозитория.&lt;/p&gt;
&lt;p&gt;У проекта есть &lt;a href="https://github.com/pyenv/pyenv-installer"&gt;умный скрипт&lt;/a&gt;,
который скачает &lt;code&gt;pyenv&lt;/code&gt; и его сотоварищей:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ curl https://pyenv.run &lt;span class="p"&gt;|&lt;/span&gt; bash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Скрипт не требует прав суперпользователя (без &lt;code&gt;sudo&lt;/code&gt;), потому что
всё устанавливается в домашнюю директорию пользователя. Туда же
будут устанавливаться и интерпретаторы. Если страшно запускать
какие-то скрипты из интернета (так и должно быть), то прочитать код скрипта можно
&lt;a href="https://github.com/pyenv/pyenv-installer/blob/master/bin/pyenv-installer"&gt;здесь&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Настроим шелл.&lt;/p&gt;
&lt;p&gt;Предыдущая команда перед завершением должна была напечатать инструкции
по настройке шелла. Допустим, в случае с &lt;code&gt;bash&lt;/code&gt; она выводит
следующее:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;WARNING&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;seems&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt; &lt;span class="n"&gt;still&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;added&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pyenv&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;load&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;Load&lt;/span&gt; &lt;span class="n"&gt;pyenv&lt;/span&gt; &lt;span class="n"&gt;automatically&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="n"&gt;adding&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;following&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="o"&gt;~/.&lt;/span&gt;&lt;span class="n"&gt;bashrc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="n"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;~/.pyenv/bin:$PATH&amp;quot;&lt;/span&gt;
&lt;span class="nf"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;$(pyenv init -)&amp;quot;&lt;/span&gt;
&lt;span class="nf"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;$(pyenv virtualenv-init -)&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;В случае с &lt;code&gt;zsh&lt;/code&gt; нужно будет добавить те же самые строки в &lt;code&gt;~/.zshrc&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;В случае с &lt;code&gt;fish&lt;/code&gt; в связи с особенностями самого шелла инструкции отличаются:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="k"&gt;Load&lt;/span&gt; &lt;span class="n"&gt;pyenv&lt;/span&gt; &lt;span class="n"&gt;automatically&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;adding&lt;/span&gt;
&lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;following&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;fish&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="n"&gt;PATH&lt;/span&gt; &lt;span class="ss"&gt;&amp;quot;~/.pyenv/bin&amp;quot;&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;PATH&lt;/span&gt;
&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="c1"&gt;--is-interactive; and . (pyenv init -|psub)&lt;/span&gt;
&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="c1"&gt;--is-interactive; and . (pyenv virtualenv-init -|psub)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Кстати, горячо рекомендую попробовать &lt;a href="https://fishshell.com/"&gt;&lt;code&gt;fish&lt;/code&gt;&lt;/a&gt;, очень удобный шелл.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Установим зависимости для сборки.&lt;/p&gt;
&lt;p&gt;При установке новой версии интерпретатора через &lt;code&gt;pyenv&lt;/code&gt; под капотом
происходит сборка из исходников, поэтому для успешной установки
необходимы некоторые зависимости. Полный и актуальный список
для своей ОС смотрите &lt;a href="https://github.com/pyenv/pyenv/wiki#suggested-build-environment"&gt;здесь&lt;/a&gt;
или &lt;a href="https://github.com/pyenv/pyenv/wiki/common-build-problems#prerequisites"&gt;здесь&lt;/a&gt;.
Лучше установить всё заранее.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Перезапустим шелл и проверим установку.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pyenv --version
pyenv &lt;span class="m"&gt;1&lt;/span&gt;.2.18
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Как это работает&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;pyenv&lt;/code&gt; работает благодаря манипуляциям над переменной окружения &lt;code&gt;$PATH&lt;/code&gt;.
Эта переменная содержит в себе список директорий, в которых ОС будет искать
исполняемые файлы, вызванные без указания полного пути. Именно
благодаря этой переменной мы можем в терминале вместо &lt;code&gt;/bin/cat&lt;/code&gt; вызывать
просто &lt;code&gt;cat&lt;/code&gt;. Когда мы набираем в терминале имя программы (&lt;code&gt;cat&lt;/code&gt;),
ОС перебирает директории из &lt;code&gt;$PATH&lt;/code&gt; слева направо, пока в одной
из них (в данном примере &lt;code&gt;/bin&lt;/code&gt;) не найдёт программу с именем &lt;code&gt;cat&lt;/code&gt;,
которую и запустит. Поиск прекращается после первого совпадения.&lt;/p&gt;
&lt;p&gt;Команда &lt;code&gt;pyenv init -&lt;/code&gt;, которую мы добавили в конфиг шелла (&lt;code&gt;.bashrc&lt;/code&gt; или аналог)
добавляет директории &lt;code&gt;pyenv&lt;/code&gt; в самое начало переменной &lt;code&gt;$PATH&lt;/code&gt;.
Зачем это нужно? &lt;code&gt;pyenv&lt;/code&gt; создаёт небольшие исполняемые файлы,
так называемые файлы-прослойки (shims), для всех команд,
которыми он собирается управлять, например, &lt;code&gt;python&lt;/code&gt;, &lt;code&gt;pip&lt;/code&gt;, &lt;code&gt;ipython&lt;/code&gt; и так далее.
Эти файлы-прослойки должны попасть в &lt;code&gt;$PATH&lt;/code&gt; прежде самих управляемых программ
и "затенить" системные &lt;code&gt;python&lt;/code&gt;, &lt;code&gt;pip&lt;/code&gt; и так далее.
Эти файлы-прослойки в конечном счёте просто вызывают сам &lt;code&gt;pyenv&lt;/code&gt;
с нужными аргументами.
Таким образом &lt;code&gt;pyenv&lt;/code&gt; перехватывает обращения к некоторым именам,
и анализируя поступившую к нему информацию,
принимает решение о том, какую именно версию Python нужно запустить.
При выборе версии &lt;code&gt;pyenv&lt;/code&gt; принимает во внимание следующие факторы в
указанном порядке:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Переменная окружения &lt;code&gt;PYENV_VERSION&lt;/code&gt;, если указана.&lt;/p&gt;
&lt;p&gt;В неё можно указать какую конкретно версию Python нужно использовать
в рамках текущего сеанса. Удобно, если вам по какой-то причине понадобится
сменить выбранную версию интерпретатора, например, в одном
из окон терминала.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Локальная версия Python.&lt;/p&gt;
&lt;p&gt;При помощи специального файла &lt;code&gt;.python-version&lt;/code&gt; можно настроить
версию интерпретатора для определенного проекта. Захо́дите внутрь
директории (&lt;code&gt;cd project/&lt;/code&gt;), и &lt;code&gt;pyenv&lt;/code&gt; внезапно понимает, что нужно
сменить Python. Выхо́дите обратно — версия Python меняется на глобальную.
Это распространяется и на все поддиректории проекта —
&lt;code&gt;pyenv&lt;/code&gt; рекурсивно ищет файл &lt;code&gt;.python-version&lt;/code&gt; вверх по файловой системе,
пока не дойдёт до корня.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Глобальная версия Python.&lt;/p&gt;
&lt;p&gt;В файле &lt;code&gt;~/.pyenv/version&lt;/code&gt; записана глобальная версия Python, которая
будет использоваться по умолчанию, если не сконфигурирована локальная
версия.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Вам вряд ли придётся вручную трогать эти файлы, потому что у &lt;code&gt;pyenv&lt;/code&gt; есть
удобные команды (&lt;code&gt;pyenv local&lt;/code&gt; и &lt;code&gt;pyenv global&lt;/code&gt;),
чтобы ими управлять, но знать о файлах всё равно полезно.&lt;/p&gt;
&lt;h2&gt;Использование&lt;/h2&gt;
&lt;h3&gt;Установка новой версии Python&lt;/h3&gt;
&lt;p&gt;Сначала посмотрим, какие версии Python &lt;code&gt;pyenv&lt;/code&gt; может установить:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pyenv install --list
...
&lt;span class="m"&gt;3&lt;/span&gt;.6.0
&lt;span class="m"&gt;3&lt;/span&gt;.6-dev
&lt;span class="m"&gt;3&lt;/span&gt;.6.1
&lt;span class="m"&gt;3&lt;/span&gt;.6.2
&lt;span class="m"&gt;3&lt;/span&gt;.6.3
&lt;span class="m"&gt;3&lt;/span&gt;.6.4
&lt;span class="m"&gt;3&lt;/span&gt;.6.5
&lt;span class="m"&gt;3&lt;/span&gt;.6.6
&lt;span class="m"&gt;3&lt;/span&gt;.6.7
&lt;span class="m"&gt;3&lt;/span&gt;.6.8
&lt;span class="m"&gt;3&lt;/span&gt;.6.9
&lt;span class="m"&gt;3&lt;/span&gt;.6.10
&lt;span class="m"&gt;3&lt;/span&gt;.7.0
&lt;span class="m"&gt;3&lt;/span&gt;.7-dev
&lt;span class="m"&gt;3&lt;/span&gt;.7.1
&lt;span class="m"&gt;3&lt;/span&gt;.7.2
&lt;span class="m"&gt;3&lt;/span&gt;.7.3
&lt;span class="m"&gt;3&lt;/span&gt;.7.4
&lt;span class="m"&gt;3&lt;/span&gt;.7.5
&lt;span class="m"&gt;3&lt;/span&gt;.7.6
&lt;span class="m"&gt;3&lt;/span&gt;.7.7
&lt;span class="m"&gt;3&lt;/span&gt;.8.0
&lt;span class="m"&gt;3&lt;/span&gt;.8-dev
&lt;span class="m"&gt;3&lt;/span&gt;.8.1
&lt;span class="m"&gt;3&lt;/span&gt;.8.2
&lt;span class="m"&gt;3&lt;/span&gt;.9.0a6
&lt;span class="m"&gt;3&lt;/span&gt;.9-dev
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Список довольно длинный, поэтому я его подсократил. Обычно вас будут
интересовать такие версии, как &lt;code&gt;3.8.2&lt;/code&gt; или &lt;code&gt;3.7.7&lt;/code&gt; — это версии самой
распространённой реализации интерпретатора CPython. Но если
вам нужна экзотика, то &lt;code&gt;pyenv&lt;/code&gt; умеет устанавливать любые сорта интерпретаторов
Python (&lt;code&gt;pypy3.6-7.3.0&lt;/code&gt;, &lt;code&gt;stackless-3.7.5&lt;/code&gt;, &lt;code&gt;jython-2.7.1&lt;/code&gt;,
&lt;code&gt;ironpython-2.7.7&lt;/code&gt;, &lt;code&gt;micropython-1.12&lt;/code&gt; и т.д.). Для вас ведь не стало
новостью, что существует много разных реализаций интерпретатора Python?&lt;/p&gt;
&lt;p&gt;Установим CPython 3.8.2:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pyenv install &lt;span class="m"&gt;3&lt;/span&gt;.8.2
Downloading Python-3.8.2.tar.xz...
Installing Python-3.8.2...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Через пару минут ожидания ваш новоиспечённый Python будет готов.&lt;/p&gt;
&lt;p&gt;Можно сразу же назначить эту версию глобальной:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pyenv global &lt;span class="m"&gt;3&lt;/span&gt;.8.2
$ python -V
Python &lt;span class="m"&gt;3&lt;/span&gt;.8.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Давайте в целях демонстрации установим ещё парочку интерпретаторов:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pyenv install &lt;span class="m"&gt;2&lt;/span&gt;.7.18
$ pyenv install &lt;span class="m"&gt;3&lt;/span&gt;.9.0a6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Получим список установленных версий интерпретатора:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pyenv versions
  &lt;span class="m"&gt;2&lt;/span&gt;.7.18
* &lt;span class="m"&gt;3&lt;/span&gt;.8.2 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; by /home/br0ke/.pyenv/version&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="m"&gt;3&lt;/span&gt;.9.0a6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Кстати, если нужно, то можно делать активными сразу несколько
версий одновременно:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pyenv global &lt;span class="m"&gt;3&lt;/span&gt;.8.2 &lt;span class="m"&gt;2&lt;/span&gt;.7.18
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Теперь вывод версий покажет следующее:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pyenv versions
* &lt;span class="m"&gt;2&lt;/span&gt;.7.18 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; by /home/br0ke/.pyenv/version&lt;span class="o"&gt;)&lt;/span&gt;
* &lt;span class="m"&gt;3&lt;/span&gt;.8.2 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; by /home/br0ke/.pyenv/version&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="m"&gt;3&lt;/span&gt;.9.0a6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;А работать это будет вот таким образом:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ python -V
Python &lt;span class="m"&gt;3&lt;/span&gt;.8.2
$ python3 -V
Python &lt;span class="m"&gt;3&lt;/span&gt;.8.2
$ python2 -V
Python &lt;span class="m"&gt;2&lt;/span&gt;.7.18
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Грубо говоря, та версия, которая указана первой (3.8.2),
имеет приоритет и занимает все нужные ей имена. Следующие версии (2.7.18)
могут занять любые оставшиеся свободные имена (в данном случае, это только имя
&lt;code&gt;python2&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;А файл глобальной версии &lt;code&gt;~/.pyenv/version&lt;/code&gt; на данный момент имеет вот
такое содержимое:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ cat ~/.pyenv/version
&lt;span class="m"&gt;3&lt;/span&gt;.8.2
&lt;span class="m"&gt;2&lt;/span&gt;.7.18
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Локальная версия&lt;/h3&gt;
&lt;p&gt;Давайте создадим директорию и войдём в неё:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ mkdir my_project
$ &lt;span class="nb"&gt;cd&lt;/span&gt; my_project
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Представим, что в этой директории мы будем разрабатывать некий
проект, на котором мы хотим опробовать фишки нового Python 3.9.
Сообщим об этом &lt;code&gt;pyenv&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pyenv &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;.9.0a
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;В директории появился файл &lt;code&gt;.python-version&lt;/code&gt; со следующим содержимым:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ cat .python-version
&lt;span class="m"&gt;3&lt;/span&gt;.9.0a6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;На данный момент список версий показывает следующее (удобно использовать
эту команду, чтобы понять какую версию и почему &lt;code&gt;pyenv&lt;/code&gt; активирует):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pyenv versions
  &lt;span class="m"&gt;2&lt;/span&gt;.7.18
  &lt;span class="m"&gt;3&lt;/span&gt;.8.2
* &lt;span class="m"&gt;3&lt;/span&gt;.9.0a6 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; by /home/br0ke/my_project/.python-version&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Изменения немедленно вступили в силу:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ python -V
Python &lt;span class="m"&gt;3&lt;/span&gt;.9.0a6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Но эта конфигурация никак не влияет на работу &lt;code&gt;pyenv&lt;/code&gt; вне директории проекта:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ &lt;span class="nb"&gt;cd&lt;/span&gt; ..
$ python -V
&lt;span class="m"&gt;3&lt;/span&gt;.8.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Как и в случае с глобальной конфигурацией, можно локально активировать
сразу несколько версий интерпретатора.&lt;/p&gt;
&lt;h3&gt;Установим IPython&lt;/h3&gt;
&lt;p&gt;Часто бывает нужно установить какой-нибудь пакет так, чтобы он тоже
стал доступен из командной строки. Допустим, что нам нужно установить
&lt;a href="https://ipython.org/"&gt;&lt;code&gt;ipython&lt;/code&gt;&lt;/a&gt; — более удобную версию REPL Python.
Сделаем это:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ &lt;span class="nb"&gt;cd&lt;/span&gt; my_project
$ pip install ipython
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Запустим:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ ipython
Python &lt;span class="m"&gt;3&lt;/span&gt;.9.0a6 &lt;span class="o"&gt;(&lt;/span&gt;default, May  &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="m"&gt;2020&lt;/span&gt;, &lt;span class="m"&gt;16&lt;/span&gt;:58:20&lt;span class="o"&gt;)&lt;/span&gt;
Type &lt;span class="s1"&gt;&amp;#39;copyright&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;credits&amp;#39;&lt;/span&gt; or &lt;span class="s1"&gt;&amp;#39;license&amp;#39;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; more information
IPython &lt;span class="m"&gt;7&lt;/span&gt;.14.0 -- An enhanced Interactive Python. Type &lt;span class="s1"&gt;&amp;#39;?&amp;#39;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; help.

In &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Программа сразу доступна, благодаря тому, что &lt;code&gt;pyenv&lt;/code&gt; очень умный и
создал новый файл-прослойку (shim) автоматически:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ which ipython
/home/br0ke/.pyenv/shims/ipython
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Вне директории с проектом &lt;code&gt;ipython&lt;/code&gt; будет недоступен, ведь он же установлен
в локальный интерпретатор &lt;code&gt;3.9.0a6&lt;/code&gt;, а снаружи активирован другой
интерпретатор — можете проверить самостоятельно.&lt;/p&gt;
&lt;p&gt;Возникают ситуации, когда по какой-то причине прослойка не создалась
или с ней случилось что-то ещё, например, удалилась:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ rm &lt;span class="k"&gt;$(&lt;/span&gt;which ipython&lt;span class="k"&gt;)&lt;/span&gt;
$ ipython
No such file or directory
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Не беда! Можно попросить &lt;code&gt;pyenv&lt;/code&gt; пересоздать их все заново:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pyenv rehash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;И всё работает снова:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ ipython
Python &lt;span class="m"&gt;3&lt;/span&gt;.9.0a6 &lt;span class="o"&gt;(&lt;/span&gt;default, May  &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="m"&gt;2020&lt;/span&gt;, &lt;span class="m"&gt;16&lt;/span&gt;:58:20&lt;span class="o"&gt;)&lt;/span&gt;
Type &lt;span class="s1"&gt;&amp;#39;copyright&amp;#39;&lt;/span&gt;, &lt;span class="s1"&gt;&amp;#39;credits&amp;#39;&lt;/span&gt; or &lt;span class="s1"&gt;&amp;#39;license&amp;#39;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; more information
IPython &lt;span class="m"&gt;7&lt;/span&gt;.14.0 -- An enhanced Interactive Python. Type &lt;span class="s1"&gt;&amp;#39;?&amp;#39;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; help.

In &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Можно вообще добавить команду &lt;code&gt;pyenv rehash&lt;/code&gt; в свой &lt;code&gt;~/.bashrc&lt;/code&gt; (или аналог),
чтобы при запуске шелла гарантированно иметь рабочие файлы-прослойки (shims).&lt;/p&gt;
&lt;h2&gt;Заключение&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;pyenv&lt;/code&gt; — очень удобный и полезный инструмент в ситуациях, когда нужную вам
версию Python нельзя установить средствами операционной системы.
Я вообще предпочитаю устанавливать все нужные мне версии интерпретатора
самостоятельно через &lt;code&gt;pyenv&lt;/code&gt; или &lt;code&gt;asdf&lt;/code&gt;, даже если ОС уже содержит точно
такую же версию — пусть ОС использует свою копию для служебных целей,
а я для разработки буду использовать свою собственную копию, где смогу
проводить любые кровавые эксперименты, не боясь поломать ОС.&lt;/p&gt;
&lt;p&gt;Обязательно
&lt;a href="https://semakin.dev/pages/subscribe/"&gt;подпишитесь на уведомления&lt;/a&gt;
о новых постах в блоге, чтобы ничего не пропустить!&lt;/p&gt;
&lt;h2&gt;Дополнительное чтение&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/pyenv/pyenv"&gt;Репозиторий проекта на GitHub&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://realpython.com/intro-to-pyenv/"&gt;Туториал на RealPython&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://asdf-vm.com/#/"&gt;Альтернатива: универсальный менеджер версий &lt;code&gt;asdf&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Обложка: &lt;a href="https://www.needpix.com/photo/download/1191596/snakes-black-snakes-animal-reptile-free-pictures-free-photos-free-images-royalty-free-free-illustrations"&gt;Schwoaze, Snakes Black Snakes Animal Free Photo&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="blog"></category><category term="python"></category><category term="pyenv"></category></entry><entry><title>pipenv — как pip, только удобнее</title><link href="https://semakin.dev/2020/04/pipenv/" rel="alternate"></link><published>2020-04-29T13:56:47+05:00</published><updated>2020-05-30T20:36:53+05:00</updated><author><name>Андрей Семакин</name></author><id>tag:semakin.dev,2020-04-29:/2020/04/pipenv/</id><summary type="html">&lt;p&gt;Как пользоваться и в каких случаях стоит обратить внимание на этот инструмент.&lt;/p&gt;</summary><content type="html">&lt;h1&gt;pipenv&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;pipenv&lt;/code&gt; — это замечательный проект, который призван упростить
организацию рабочего процесса для Python-разработчиков. Он
решает несколько наиболее актуальных для разработчика проблем
(да, несколько, вопреки &lt;a href="https://ru.wikipedia.org/wiki/%D0%A4%D0%B8%D0%BB%D0%BE%D1%81%D0%BE%D1%84%D0%B8%D1%8F_Unix"&gt;Unix-way&lt;/a&gt;).
Этакий швейцарский нож для питонистов.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pipenv&lt;/code&gt; нельзя рассматривать как замену &lt;code&gt;pip&lt;/code&gt;, скорее это надстройка над
&lt;code&gt;pip&lt;/code&gt;. Но даже
&lt;a href="https://packaging.python.org/guides/tool-recommendations/"&gt;PyPA всерьёз рекомендует&lt;/a&gt;
рассмотреть &lt;code&gt;pipenv&lt;/code&gt; для управления зависимостями приложений, что как
минимум означает, что проект хорошо зарекомендовал себя в сообществе.&lt;/p&gt;
&lt;p&gt;Изначальный автор проекта — 
&lt;a href="https://kenreitz.org/"&gt;Кеннет Рейц (Kenneth Reitz)&lt;/a&gt; — он ещё
и автор &lt;a href="https://github.com/psf/requests"&gt;&lt;code&gt;requests&lt;/code&gt;&lt;/a&gt; и множества
других проектов "for humans",
очевидно, вдохновлялся пакетными менеджерами из других экосистем,
такими как &lt;a href="https://www.npmjs.com/"&gt;&lt;code&gt;npm&lt;/code&gt;&lt;/a&gt; (JavaScript)
и &lt;a href="https://bundler.io/"&gt;&lt;code&gt;bundler&lt;/code&gt;&lt;/a&gt; (Ruby), так что если вы когда-то
пользовались этими инструментами, то можете заметить множество параллелей.&lt;/p&gt;
&lt;p&gt;В названии проекта кроются два основных его назначения:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pip&lt;/code&gt; — установка и управления зависимостями проекта;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;env&lt;/code&gt; — создание и управление виртуальным окружением для проекта.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Грубо говоря, &lt;code&gt;pipenv&lt;/code&gt; можно рассматривать как симбиоз утилит &lt;code&gt;pip&lt;/code&gt; и
&lt;code&gt;venv&lt;/code&gt; (или &lt;code&gt;virtualenv&lt;/code&gt;), которые работают вместе, пряча многие
неудобные детали от конечного пользователя.&lt;/p&gt;
&lt;p&gt;Помимо этого &lt;code&gt;pipenv&lt;/code&gt; ещё умеет вот такое:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;автоматически находить интерпретатор Python нужной версии
(находит даже интерпретаторы, установленные через
&lt;a href="https://github.com/pyenv/pyenv"&gt;&lt;code&gt;pyenv&lt;/code&gt;&lt;/a&gt;
и &lt;a href="https://asdf-vm.com/"&gt;&lt;code&gt;asdf&lt;/code&gt;&lt;/a&gt;!);&lt;/li&gt;
&lt;li&gt;запускать вспомогательные скрипты для разработки;&lt;/li&gt;
&lt;li&gt;загружать переменные окружения из файла &lt;code&gt;.env&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;проверять зависимости на наличие известных уязвимостей.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Стоит сразу оговориться, что если вы разрабатываете библиотеку (или
что-то, что устанавливается через &lt;code&gt;pip&lt;/code&gt;, и должно работать на нескольких
версиях интерпретатора),
то &lt;code&gt;pipenv&lt;/code&gt; — не ваш путь. Этот инструмент создан в первую очередь
для разработчиков конечных приложений (консольных утилит, микросервисов,
веб-сервисов). Формат хранения зависимостей подразумевает работу
только на одной конкретной версии интерпретатора (это имеет смысл для
конечных приложений, но для библиотек это, как правило, не приемлемо).
Для разработчиков библиотек существует другой прекрасный инструмент —
&lt;a href="https://python-poetry.org/"&gt;&lt;code&gt;poetry&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Итак, начнём по порядку.&lt;/p&gt;
&lt;h2&gt;Установка&lt;/h2&gt;
&lt;p&gt;Как я писал в &lt;a href="https://semakin.dev/2020/04/python_virtualenv/"&gt;посте про виртуальные окружения&lt;/a&gt;,
не стоит устанавливать пакеты в глобальный интерпретатор, поэтому предпочтительным
способом установки является пакетный менеджер вашей ОС.&lt;/p&gt;
&lt;p&gt;Например, на MacOS &lt;code&gt;pipenv&lt;/code&gt; можно установить через &lt;code&gt;brew&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ brew install pipenv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;А на Fedora Linux вот так:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ sudo dnf install pipenv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;На Ubuntu можно установить &lt;code&gt;pipenv&lt;/code&gt; из &lt;a href="https://launchpad.net/~pypa/+archive/ubuntu/ppa"&gt;специального PPA&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ sudo apt install software-properties-common python-software-properties
$ sudo add-apt-repository ppa:pypa/ppa
$ sudo apt update
$ sudo apt install pipenv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Во всех остальных случаях, в частности на Windows, самый простой способ — это установка
в домашнюю директорию пользователя
(опять же, см. &lt;a href="https://semakin.dev/2020/04/python_virtualenv/"&gt;пост про виртуальные окружения&lt;/a&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pip install --user pipenv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Теперь проверим установку:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv --version
pipenv, version &lt;span class="m"&gt;2018&lt;/span&gt;.11.26
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Если вы получили похожий вывод, значит, всё в порядке.&lt;/p&gt;
&lt;p&gt;При возникновении проблем с установкой, обратитесь к
&lt;a href="https://pipenv.pypa.io/en/latest/install/"&gt;официальной документации&lt;/a&gt;.
Если совсем беда, то напишите комментарий под этим постом,
попробуем помочь 😊&lt;/p&gt;
&lt;h2&gt;Файлы &lt;code&gt;pipenv&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;pipenv&lt;/code&gt; использует свой собственный формат файла для описания зависимостей
проекта — &lt;code&gt;Pipfile&lt;/code&gt;.
Этот файл имеет &lt;a href="https://github.com/toml-lang/toml"&gt;формат TOML&lt;/a&gt;.
В принципе его можно редактировать руками, но &lt;code&gt;pipenv&lt;/code&gt; достаточно неплохо
и сам умеет обновлять этот файл, когда вы просто работаете с утилитой
через командную строку. Структуру этого файла рассмотрим чуть позже.&lt;/p&gt;
&lt;p&gt;В паре с &lt;code&gt;Pipfile&lt;/code&gt; идёт &lt;code&gt;Pipfile.lock&lt;/code&gt;. Он имеет формат JSON и не
предназначен для редактирования руками. Этот файл хранит контрольные
суммы пакетов, которые вы устанавливаете в проект, что даёт гарантию,
что развёрнутые на разных машинах окружения будут идентичны друг другу.
&lt;code&gt;pipenv&lt;/code&gt; автоматически обновляет контрольные суммы в этом файле, когда
вы устанавливаете или обновляете зависимости. При развёртывании окружения
&lt;code&gt;pipenv&lt;/code&gt; сверит сохранённые контрольные суммы с фактически
получившимися, и в случае чего уведомит вас, что развёртывание
не удалось. Это очень важный плюс в копилку &lt;code&gt;pipenv&lt;/code&gt; по сравнению с &lt;code&gt;pip&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Оба этих файла можно и нужно сохранять в системе контроля версий (git).&lt;/p&gt;
&lt;p&gt;Вообще, идею использовать два файла для описания зависимостей нельзя
назвать новой.
Здесь явно прослеживается параллель между &lt;code&gt;Gemfile&lt;/code&gt; и &lt;code&gt;Gemfile.lock&lt;/code&gt;
из мира Ruby и &lt;code&gt;package.json&lt;/code&gt; и &lt;code&gt;package-lock.json&lt;/code&gt; из мира JavaScript.
Все эти файлы имеют схожее назначение.&lt;/p&gt;
&lt;h2&gt;Использование&lt;/h2&gt;
&lt;h3&gt;Инициализация проекта&lt;/h3&gt;
&lt;p&gt;Давайте создадим простой проект под управлением &lt;code&gt;pipenv&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Подготовка:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ mkdir pipenv_demo
$ &lt;span class="nb"&gt;cd&lt;/span&gt; pipenv_demo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Создать новый проект, использующий конкретную версию Python можно вот такой командой:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv --python &lt;span class="m"&gt;3&lt;/span&gt;.8
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Если же вам не нужно указывать версию так конкретно, то есть шорткаты:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Создает проект с Python 3, версию выберет автоматически.&lt;/span&gt;
$ pipenv --three

&lt;span class="c1"&gt;# Аналогично с Python 2.&lt;/span&gt;
&lt;span class="c1"&gt;# В 2020 году эта опция противопоказана.&lt;/span&gt;
$ pipenv --two
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;После выполнения одной из этих команд, &lt;code&gt;pipenv&lt;/code&gt; создал файл &lt;code&gt;Pipfile&lt;/code&gt; и
виртуальное окружение где-то в заранее определенной директории
(по умолчанию вне директории проекта).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ cat Pipfile
&lt;span class="o"&gt;[[&lt;/span&gt;source&lt;span class="o"&gt;]]&lt;/span&gt;
&lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;pypi&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://pypi.org/simple&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;verify_ssl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;dev-packages&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;packages&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;requires&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;python_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;3.8&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Это минимальный образец &lt;code&gt;Pipfile&lt;/code&gt;. В секции &lt;code&gt;[[source]]&lt;/code&gt; перечисляются
индексы пакетов — сейчас тут только PyPI, но может быть и ваш
собственный индекс пакетов. В секциях &lt;code&gt;[packages]&lt;/code&gt; и &lt;code&gt;[dev-packages]&lt;/code&gt;
перечисляются зависимости приложения — те, которые нужны для непосредственной
работы приложения (минимум), и те, которые нужны для разработки (запуск
тестов, линтеры и прочее). В секции &lt;code&gt;[requires]&lt;/code&gt; указана версия интерпретатора,
на которой данное приложение может работать.&lt;/p&gt;
&lt;p&gt;Очень полезно правильно разделять зависимости на "основные" и "разработческие".
Это позволит уменьшить размер окружения при развёртывании на
продакшн (например, размер Docker-образа). Кроме того, чем меньше в системе,
работающей на продакшне, установлено пакетов, тем меньше потенциальных уязвимостей.&lt;/p&gt;
&lt;p&gt;Если вам нужно узнать, где именно &lt;code&gt;pipenv&lt;/code&gt; создал виртуальное окружение
(например, для настройки IDE), то сделать это можно вот так:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv --py
/Users/and-semakin/.local/share/virtualenvs/pipenv_demo-1dgGUSFy/bin/python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Управление зависимостями через &lt;code&gt;pipenv&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Теперь давайте установим в проект первую зависимость. Делается
это при помощи команды &lt;code&gt;pipenv install&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv install requests
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Давайте посмотрим, что поменялось в &lt;code&gt;Pipfile&lt;/code&gt; (здесь и дальше я
буду сокращать вывод команд или содержимое файлов при помощи &lt;code&gt;...&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ cat Pipfile
...

&lt;span class="o"&gt;[&lt;/span&gt;packages&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;*&amp;quot;&lt;/span&gt;

...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;В секцию &lt;code&gt;[packages]&lt;/code&gt; добавилась зависимость &lt;code&gt;requests&lt;/code&gt; с версией &lt;code&gt;*&lt;/code&gt;
(версия не фиксирована).&lt;/p&gt;
&lt;p&gt;А теперь давайте установим зависимость, которая нужна для разработки,
например, восхитительный &lt;a href="https://flake8.pycqa.org/"&gt;линтер &lt;code&gt;flake8&lt;/code&gt;&lt;/a&gt;,
передав флаг &lt;code&gt;--dev&lt;/code&gt; в ту же команду &lt;code&gt;install&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv install --dev flake8

$ cat Pipfile
...

&lt;span class="o"&gt;[&lt;/span&gt;dev-packages&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;flake8&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;*&amp;quot;&lt;/span&gt;

...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Теперь можно увидеть всё дерево зависимостей проекта при помощи команды
&lt;code&gt;pipenv graph&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv graph
&lt;span class="nv"&gt;flake8&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.7.9
  - entrypoints &lt;span class="o"&gt;[&lt;/span&gt;required: &amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.3.0,&amp;lt;&lt;span class="m"&gt;0&lt;/span&gt;.4.0, installed: &lt;span class="m"&gt;0&lt;/span&gt;.3&lt;span class="o"&gt;]&lt;/span&gt;
  - mccabe &lt;span class="o"&gt;[&lt;/span&gt;required: &amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.6.0,&amp;lt;&lt;span class="m"&gt;0&lt;/span&gt;.7.0, installed: &lt;span class="m"&gt;0&lt;/span&gt;.6.1&lt;span class="o"&gt;]&lt;/span&gt;
  - pycodestyle &lt;span class="o"&gt;[&lt;/span&gt;required: &amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.5.0,&amp;lt;&lt;span class="m"&gt;2&lt;/span&gt;.6.0, installed: &lt;span class="m"&gt;2&lt;/span&gt;.5.0&lt;span class="o"&gt;]&lt;/span&gt;
  - pyflakes &lt;span class="o"&gt;[&lt;/span&gt;required: &amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.1.0,&amp;lt;&lt;span class="m"&gt;2&lt;/span&gt;.2.0, installed: &lt;span class="m"&gt;2&lt;/span&gt;.1.1&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.23.0
  - certifi &lt;span class="o"&gt;[&lt;/span&gt;required: &amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2017&lt;/span&gt;.4.17, installed: &lt;span class="m"&gt;2020&lt;/span&gt;.4.5.1&lt;span class="o"&gt;]&lt;/span&gt;
  - chardet &lt;span class="o"&gt;[&lt;/span&gt;required: &amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.0.2,&amp;lt;&lt;span class="m"&gt;4&lt;/span&gt;, installed: &lt;span class="m"&gt;3&lt;/span&gt;.0.4&lt;span class="o"&gt;]&lt;/span&gt;
  - idna &lt;span class="o"&gt;[&lt;/span&gt;required: &amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.5,&amp;lt;&lt;span class="m"&gt;3&lt;/span&gt;, installed: &lt;span class="m"&gt;2&lt;/span&gt;.9&lt;span class="o"&gt;]&lt;/span&gt;
  - urllib3 &lt;span class="o"&gt;[&lt;/span&gt;required: &amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.21.1,&amp;lt;&lt;span class="m"&gt;1&lt;/span&gt;.26,!&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.25.1,!&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.25.0, installed: &lt;span class="m"&gt;1&lt;/span&gt;.25.9&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Это бывает полезно, чтобы узнать, что от чего зависит, или почему в вашем
виртуальном окружении есть определённый пакет.&lt;/p&gt;
&lt;p&gt;Также, пока мы устанавливали пакеты, &lt;code&gt;pipenv&lt;/code&gt; создал &lt;code&gt;Pipfile.lock&lt;/code&gt;,
но этот файл длинный и не интересный, поэтому показывать содержимое я не буду.&lt;/p&gt;
&lt;p&gt;Удаление и обновление зависимостей происходит при помощи команд
&lt;code&gt;pipenv uninstall&lt;/code&gt; и &lt;code&gt;pipenv update&lt;/code&gt; соответственно. Работают они довольно
интуитивно, но если возникают вопросы, то вы всегда можете получить
справку при помощи флага &lt;code&gt;--help&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv uninstall --help
$ pipenv update --help
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Управление виртуальными окружениями&lt;/h3&gt;
&lt;p&gt;Давайте удалим созданное виртуальное окружение:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv --rm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;И представим себя в роли другого разработчика, который только присоединился
к вашему проекту. Чтобы создать виртуальное окружение и установить
в него зависимости нужно выполнить следующую команду:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv sync --dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Эта команда на основе &lt;code&gt;Pipfile.lock&lt;/code&gt; воссоздаст точно то же самое виртуальное
окружение, что и у других разработчиков проекта.&lt;/p&gt;
&lt;p&gt;Если же вам не нужны dev-зависимости (например, вы разворачиваете ваш
проект на продакшн), то можно не передавать флаг &lt;code&gt;--dev&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv sync
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Чтобы "войти" внутрь виртуального окружения, нужно выполнить:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv shell
&lt;span class="o"&gt;(&lt;/span&gt;pipenv_demo&lt;span class="o"&gt;)&lt;/span&gt; $
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;В этом режиме будут доступны все установленные пакеты, а имена &lt;code&gt;python&lt;/code&gt; и &lt;code&gt;pip&lt;/code&gt;
будут указывать на соответствующие программы внутри виртуального окружения.&lt;/p&gt;
&lt;p&gt;Есть и другой способ запускать что-то внутри виртуального окружения без
создания нового шелла:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# это запустит REPL внутри виртуального окружения&lt;/span&gt;
$ pipenv run python

&lt;span class="c1"&gt;# а вот так можно запустить какой-нибудь файл&lt;/span&gt;
$ pipenv run python script.py

&lt;span class="c1"&gt;# а так можно получить список пакетов внутри виртуального окружения&lt;/span&gt;
$ pipenv run pip freeze
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Переменные окружения&lt;/h3&gt;
&lt;p&gt;Согласно идеологии 12-факторных приложений, конфигурацию принято хранить
отдельно от кода, а &lt;a href="https://12factor.net/ru/config"&gt;лучше всего конфигурацию вообще
хранить в переменных окружения&lt;/a&gt;
(environment variables или env vars). Чтобы упростить работу с
переменными окружения в процессе разработки, широкое айти-сообщество придумало
сохранять их в специальный файл &lt;code&gt;.env&lt;/code&gt; и загружать в шелл по мере
необходимости. Такие файлы используются во множестве фреймворков,
инструментов и экосистем.
&lt;code&gt;pipenv&lt;/code&gt; упрощает работу с переменными окружения в Python-проектах.&lt;/p&gt;
&lt;p&gt;Давайте создадим файл &lt;code&gt;.env&lt;/code&gt; и запишем туда какое-нибудь значение:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;SECRET_VALUE=hello pipenv!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;ВАЖНО:&lt;/strong&gt; файл &lt;code&gt;.env&lt;/code&gt; может содержать пароли для подключения к СУБД
или токены для доступа к внешним сервисам. Такие данные &lt;strong&gt;никогда&lt;/strong&gt; не должны
попадать в git.&lt;/p&gt;
&lt;p&gt;Давайте напишем небольшой скрипт (&lt;code&gt;script.py&lt;/code&gt;), который будет использовать эту
переменную окружения:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Secret value:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SECRET_VALUE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Попробуем запустить его без использования &lt;code&gt;pipenv&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ python script.py
Secret value: None
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Скрипт вместо секретного значения вывел &lt;code&gt;None&lt;/code&gt;, потому что переменная
окружения так и осталась просто лежать в файле, и никак не повлияла на
работу скрипта. А теперь запустим этот же скрипт через &lt;code&gt;pipenv&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv run python script.py
Loading .env environment variables…
Secret value: hello pipenv!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;pipenv&lt;/code&gt; увидел файл &lt;code&gt;.env&lt;/code&gt; и автоматически загрузил переменные из него.
Скрипт вывел то значение, которое мы и ожидали увидеть. Команда
&lt;code&gt;pipenv shell&lt;/code&gt; тоже подгружает переменные окружения из файла.&lt;/p&gt;
&lt;h3&gt;Запуск скриптов&lt;/h3&gt;
&lt;p&gt;Часто в процессе разработки встречаются повторяющиеся задачи. Если вы
работаете в команде, то ваши коллеги наверняка тоже с ними сталкиваются.
Было бы разумно сохранить/задокументировать где-то команды, нужные
для решения этих повторяющихся задач, чтобы их было проще найти и чтобы
они всегда выполнялись одинаково. Можно, конечно, использовать обычные
&lt;code&gt;.sh&lt;/code&gt; файлы, но у &lt;code&gt;pipenv&lt;/code&gt; тоже есть инструмент, который может в
этом помочь, и даже лучше.&lt;/p&gt;
&lt;p&gt;Допустим, что вы регулярно запускаете проверку кода &lt;code&gt;flake8&lt;/code&gt;, но с
указанием дополнительных флагов, например, вам не нужно проверять
определенную директорию, а так же вы хотите пропускать один вид ошибок
(правильнее было бы просто сохранить эти параметры в конфигурационный файл,
но примера ради будем передавать всё через командную строку):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ flake8 --exclude&lt;span class="o"&gt;=&lt;/span&gt;tests --ignore&lt;span class="o"&gt;=&lt;/span&gt;E121 .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Отредактируем &lt;code&gt;Pipfile&lt;/code&gt;, создав там секцию &lt;code&gt;[scripts]&lt;/code&gt; со следующим
содержимым:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[scripts]&lt;/span&gt;
&lt;span class="n"&gt;lint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;flake8 --exclude=tests --ignore=E121 .&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Теперь тот же самый скрипт можно запустить при помощи команды:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv run lint
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;В качестве бонуса &lt;code&gt;pipenv&lt;/code&gt; автоматически подгрузит переменные окружения,
так что таким же образом можно выполнять и скрипты, которые зависят от
конфигурации проекта (миграции БД, очистки кэшей, удаление временных файлов,
да что угодно).&lt;/p&gt;
&lt;h2&gt;Распространённые проблемы&lt;/h2&gt;
&lt;p&gt;Перечислю проблемы, с которыми я сталкивался в процессе работы с &lt;code&gt;pipenv&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Лишние зависимости в виртуальном окружении&lt;/h3&gt;
&lt;p&gt;Бывает, что кроме перечисленных в &lt;code&gt;Pipfile&lt;/code&gt; и &lt;code&gt;Pipfile.lock&lt;/code&gt; зависимостей
в виртуальном окружении установлены и другие пакеты. Такое может случиться,
например, при переключении между ветками в git, где в &lt;code&gt;Pipfile.lock&lt;/code&gt;
находятся разные зависимости. Или, банально, если внутри виртуального
окружения вы установите что-то через &lt;code&gt;pip&lt;/code&gt; помимо &lt;code&gt;pipenv&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Чаще всего вам будет безразлично, есть в виртуальном окружении какие-то
лишние пакеты или нет, но иногда такие лишние пакеты влияют на работу
приложения. Пример из моей практики: &lt;a href="https://orator-orm.com/"&gt;ORM &lt;code&gt;orator&lt;/code&gt;&lt;/a&gt;
будет использовать тот драйвер для подключения к MySQL, &lt;a href="https://github.com/sdispater/orator/blob/0.9/orator/connectors/mysql_connector.py#L7"&gt;который первым найдёт&lt;/a&gt;
в виртуальном окружении, поэтому если вы хотите использовать &lt;a href="https://github.com/PyMySQL/PyMySQL"&gt;&lt;code&gt;pymysql&lt;/code&gt;&lt;/a&gt;,
то нужно убедиться, что в виртуальном окружении нет &lt;a href="https://github.com/PyMySQL/mysqlclient-python"&gt;&lt;code&gt;MySQLdb&lt;/code&gt;&lt;/a&gt; (он приоритетнее).&lt;/p&gt;
&lt;p&gt;Нужно учитывать, что команда &lt;code&gt;pipenv sync --dev&lt;/code&gt; только доустанавливает пакеты
в виртуальное окружение, но не удаляет оттуда уже установленные. Поэтому, если
вам нужно обеспечить отсутствие в виртуальном окружении лишних пакетов, то
приходится удалять его полностью и создавать заново:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv --rm &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pipenv sync --dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Пререлизные зависимости&lt;/h3&gt;
&lt;p&gt;По умолчанию &lt;code&gt;pipenv&lt;/code&gt; игнорирует нестабильные альфа- и бета-версии пакетов,
и устанавливает только стабильные. Может случиться так, что вам нужно
установить пререлизную версию пакета, например, автоформаттер &lt;a href="https://github.com/psf/black"&gt;&lt;code&gt;black&lt;/code&gt;&lt;/a&gt;,
который на данный момент всё ещё не имеет стабильных релизов вообще:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv install --dev black
...
  Hint: try $ pipenv lock --pre &lt;span class="k"&gt;if&lt;/span&gt; it is a pre-release dependency.
ERROR: ERROR: Could not find a version that matches black
Skipped pre-versions: &lt;span class="m"&gt;18&lt;/span&gt;.3a0, &lt;span class="m"&gt;18&lt;/span&gt;.3a0, &lt;span class="m"&gt;18&lt;/span&gt;.3a1, &lt;span class="m"&gt;18&lt;/span&gt;.3a1, &lt;span class="m"&gt;18&lt;/span&gt;.3a2, &lt;span class="m"&gt;18&lt;/span&gt;.3a2, &lt;span class="m"&gt;18&lt;/span&gt;.3a3, &lt;span class="m"&gt;18&lt;/span&gt;.3a3, &lt;span class="m"&gt;18&lt;/span&gt;.3a4, &lt;span class="m"&gt;18&lt;/span&gt;.3a4, &lt;span class="m"&gt;18&lt;/span&gt;.4a0, &lt;span class="m"&gt;18&lt;/span&gt;.4a0, &lt;span class="m"&gt;18&lt;/span&gt;.4a1, &lt;span class="m"&gt;18&lt;/span&gt;.4a1, &lt;span class="m"&gt;18&lt;/span&gt;.4a2, &lt;span class="m"&gt;18&lt;/span&gt;.4a2, &lt;span class="m"&gt;18&lt;/span&gt;.4a3, &lt;span class="m"&gt;18&lt;/span&gt;.4a3, &lt;span class="m"&gt;18&lt;/span&gt;.4a4, &lt;span class="m"&gt;18&lt;/span&gt;.4a4, &lt;span class="m"&gt;18&lt;/span&gt;.5b0, &lt;span class="m"&gt;18&lt;/span&gt;.5b0, &lt;span class="m"&gt;18&lt;/span&gt;.5b1, &lt;span class="m"&gt;18&lt;/span&gt;.5b1, &lt;span class="m"&gt;18&lt;/span&gt;.6b0, &lt;span class="m"&gt;18&lt;/span&gt;.6b0, &lt;span class="m"&gt;18&lt;/span&gt;.6b1, &lt;span class="m"&gt;18&lt;/span&gt;.6b1, &lt;span class="m"&gt;18&lt;/span&gt;.6b2, &lt;span class="m"&gt;18&lt;/span&gt;.6b2, &lt;span class="m"&gt;18&lt;/span&gt;.6b3, &lt;span class="m"&gt;18&lt;/span&gt;.6b3, &lt;span class="m"&gt;18&lt;/span&gt;.6b4, &lt;span class="m"&gt;18&lt;/span&gt;.6b4, &lt;span class="m"&gt;18&lt;/span&gt;.9b0, &lt;span class="m"&gt;18&lt;/span&gt;.9b0, &lt;span class="m"&gt;19&lt;/span&gt;.3b0, &lt;span class="m"&gt;19&lt;/span&gt;.3b0, &lt;span class="m"&gt;19&lt;/span&gt;.10b0, &lt;span class="m"&gt;19&lt;/span&gt;.10b0
There are incompatible versions in the resolved dependencies.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Команда завершилась ошибкой, но &lt;code&gt;pipenv&lt;/code&gt; предлагает воспользоваться опцией
&lt;code&gt;--pre&lt;/code&gt;, чтобы установить пререлизную зависимость. &lt;strong&gt;Избегайте искушения
сделать так.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Что произойдёт, если всё-таки рискнуть:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv install --dev --pre black
...
✔ Installation Succeeded
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;На первый взгляд, всё хорошо. Но давайте заглянем в &lt;code&gt;Pipfile&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ cat Pipfile
...
&lt;span class="o"&gt;[&lt;/span&gt;pipenv&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;allow_prereleases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Там появилась директива &lt;code&gt;allow_prereleases = true&lt;/code&gt;, которая глобально меняет
поведение &lt;code&gt;pipenv&lt;/code&gt; и разрешает ему устанавливать пререлизные версии
вообще любых зависимостей, а не только той, которую вы хотели установить.
Если у вас в &lt;code&gt;Pipfile&lt;/code&gt; не ограничены версии зависимостей (как у &lt;code&gt;requests = "*"&lt;/code&gt;),
то следующий запуск &lt;code&gt;pipenv install&lt;/code&gt; или &lt;code&gt;pipenv update&lt;/code&gt; может принести
в ваш проект кучу нестабильных зависимостей. Не факт, что приложение
это переживёт.&lt;/p&gt;
&lt;p&gt;Чтобы установить пререлизную зависимость правильно, нужно указать
конкретную версию:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv install --dev &lt;span class="nv"&gt;black&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;.10b0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Если же вы уже попались в эту ловушку &lt;code&gt;pipenv&lt;/code&gt;, то просто отредактируйте
&lt;code&gt;Pipfile&lt;/code&gt; и либо удалите оттуда директиву &lt;code&gt;allow_prereleases&lt;/code&gt; вообще,
либо поменяйте значение на &lt;code&gt;false&lt;/code&gt;. После этого можно спать спокойно.&lt;/p&gt;
&lt;h3&gt;Мердж-конфликты в &lt;code&gt;Pipfile.lock&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Когда в двух параллельных ветках происходит установка или обновление
пакетов, либо просто редактируется &lt;code&gt;Pipfile&lt;/code&gt;, то при слиянии этих веток
обязательно произойдет конфликт в &lt;code&gt;Pipfile.lock&lt;/code&gt;. Git добавит в этот файл
маркеры конфликтов, после чего, само собой, он перестает быть валидным JSON.
В таких случаях &lt;code&gt;pipenv&lt;/code&gt; просто превращается в тыкву и ничего не может сделать:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv sync --dev
...
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line &lt;span class="m"&gt;3&lt;/span&gt; column &lt;span class="m"&gt;8&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;char &lt;span class="m"&gt;24&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;План выхода из такой ситуации следующий:
1. Не пытайтесь осознанно решать конфликты в &lt;code&gt;Pipfile.lock&lt;/code&gt; вручную, всё равно
не сможете; &lt;code&gt;pipenv&lt;/code&gt; сам создал этот файл, вот пусть сам и разбирается.
2. Разрешите конфликт в любую сторону, главное, чтобы в итоге получился
валидный JSON.
3. Пересоздайте &lt;code&gt;Pipfile.lock&lt;/code&gt; заново:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pipenv lock --keep-outdated
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Флаг &lt;code&gt;--keep-outdated&lt;/code&gt; позволяет избежать лишних обновлений версий — вы
ведь просто хотите разрешить конфликты, а не обновить все пакеты, верно?
Тем не менее, &lt;code&gt;pipenv&lt;/code&gt; может вас проигнорировать, и всё равно обновить
некоторые пакеты, будьте к этому готовы (это &lt;a href="https://github.com/pypa/pipenv/issues/3517"&gt;известный баг&lt;/a&gt;).&lt;/p&gt;
&lt;h2&gt;Статус проекта: пациент скорее мертв, чем жив, но надежда есть&lt;/h2&gt;
&lt;p&gt;Стоит отметить, что после &lt;a href="https://vorpus.org/blog/why-im-not-collaborating-with-kenneth-reitz/"&gt;какой-то драмы в сообществе&lt;/a&gt;,
изначальный автор (Kenneth Reitz) покинул проект (и вообще все свои проекты),
и проект перешёл в общественное достояние.
Любые такие конфликты всегда плохо сказываются на успехе проекта, и &lt;code&gt;pipenv&lt;/code&gt;,
определенно, переживает сейчас не лучшие времена.
На данный момент последний релиз был 26 ноября 2018 года.
За полтора года накопилось большое количество незарелиженных баг-фиксов,
что говорит о проблемах с поддержкой проекта.&lt;/p&gt;
&lt;p&gt;Несмотря на это, я всё равно рекомендую присмотреться к &lt;code&gt;pipenv&lt;/code&gt;, потому что
он действительно хорош. Недавно проект стал проявлять
&lt;a href="https://github.com/pypa/pipenv/issues/3369"&gt;признаки жизни&lt;/a&gt;,
и я очень надеюсь, что всё с ним будет хорошо. По-моему, это очень
важный для экосистемы Python проект.&lt;/p&gt;
&lt;p&gt;Обновление от 30 мая 2020: &lt;code&gt;pipenv&lt;/code&gt; наконец выпустил
&lt;a href="https://pypi.org/project/pipenv/#history"&gt;долгожданный релиз &lt;code&gt;2020.5.28&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Обновляемся:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pip install --user --upgrade pipenv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Проект будет жить!&lt;/p&gt;
&lt;h2&gt;Заключение&lt;/h2&gt;
&lt;p&gt;Вместо заключения оставлю вас поразмышлять над вот этой программой:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;use_pipenv&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;know_common_workflows&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;distinguish_between_main_and_dev_dependencies&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;use_dot_env_file&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;use_scripts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;know_pitfalls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;PROFIT!!!!!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;work_on_application&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;use_pipenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;work_on_library&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;use_poetry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;wtf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;use_pip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Дополнительное чтение&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/pypa/pipenv"&gt;Исходный код &lt;code&gt;pipenv&lt;/code&gt;&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pipenv.pypa.io/en/latest/"&gt;Официальная документация&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://realpython.com/pipenv-guide/"&gt;Гайд на RealPython&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/GBQAKldqgZs"&gt;Kenneth Reitz - Pipenv: The Future of Python Dependency Management - PyCon 2018&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://packaging.python.org/tutorials/managing-dependencies/"&gt;Managing Application Dependencies Tutorial&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Подпишитесь!&lt;/h2&gt;
&lt;p&gt;Чтобы получить уведомление о новом посте можно:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://t.me/pythonic_attacks"&gt;подписаться на канал в Telegram&lt;/a&gt;
(&lt;a href="https://tele.click/pythonic_attacks"&gt;альтернативная&lt;/a&gt; &lt;a href="https://tele.gg/pythonic_attacks"&gt;ссылка&lt;/a&gt;);&lt;/li&gt;
&lt;li&gt;&lt;a href="/feeds/all.atom.xml"&gt;подписаться на Atom-фид&lt;/a&gt;, если &lt;s&gt;вы олдфаг-старовер&lt;/s&gt; вам так удобно.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Обложка: &lt;a href="https://commons.wikimedia.org/wiki/File:Victorinox_Swiss_Army_Knife.jpg"&gt;James Case, Victorinox Swiss Army Knife&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="blog"></category><category term="python"></category><category term="pip"></category><category term="virtualenv"></category><category term="venv"></category><category term="pipenv"></category></entry><entry><title>requirements.txt — что это и зачем?</title><link href="https://semakin.dev/2020/04/requirements_txt/" rel="alternate"></link><published>2020-04-22T22:00:00+05:00</published><updated>2020-04-27T13:57:44+05:00</updated><author><name>Андрей Семакин</name></author><id>tag:semakin.dev,2020-04-22:/2020/04/requirements_txt/</id><summary type="html">&lt;p&gt;Расскажу, как создавать и как пользоваться этим файлом для хранения дерева зависимостей проекта.&lt;/p&gt;</summary><content type="html">&lt;h1&gt;requirements.txt&lt;/h1&gt;
&lt;p&gt;В исходниках множества Python-проектов можно встретить этот
странный текстовый файл. Например, им пользуются
&lt;a href="https://github.com/urllib3/urllib3/blob/master/dev-requirements.txt"&gt;urllib3&lt;/a&gt;,
&lt;a href="https://github.com/numpy/numpy/blob/master/test_requirements.txt"&gt;numpy&lt;/a&gt;,
&lt;a href="https://github.com/pandas-dev/pandas/blob/master/requirements-dev.txt"&gt;pandas&lt;/a&gt;,
&lt;a href="https://gitlab.com/pycqa/flake8/-/blob/master/dev-requirements.txt"&gt;flake8&lt;/a&gt;
и куча других проектов.
Давайте разберемся, что это такое, как этим пользоваться и зачем нам это нужно.&lt;/p&gt;
&lt;h2&gt;Гипотетическая предыстория&lt;/h2&gt;
&lt;p&gt;Давайте представим, что вы написали &lt;em&gt;замечательный&lt;/em&gt; скрипт,
который спрашивает у пользователя название города и
выводит текущую температуру и общее состояние погоды:&lt;/p&gt;
&lt;script src="https://gist.github.com/and-semakin/1e999af221df36afbeec5909a413081a.js?file=weather.py"&gt;&lt;/script&gt;

&lt;p&gt;Скрипт получился настолько хорош, что вы хотите поделиться
им со всеми своими друзьями. К сожалению, друзья при попытке
запустить вашу программу получают следующую ошибку:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Traceback (most recent call last):
  File &amp;quot;weather.py&amp;quot;, line 3, in &amp;lt;module&amp;gt;
    from pyowm import OWM
ModuleNotFoundError: No module named &amp;#39;pyowm&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Кажется, что скинуть только код недостаточно.&lt;/p&gt;
&lt;p&gt;Или, допустим, что вы сами через полгода-год попытаетесь
запустить эту же программу. За это время вы успели пару
раз переустановить Python, переустановить ОС, отформатировать свой
магнитный накопитель (используйте SSD — нет, я серьёзно!)
или может быть вообще сменили компьютер.
Почти уверен, что при запуске скрипта вы получите ровно ту же самую ошибку.&lt;/p&gt;
&lt;p&gt;Зачастую, когда мы пишем код, мы полагаемся на какие-либо
библиотеки или фреймворки. Это со всех сторон хорошо — это удобно,
уменьшает размер программы во много раз, позволяет не думать о мелких деталях,
а решать свою конкретную задачу, опираясь на высокоуровневые абстракции.
Но, к сожалению, есть "но" — такие библиотеки становятся
частью вашей программы, ваш код становится &lt;em&gt;зависим&lt;/em&gt;.
Это значит, что ваш код больше не сможет
работать сам по себе, для его работы должны быть установлены &lt;em&gt;все зависимости&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Если ваша программа небольшая (состоит из пары файлов), то можно
довольно легко просмотреть её глазами, найти там все инструкции &lt;code&gt;import&lt;/code&gt;,
отсеять из них импорты из стандартной библиотеки
(вы ведь знаете модули стандартной библиотеки наизусть, да?), и таким образом
восстановить список внешних зависимостей программы, которые устанавливаются
через &lt;code&gt;pip&lt;/code&gt;. Чем крупнее проект, тем сложнее это сделать. Бывают ситуации,
когда по коду вообще нельзя понять, что ему нужна определенная зависимость.&lt;/p&gt;
&lt;p&gt;Я хочу сказать, что намного мудрее составлять этот список зависимостей
сразу же и просто поддерживать его в актуальном состоянии по мере развития проекта.&lt;/p&gt;
&lt;h2&gt;requirements.txt — это список внешних зависимостей&lt;/h2&gt;
&lt;p&gt;Сообщество Python &lt;a href="https://www.python.org/dev/peps/pep-0020/"&gt;исповедует идеологию&lt;/a&gt;
"простое лучше, чем сложное".
Наверное, поэтому для хранения списка зависимостей сообщество выбрало
самый простой из возможных форматов — текстовый файл, где на каждой
строке перечислено ровно по одной зависимости.&lt;/p&gt;
&lt;p&gt;Стоит отметить, что &lt;code&gt;requirements.txt&lt;/code&gt; не является стандартом, т.е. нет
документа, который описывал бы требования к этому файлу.
Скорее, это просто распространённая практика в сообществе, которая,
наверное, возникла спонтанно и хорошо прижилась.&lt;/p&gt;
&lt;p&gt;Не обязательно называть файл именно &lt;code&gt;requirements.txt&lt;/code&gt;, можно назвать
его как угодно, лишь бы его назначение оставалось понятно.
Я встречал такие вариации, как
&lt;code&gt;requirements-dev.txt&lt;/code&gt;, &lt;code&gt;test-requirements.txt&lt;/code&gt;, &lt;code&gt;requirements/docs.txt&lt;/code&gt;
и другие.&lt;/p&gt;
&lt;p&gt;Вот пример самого простого такого файла (кстати, именно этим файлом
можно описать зависимости, которые нужны для запуска нашего скрипта
с погодой):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pyowm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Если бы было несколько зависимостей, то файл выглядел бы так:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;numpy
requests
pytest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Можно указать конкретную версию зависимости. Если версия не указана, то
считается, что нужна последняя доступная:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;numpy  # нужна последняя версия
requests==2.23.0  # нужна конкретная версия
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Можно указывать диапазоны и другие более сложные &lt;a href="https://www.python.org/dev/peps/pep-0440/#version-specifiers"&gt;"спецификаторы версий"&lt;/a&gt;.
В целом, в &lt;code&gt;requirements.txt&lt;/code&gt; можно писать любые "запросы", которые понимает команда
&lt;code&gt;pip install&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;requests&amp;gt;=2.20.0,&amp;lt;2.23.0  # подойдет любая версия между 2.20.0 и 2.23.0
numpy!=1.0  # подойдет любая версия, кроме 1.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Как пользоваться&lt;/h2&gt;
&lt;p&gt;Команда &lt;code&gt;pip install&lt;/code&gt; умеет читать такие файлы, если передать специальный флаг:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pip install --help
...
Install Options:
  -r, --requirement &amp;lt;file&amp;gt;    Install from the given requirements file. This option can be used multiple times.
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Таким образом, если &lt;code&gt;requirements.txt&lt;/code&gt; будет иметь вот такое содержимое:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pytest
flake8
black
isort
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;То следующие две команды будут иметь одинаковое действие:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pip install -r requirements.txt

$ pip install pytest flake8 black isort
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Преимущества использования &lt;code&gt;requirements.txt&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Краткость.&lt;/p&gt;
&lt;p&gt;На таком маленьком примере разница может быть не очевидна, но
когда список зависимостей разрастётся до определенного размера,
то вам не захочется больше перечислять его в &lt;code&gt;pip install&lt;/code&gt; напрямую.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Стабильность.&lt;/p&gt;
&lt;p&gt;Как бы ни поменялся файл &lt;code&gt;requirements.txt&lt;/code&gt;, команда &lt;code&gt;pip install -r requirements.txt&lt;/code&gt;
не поменяется.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Универсальность.&lt;/p&gt;
&lt;p&gt;Так как это распространённое соглашение, то другим разработчикам
будет достаточно увидеть этот файл, чтобы понять, что нужно сделать. Это
здорово экономит время на чтении инструкций.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Как создать&lt;/h2&gt;
&lt;p&gt;Есть два подхода:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;создавать этот файл вручную;&lt;/li&gt;
&lt;li&gt;генерировать автоматически.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Главный принцип ручного подхода — если что-то поменялось в списке зависимостей
(добавилась или удалилась зависимость, обновилась версия и т.д.), это изменение
нужно отразить в &lt;code&gt;requirements.txt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Но можно использовать и встроенную в &lt;code&gt;pip&lt;/code&gt; функциональность:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;env&lt;span class="o"&gt;)&lt;/span&gt; $ pip freeze
&lt;span class="nv"&gt;certifi&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;2020&lt;/span&gt;.4.5.1
&lt;span class="nv"&gt;chardet&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.0.4
&lt;span class="nv"&gt;geojson&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.5.0
&lt;span class="nv"&gt;idna&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.9
&lt;span class="nv"&gt;pyowm&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.10.0
&lt;span class="nv"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;.23.0
&lt;span class="nv"&gt;urllib3&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.25.9
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Команда &lt;code&gt;pip freeze&lt;/code&gt; выводит все установленные в интерпретатор сторонние пакеты.
Заметьте, что в список попали не только прямые зависимости (&lt;code&gt;pyowm&lt;/code&gt;), но
и подзависимости — это даже лучше, потому что вы сможете более
точно воссоздать окружение по этому файлу.&lt;/p&gt;
&lt;p&gt;Можно перенаправить вывод этой команды в файл при помощи стандартного &lt;a href="https://losst.ru/vyvod-v-fajl-bash-v-linux#1_%D0%9F%D0%B5%D1%80%D0%B5%D0%BD%D0%B0%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5_%D1%81%D1%82%D0%B0%D0%BD%D0%B4%D0%B0%D1%80%D1%82%D0%BD%D0%BE%D0%B3%D0%BE_%D0%BF%D0%BE%D1%82%D0%BE%D0%BA%D0%B0_%D0%B2%D1%8B%D0%B2%D0%BE%D0%B4%D0%B0"&gt;консольного
приема&lt;/a&gt;
(работает и на Windows), и получить валидный файл &lt;code&gt;requirements.txt&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;env&lt;span class="o"&gt;)&lt;/span&gt; $ pip freeze &amp;gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Обратите внимание, что &lt;code&gt;pip freeze&lt;/code&gt; выведет список пакетов в том окружении,
в котором он запущен. Не забудьте активировать виртуальное окружение перед
запуском этой команды, иначе получите список пакетов, установленных в глобальный
интерпретатор. Кстати, у меня есть
&lt;a href="https://semakin.dev/2020/04/python_virtualenv/"&gt;пост про виртуальные окружения&lt;/a&gt;, где объясняется
как ими пользоваться.&lt;/p&gt;
&lt;p&gt;Подытожим плюсы и минусы ручного и автоматического подходов:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Вручную:&lt;ul&gt;
&lt;li&gt;минимальный файл, содержащий только прямые зависимости;&lt;/li&gt;
&lt;li&gt;можно указывать сложные спецификаторы версий;&lt;/li&gt;
&lt;li&gt;человеческий фактор — легко ошибиться или забыть что-нибудь;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pip freeze&lt;/code&gt;:&lt;ul&gt;
&lt;li&gt;автоматически, быстро;&lt;/li&gt;
&lt;li&gt;автоматически добавляет версии установленных пакетов;&lt;/li&gt;
&lt;li&gt;в файл попадет всё дерево зависимостей, а не только прямые зависимости.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Можно использовать и смешанный подход: сгенерировать начальную версию файла
при помощи &lt;code&gt;pip freeze&lt;/code&gt; и допилить её затем руками, если у вас какая-то
сложная нестандартная ситуация.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ Файл &lt;code&gt;requirements.txt&lt;/code&gt;, конечно же, должен быть добавлен в систему
контроля версий (git). Это такая же важная часть проекта, как и код.
При этом виртуальное окружение не должно попадать в систему контроля
версий. Оно должно воссоздаваться из &lt;code&gt;requirements.txt&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Проблемы &lt;code&gt;requirements.txt&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Некоторые пакеты часто меняются, поэтому если вы не указываете конкретные
версии, то в следующий раз при установке вы можете получить совсем другую
среду. Это бывает особенно обидно, когда локально на машине разработчика
всё работает правильно, но при деплое на боевой сервер программа либо работает
иначе, либо вообще отказывается запускаться. Поэтому обязательно фиксируйте
версии пакетов в &lt;code&gt;requirements.txt&lt;/code&gt; — это сделает разные окружения хотя бы
примерно похожими.&lt;/p&gt;
&lt;p&gt;Почему "хотя бы примерно"? Практика показывает, что зафиксировать версию
пакета недостаточно. Иногда случается, что под одной версией пакета в разное
время может находиться совершенно разный код. PyPI, конечно, не позволит
перезаписать уже опубликованную версию, но, например, ваш приватный
корпоративный индекс пакетов может быть не таким строгим.&lt;/p&gt;
&lt;p&gt;Чтобы действительно гарантировать, что вы устанавливаете те пакеты, что и
ожидали, нужно рассчитывать и сверять их контрольные суммы. &lt;code&gt;requirements.txt&lt;/code&gt;
может &lt;a href="https://pip.pypa.io/en/stable/user_guide/#hash-checking-mode"&gt;содержать хэши пакетов&lt;/a&gt;,
но, к сожалению, на данный момент нет
простого стандартного способа как их туда положить, кроме как вручную (сложно).
В качестве альтернативы опять предлагаю присмотреться к таким проектам, как
&lt;a href="https://python-poetry.org/"&gt;&lt;code&gt;poetry&lt;/code&gt;&lt;/a&gt; (хранит хэши в &lt;code&gt;poetry.lock&lt;/code&gt;)
и &lt;a href="https://github.com/pypa/pipenv"&gt;&lt;code&gt;pipenv&lt;/code&gt;&lt;/a&gt; (хранит хэши в &lt;code&gt;Pipfile.lock&lt;/code&gt;), где эта
проблема решена хорошо,
и вам не придётся переживать о воспроизводимости ваших сборок.
Если всё-таки хочется добиться такого же эффекта при помощи
&lt;code&gt;requirements.txt&lt;/code&gt;, то можно посмотреть на такие проекты как
&lt;a href="https://github.com/jazzband/pip-tools"&gt;&lt;code&gt;pip-tools&lt;/code&gt;&lt;/a&gt;
(&lt;a href="https://github.com/pypa/pip/issues/4732#issuecomment-500289841"&gt;пример использования&lt;/a&gt;)
и &lt;a href="https://github.com/peterbe/hashin"&gt;&lt;code&gt;hashin&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Заключение&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;requirements.txt&lt;/code&gt; — это очень популярный способ хранения списка внешних
зависимостей проекта, поэтому вам определенно нужно уметь работать с
такими файлами. Однако этот способ хранения списка зависимостей не лишён
недостатков, поэтому если вы начинаете новый проект, то я предлагаю вам
лучше использовать для этого &lt;a href="https://python-poetry.org/"&gt;&lt;code&gt;poetry&lt;/code&gt;&lt;/a&gt;
или &lt;a href="https://github.com/pypa/pipenv"&gt;&lt;code&gt;pipenv&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Для тренировки можно попытаться запустить скрипт с погодой.
Все исходники лежат &lt;a href="https://gist.github.com/and-semakin/1e999af221df36afbeec5909a413081a"&gt;здесь&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Дополнительное чтение&lt;/h2&gt;
&lt;p&gt;Конечно, я затронул лишь верхушку айсберга. На самом деле,
&lt;code&gt;requirements&lt;/code&gt;-файлы немножко сложнее.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pip.pypa.io/en/stable/user_guide/#requirements-files"&gt;Секция PIP User Guide про requirements.txt&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;А вот тут &lt;a href="https://pip.pypa.io/en/stable/reference/pip_install/#requirements-file-format"&gt;подробное описание формата &lt;code&gt;requirements&lt;/code&gt;-файлов&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;А ещё &lt;a href="https://pip.pypa.io/en/stable/user_guide/#constraints-files"&gt;бывают &lt;code&gt;constraints.txt&lt;/code&gt;&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;Бонус: PyCharm &lt;a href="https://www.jetbrains.com/help/pycharm/managing-dependencies.html"&gt;умеет умно обновлять &lt;code&gt;requirements.txt&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Подпишитесь!&lt;/h2&gt;
&lt;p&gt;Чтобы получить уведомление о новом посте можно:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://t.me/pythonic_attacks"&gt;подписаться на канал в Telegram&lt;/a&gt;
(&lt;a href="https://tele.click/pythonic_attacks"&gt;альтернативная&lt;/a&gt; &lt;a href="https://tele.gg/pythonic_attacks"&gt;ссылка&lt;/a&gt;);&lt;/li&gt;
&lt;li&gt;&lt;a href="/feeds/all.atom.xml"&gt;подписаться на Atom-фид&lt;/a&gt;, если &lt;s&gt;вы олдфаг-старовер&lt;/s&gt; вам так удобно.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Обложка: &lt;a href="https://www.flickr.com/photos/mustangjoe/14675770684"&gt;Joe deSousa, Gnarled Tree&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><category term="blog"></category><category term="python"></category><category term="pip"></category><category term="requirements.txt"></category></entry><entry><title>Виртуальные окружения в Python</title><link href="https://semakin.dev/2020/04/python_virtualenv/" rel="alternate"></link><published>2020-04-18T20:42:16+05:00</published><updated>2020-04-29T13:56:23+05:00</updated><author><name>Андрей Семакин</name></author><id>tag:semakin.dev,2020-04-18:/2020/04/python_virtualenv/</id><summary type="html">&lt;p&gt;Почему всегда нужно использовать виртуальные окружения и как правильно их готовить.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Python знаменит своей обширной стандартной библиотекой и девизом
"батарейки в комплекте"
(&lt;a href="https://docs.python.org/3/tutorial/stdlib.html#batteries-included"&gt;batteries included&lt;/a&gt;).
Даже из коробки Python позволяет удобно и быстро решить огромный пласт задач,
например, например, работа с файлами, запуск простого веб-сервера,
работа с электронной почтой, парсинг XML и JSON, и так далее. Во всяком случае,
это намного удобнее, чем писать shell-скрипты 😅&lt;/p&gt;
&lt;p&gt;Кроме того, у Python имеется огромная экосистема сторонних библиотек,
поддерживаемых сообществом энтузиастов. Эти библиотеки реализуют отсутствующую
в стандартной поставке функциональность, либо пере-реализуют уже имеющуюся, но
удобнее. Если у вас возникла потребность в какой-то функциональности, то
почти наверняка кто-то уже написал для этого библиотеку, и нужно просто погуглить.&lt;/p&gt;
&lt;h2&gt;Установка сторонней библиотеки&lt;/h2&gt;
&lt;p&gt;Каждый начинающий программист знает, как установить библиотеку. Набираем
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pip install requests
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;и понеслась! Множество библиотек в своих инструкциях по установке именно так
и предлагают их устанавливать. Это и правда работает, это и правда так просто,
&lt;em&gt;но есть нюансы&lt;/em&gt;. В этом месте закопаны очень популярные грабли, по которым
прошлось множество начинающих питонистов, в том числе и я.&lt;/p&gt;
&lt;h3&gt;Как &lt;code&gt;pip&lt;/code&gt; устанавливает пакеты&lt;/h3&gt;
&lt;p&gt;Давайте разберемся, что же происходит, когда юзер набирает в терминал такую
команду. В общих чертах происходит следующее.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;pip&lt;/code&gt; обращается в &lt;a href="https://pypi.org/"&gt;PyPI&lt;/a&gt; (Python Package Index) и
находит там запрашиваемый пакет.&lt;/li&gt;
&lt;li&gt;Пакет скачивается. Обычно это просто zip-архив, который содержит
код библиотеки, разложенный внутри согласно формату. Современным и
рекомендуемым форматом пакетов является wheel (&lt;a href="https://www.python.org/dev/peps/pep-0427/"&gt;PEP-427&lt;/a&gt;),
но в дикой природе встречаются и другие форматы.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pip&lt;/code&gt; устанавливает пакет.&lt;/li&gt;
&lt;li&gt;Библиотека установлена, ее можно импортировать и использовать.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Давайте подробнее разберем третий шаг. Установка пакета — звучит загадочно и
сложно, но на самом деле ничего сложного здесь не происходит. &lt;code&gt;pip&lt;/code&gt; просто
распаковывает zip-архив в определенное место (это справедливо для формата wheel,
для установки пакетов в других форматах могут потребоваться дополнительные
действия, но давайте разберем самый распространённый и простой случай). Куда
именно происходит установка? Это можно узнать, выполнив следующую команду:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ python -m site
sys.path &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;/Users/and-semakin&amp;#39;&lt;/span&gt;,
    &lt;span class="s1"&gt;&amp;#39;/Users/and-semakin/.asdf/installs/python/3.8.2/lib/python38.zip&amp;#39;&lt;/span&gt;,
    &lt;span class="s1"&gt;&amp;#39;/Users/and-semakin/.asdf/installs/python/3.8.2/lib/python3.8&amp;#39;&lt;/span&gt;,
    &lt;span class="s1"&gt;&amp;#39;/Users/and-semakin/.asdf/installs/python/3.8.2/lib/python3.8/lib-dynload&amp;#39;&lt;/span&gt;,
    &lt;span class="s1"&gt;&amp;#39;/Users/and-semakin/env/lib/python3.8/site-packages&amp;#39;&lt;/span&gt;,
&lt;span class="o"&gt;]&lt;/span&gt;
USER_BASE: &lt;span class="s1"&gt;&amp;#39;/Users/and-semakin/.local&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;exists&lt;span class="o"&gt;)&lt;/span&gt;
USER_SITE: &lt;span class="s1"&gt;&amp;#39;/Users/and-semakin/.local/lib/python3.8/site-packages&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;doesn&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t exist&lt;span class="o"&gt;)&lt;/span&gt;
ENABLE_USER_SITE: False
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;В списке &lt;code&gt;sys.path&lt;/code&gt; можно увидеть директорию &lt;code&gt;site-packages&lt;/code&gt; — именно туда
и будет установлена библиотека. Давайте в этом убедимся.&lt;/p&gt;
&lt;p&gt;До установки пакета:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ ls -l /Users/and-semakin/env/lib/python3.8/site-packages
total &lt;span class="m"&gt;8&lt;/span&gt;
drwxr-xr-x   &lt;span class="m"&gt;3&lt;/span&gt; and-semakin  awesome    &lt;span class="m"&gt;96&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:39 __pycache__
-rw-r--r--   &lt;span class="m"&gt;1&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;126&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:39 easy_install.py
drwxr-xr-x   &lt;span class="m"&gt;7&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;224&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:39 pip
drwxr-xr-x   &lt;span class="m"&gt;9&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;288&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:39 pip-19.2.3.dist-info
drwxr-xr-x   &lt;span class="m"&gt;7&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;224&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:39 pkg_resources
drwxr-xr-x  &lt;span class="m"&gt;42&lt;/span&gt; and-semakin  awesome  &lt;span class="m"&gt;1344&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:39 setuptools
drwxr-xr-x  &lt;span class="m"&gt;11&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;352&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:39 setuptools-41.2.0.dist-info
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Установим пакет:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pip install requests
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;После установки пакета:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ ls -l /Users/and-semakin/env/lib/python3.8/site-packages
total &lt;span class="m"&gt;8&lt;/span&gt;
drwxr-xr-x   &lt;span class="m"&gt;3&lt;/span&gt; and-semakin  awesome    &lt;span class="m"&gt;96&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:39 __pycache__
drwxr-xr-x   &lt;span class="m"&gt;7&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;224&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:41 certifi
drwxr-xr-x   &lt;span class="m"&gt;8&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;256&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:41 certifi-2020.4.5.1.dist-info
drwxr-xr-x  &lt;span class="m"&gt;43&lt;/span&gt; and-semakin  awesome  &lt;span class="m"&gt;1376&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:41 chardet
drwxr-xr-x  &lt;span class="m"&gt;10&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;320&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:41 chardet-3.0.4.dist-info
-rw-r--r--   &lt;span class="m"&gt;1&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;126&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:39 easy_install.py
drwxr-xr-x  &lt;span class="m"&gt;11&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;352&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:41 idna
drwxr-xr-x   &lt;span class="m"&gt;8&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;256&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:41 idna-2.9.dist-info
drwxr-xr-x   &lt;span class="m"&gt;7&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;224&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:39 pip
drwxr-xr-x   &lt;span class="m"&gt;9&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;288&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:39 pip-19.2.3.dist-info
drwxr-xr-x   &lt;span class="m"&gt;7&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;224&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:39 pkg_resources
drwxr-xr-x  &lt;span class="m"&gt;21&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;672&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:41 requests
drwxr-xr-x   &lt;span class="m"&gt;8&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;256&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:41 requests-2.23.0.dist-info
drwxr-xr-x  &lt;span class="m"&gt;42&lt;/span&gt; and-semakin  awesome  &lt;span class="m"&gt;1344&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:39 setuptools
drwxr-xr-x  &lt;span class="m"&gt;11&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;352&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:39 setuptools-41.2.0.dist-info
drwxr-xr-x  &lt;span class="m"&gt;16&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;512&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:41 urllib3
drwxr-xr-x   &lt;span class="m"&gt;8&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;256&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;17&lt;/span&gt;:41 urllib3-1.25.9.dist-info
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Как видим, в директорию &lt;code&gt;site-packages&lt;/code&gt; добавилась библиотека &lt;code&gt;requests&lt;/code&gt; вместе
со всеми своими зависимостями.&lt;/p&gt;
&lt;p&gt;Важные мысли, которые я пытаюсь донести:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;установка библиотеки напрямую влияет на файловую систему;&lt;/li&gt;
&lt;li&gt;у интерпретатора Python есть &lt;strong&gt;только одна&lt;/strong&gt; директория &lt;code&gt;site-packages&lt;/code&gt;, куда
&lt;code&gt;pip&lt;/code&gt; и устанавливает пакеты.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;А это значит, что в один интерпретатор Python нельзя установить две версии
одной библиотеки одновременно. При установке новой версии предыдущая "перезатирается".
Просто как если бы вы распаковали другой архив с совпадающими именами файлов
в то же самое место.&lt;/p&gt;
&lt;h3&gt;Боль — это жизненный опыт&lt;/h3&gt;
&lt;p&gt;Что же будет, если вам понадобится работать над двумя проектами, которые
будут требовать разных, не совместимых между собой версий одной и той же
библиотеки? Возможно, между этими версиями в библиотеку были внесены
какие-то крупные ломающие изменения, например, переименовались методы/функции
или изменился набор аргументов.&lt;/p&gt;
&lt;p&gt;Например, проект А:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# requirements.txt
requests==2.23.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Проект Б:
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# requirements.txt
requests==1.2.3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Вы просто не сможете работать над такими проектами одновременно. Установка
зависимостей одного проекта сломает другой, и наоборот. При переключении между
проектами придётся каждый раз устанавливать зависимости нужного проекта, что
довольно легко забыть сделать.&lt;/p&gt;
&lt;p&gt;Ситуация кажется маловероятной, но я гарантирую, что рано или поздно это
случится, если устанавливать зависимости всех своих проектов в один интерпретатор.
Всё усугубляется тем фактом, что прямые зависимости вашего проекта тянут за
собой свои зависимости (под-зависимости),
те, в свою очередь, тоже могут от чего-то зависеть (под-под-зависимости). В
итоге вы получаете целое дерево зависимостей. И если где-то в этом дереве
окажется библиотека не той версии, что ожидалось, то весь проект может начать
очень странно работать. Вы получите такие эзотерические ошибки, которых
еще никто в интернете до вас не встречал. Если всё сразу сломалось, то считайте,
что легко отделались — по крайней мере, так довольно просто понять, в чём проблема.
Но бывают и ситуации намного хуже, когда приложение просто начинает &lt;em&gt;немножко&lt;/em&gt;
иначе работать, без каких-либо ошибок, и возможно придется потратить долгие
часы на траблшутинг, чтобы найти настоящую причину.&lt;/p&gt;
&lt;p&gt;Надеюсь, я убедил вас, что устанавливать зависимости нескольких проектов в
один интерпретатор — это очень-очень плохо. Но как же тогда правильно?&lt;/p&gt;
&lt;h2&gt;Виртуальные окружения&lt;/h2&gt;
&lt;p&gt;Решение очевидно — у каждого проекта должен быть свой интерпретатор Python,
со своей собственной изолированной директорией &lt;code&gt;site-packages&lt;/code&gt;. Это и есть
основная идея, стоящая за виртуальными окружениями. Виртуальное окружение —
это самостоятельная копия интерпретатора со своими пакетами.&lt;/p&gt;
&lt;h3&gt;Как создавать виртуальные окружения&lt;/h3&gt;
&lt;p&gt;Начиная с Python версии 3.5 (на данный момент это самая старая из официально
поддерживаемых версий языка, так что справедливо ожидать, что как минимум
везде установлен Python 3.5 или новее), создать виртуальное окружение стало
очень просто:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ python -m venv &amp;lt;путь к виртуальному окружению&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Например, допустим, что мы работаем над проектом &lt;code&gt;blog_source&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# заходим в директорию с проектом&lt;/span&gt;
$ &lt;span class="nb"&gt;cd&lt;/span&gt; src/blog_source

&lt;span class="c1"&gt;# создаем виртуальное окружение прямо рядом с кодом в директории env&lt;/span&gt;
$ python -m venv env
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Создавать виртуальное окружения рядом с кодом — это распространённая практика,
так проще ничего не перепутать, но вообще виртуальное окружение может быть
где угодно, и директория тоже может называться как угодно. Обратите внимание,
что если вы создаете виртуальное окружение в директории под управлением
системы контроля версий (git), то его не нужно коммитить.
Лучше вообще добавьте его (&lt;code&gt;env/&lt;/code&gt;) в &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;В директорию &lt;code&gt;env&lt;/code&gt; будет скопирован тот самый интерпретатор, при помощи
которого виртуальное окружение и создавалось. Т.е. если&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ python -V
Python &lt;span class="m"&gt;3&lt;/span&gt;.8.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;то в виртуальном окружении будет та же самая версия:
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ env/bin/python -V
Python &lt;span class="m"&gt;3&lt;/span&gt;.8.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/p&gt;
&lt;h3&gt;Активируем окружение&lt;/h3&gt;
&lt;p&gt;Посмотрим, что внутри директории &lt;code&gt;env&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ ls -l env/
total &lt;span class="m"&gt;8&lt;/span&gt;
drwxr-xr-x  &lt;span class="m"&gt;13&lt;/span&gt; and-semakin  awesome  &lt;span class="m"&gt;416&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;:55 bin
drwxr-xr-x   &lt;span class="m"&gt;2&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;64&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;:55 include
drwxr-xr-x   &lt;span class="m"&gt;3&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;96&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;:55 lib
-rw-r--r--   &lt;span class="m"&gt;1&lt;/span&gt; and-semakin  awesome  &lt;span class="m"&gt;113&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;:55 pyvenv.cfg

$ ls -l env/bin/
total &lt;span class="m"&gt;88&lt;/span&gt;
-rw-r--r--  &lt;span class="m"&gt;1&lt;/span&gt; and-semakin  awesome  &lt;span class="m"&gt;8471&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;:55 Activate.ps1
-rw-r--r--  &lt;span class="m"&gt;1&lt;/span&gt; and-semakin  awesome  &lt;span class="m"&gt;2218&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;:55 activate
-rw-r--r--  &lt;span class="m"&gt;1&lt;/span&gt; and-semakin  awesome  &lt;span class="m"&gt;1270&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;:55 activate.csh
-rw-r--r--  &lt;span class="m"&gt;1&lt;/span&gt; and-semakin  awesome  &lt;span class="m"&gt;2422&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;:55 activate.fish
-rwxr-xr-x  &lt;span class="m"&gt;1&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;268&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;:55 easy_install
-rwxr-xr-x  &lt;span class="m"&gt;1&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;268&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;:55 easy_install-3.8
-rwxr-xr-x  &lt;span class="m"&gt;1&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;250&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;:55 pip
-rwxr-xr-x  &lt;span class="m"&gt;1&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;250&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;:55 pip3
-rwxr-xr-x  &lt;span class="m"&gt;1&lt;/span&gt; and-semakin  awesome   &lt;span class="m"&gt;250&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;:55 pip3.8
lrwxr-xr-x  &lt;span class="m"&gt;1&lt;/span&gt; and-semakin  awesome    &lt;span class="m"&gt;59&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;:55 python -&amp;gt; /Users/and-semakin/.asdf/installs/python/3.8.2/bin/python
lrwxr-xr-x  &lt;span class="m"&gt;1&lt;/span&gt; and-semakin  awesome     &lt;span class="m"&gt;6&lt;/span&gt; Apr &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt;:55 python3 -&amp;gt; python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Обратите внимание, что в директории &lt;code&gt;bin&lt;/code&gt; есть некий файл &lt;code&gt;activate&lt;/code&gt; в
нескольких вариантах для разных шеллов. Это и есть "точка входа" в виртуальное
окружение. Просто создать виртуальное окружение мало, нужно его активировать.
Но сначала проверим, какие &lt;code&gt;python&lt;/code&gt; и &lt;code&gt;pip&lt;/code&gt; (исполняемые файлы) используются
в обычном режиме работы:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ which python
/Users/and-semakin/.asdf/shims/python
$ which pip
/Users/and-semakin/.asdf/shims/pip
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Это мой обычный Python, вне виртуального окружения, назовём его глобальным.
Теперь активируем виртуальное окружение:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ &lt;span class="nb"&gt;source&lt;/span&gt; env/bin/activate
&lt;span class="o"&gt;(&lt;/span&gt;env&lt;span class="o"&gt;)&lt;/span&gt; $
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Для Windows процесс активации будет отличаться (допустим, что виртуальное окружение
создано в &lt;code&gt;C:\src\blog_source&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# cmd&lt;/span&gt;
C:&lt;span class="se"&gt;\s&lt;/span&gt;rc&lt;span class="se"&gt;\b&lt;/span&gt;log_source&lt;span class="se"&gt;\&amp;gt;&lt;/span&gt; env&lt;span class="se"&gt;\S&lt;/span&gt;cripts&lt;span class="se"&gt;\a&lt;/span&gt;ctivate.bat

&lt;span class="c1"&gt;# либо в PowerShell&lt;/span&gt;
PS C:&lt;span class="se"&gt;\s&lt;/span&gt;rc&lt;span class="se"&gt;\b&lt;/span&gt;log_source&lt;span class="se"&gt;\&amp;gt;&lt;/span&gt; env&lt;span class="se"&gt;\S&lt;/span&gt;cripts&lt;span class="se"&gt;\A&lt;/span&gt;ctivate.ps1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Обратите внимание, что приветствие шелла изменилось (добавилось &lt;code&gt;(env)&lt;/code&gt;), чтобы
показать, что мы "внутри" виртуального окружения под названием &lt;code&gt;env&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Теперь проверим еще раз, какие &lt;code&gt;python&lt;/code&gt; и &lt;code&gt;pip&lt;/code&gt; используются:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;env&lt;span class="o"&gt;)&lt;/span&gt; $ which python
/Users/and-semakin/src/blog_source/env/bin/python
&lt;span class="o"&gt;(&lt;/span&gt;env&lt;span class="o"&gt;)&lt;/span&gt; $ which pip
/Users/and-semakin/src/blog_source/env/bin/pip
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Посмотрите на пути — мы внутри виртуального окружения! Теперь можно смело
устанавливать любые пакеты, и это никак не повлияет на глобальный Python или
на другие виртуальные окружения:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;env&lt;span class="o"&gt;)&lt;/span&gt; $ pip install requests flask whatever-you-need
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Можно запускать любые файлы, и они будут иметь доступ к установленным пакетам:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;env&lt;span class="o"&gt;)&lt;/span&gt; $ python make_money.py
Done! You are rich!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;IDE тоже нужно настроить, указав путь к &lt;code&gt;bin/python&lt;/code&gt; внутри виртуального
окружения, тогда редактор сможет лучше вам помогать.&lt;/p&gt;
&lt;p&gt;По завершению работы с виртуальным окружением можно просто набрать &lt;code&gt;deactivate&lt;/code&gt;,
либо закрыть окно терминала:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;env&lt;span class="o"&gt;)&lt;/span&gt; $ deactivate
$ which python
/Users/and-semakin/.asdf/shims/python
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;И мы видим, что команда &lt;code&gt;python&lt;/code&gt; снова вызывает глобальный интерпретатор.
При этом виртуальное окружение осталось в своей директории, оно просто не
активно. В следующий раз, когда будет нужно поработать с виртуальным
окружением, не забудьте снова его активировать.&lt;/p&gt;
&lt;p&gt;Виртуальное окружение можно полностью удалить, когда оно перестанет быть
нужным:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ rm -r env/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;В идеале, у вас должна быть возможность в любой момент удалить и пересоздать
виртуальное окружение заново, для этого храните список зависимостей проекта и
содержите его в актуальном состоянии (например, в &lt;code&gt;requirements.txt&lt;/code&gt;).
В процессе разработки могут случиться всякие казусы с зависимостями,
и иногда проще пересоздать виртуальное окружение заново, чем пытаться
починить сломанное.&lt;/p&gt;
&lt;p&gt;Вот так можно работать с виртуальными окружениями в Python. Всегда устанавливайте
зависимости проектов только в изолированные виртуальные окружения. Не смешивайте
зависимости разных проектов в одном окружении.&lt;/p&gt;
&lt;p&gt;Когда в инструкции по установке библиотеки написано &lt;code&gt;pip install ...&lt;/code&gt;,
подразумевается, что у читателя есть понимание, что он делает. Думаю,
разработчики библиотек не пишут про создание виртуальных окружений
только потому, что это сильно раздуло бы все инструкции.&lt;/p&gt;
&lt;h2&gt;Ничего не устанавливайте в глобальный интерпретатор&lt;/h2&gt;
&lt;p&gt;Иногда возникает желание установить какой-нибудь пакет прямо в глобальный
интерпретатор, потому что по смыслу этот пакет вроде как должен быть вне
виртуальных окружений. Например, это может быть какая-нибудь программа,
типа &lt;a href="https://python-poetry.org/"&gt;&lt;code&gt;poetry&lt;/code&gt;&lt;/a&gt;,
&lt;a href="https://docs.docker.com/compose/"&gt;&lt;code&gt;docker-compose&lt;/code&gt;&lt;/a&gt;,
&lt;a href="https://github.com/ytdl-org/youtube-dl"&gt;&lt;code&gt;youtube-dl&lt;/code&gt;&lt;/a&gt; или
&lt;a href="https://pypi.org/project/howdoi/"&gt;&lt;code&gt;howdoi&lt;/code&gt;&lt;/a&gt;. Руки набирают в терминал:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pip install poetry
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Установка начинается, прогресс-бары заполняются, но в итоге всё завершается
чем-то типа такого:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;error: could not create &amp;#39;/lib/python2.7/site-packages/poetry&amp;#39;: Permission denied
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Можно попробовать установить, используя &lt;code&gt;sudo&lt;/code&gt;, и это сработает, но это
считается &lt;a href="https://ru.stackoverflow.com/q/651860/340643"&gt;очень плохой практикой&lt;/a&gt;,
и я настоятельно рекомендую так не делать по нескольким причинам:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Угроза безопасности.&lt;/p&gt;
&lt;p&gt;В секции про установку пакетов я упомянул, что для пакетов других форматов,
кроме wheel, могут потребоваться дополнительные действия. На самом деле,
при установке пакета формата &lt;code&gt;sdist&lt;/code&gt; исполняется файл&lt;code&gt;setup.py&lt;/code&gt;, в котором
потенциально могут содержаться любые действия — от честной установки пакета,
до &lt;code&gt;rm -rf /&lt;/code&gt; или установки криптомайнера в систему. Т.к. в PyPI пакет
загрузить может кто угодно, никогда нельзя быть уверенным, что именно сделает пакет
при установке. Выполнять такой скрипт с системными привилегиями (&lt;code&gt;sudo&lt;/code&gt;) —
не самый мудрый ход.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Может нарушить целостность системы.&lt;/p&gt;
&lt;p&gt;Часто в операционных системах принято устанавливать программы через
пакетный менеджер (будь то &lt;code&gt;apt&lt;/code&gt;, &lt;code&gt;dnf&lt;/code&gt; или &lt;code&gt;pacman&lt;/code&gt;). Этот же пакетный
менеджер затем может без следа удалить установленную программу, потому что он
ведёт учёт файлов — какой программе какие файлы принадлежит. Если начать
изменять файлы программ каким-либо образом, помимо пакетного менеджера,
то это может нарушить его работу. &lt;code&gt;pip&lt;/code&gt;, конечно, установит что нужно,
но после этого могут возникнуть проблемы с системным пакетным менеджером.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Как правильно:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;сказать &lt;code&gt;pip&lt;/code&gt;, чтобы он установил пакет не в директорию &lt;code&gt;site-packages&lt;/code&gt;, а в
домашнюю директорию пользователя при помощи флага &lt;code&gt;--user&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ pip install --user poetry  # без sudo!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Обязательно нужно убедиться, что директория, куда установится пакет,
перечислена в переменной &lt;code&gt;$PATH&lt;/code&gt;. Путь к директории можно получить при помощи
следующей команды:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ python -m site --user-base
/Users/and-semakin/.local
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;К получившемуся пути нужно в конце добавить &lt;code&gt;/bin&lt;/code&gt; для Linux и MacOS,
либо &lt;code&gt;\Scripts&lt;/code&gt; для Windows, так что
в моём случае (MacOS) в &lt;code&gt;$PATH&lt;/code&gt; нужно добавить вот такой путь:
&lt;code&gt;/Users/and-semakin/.local/bin&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Подробнее про этот метод установки читайте &lt;a href="https://pip.pypa.io/en/stable/user_guide/#user-installs"&gt;здесь&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;установить программу через пакетный менеджер ОС, например:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ sudo dnf install python-poetry
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Часто мейнтейнеры ОС создают обёртки для пакетов из PyPI, которые можно
установить при помощи системного пакетного менеджера. Как правило, такие
обёртки называются &lt;code&gt;python-&amp;lt;имя пакета&amp;gt;&lt;/code&gt; или &lt;code&gt;python3-&amp;lt;имя пакета&amp;gt;&lt;/code&gt;.
Это делается как раз для того, чтобы дать пользователям возможность
устанавливать Python-программы, не нарушая работу пакетного менеджера ОС.
Кроме того, эти пакеты проходят проверку безопасности, так что риск получить
криптомайнер значительно ниже.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Выводы&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;всегда устанавливайте зависимости проектов в отдельные виртуальные окружения;&lt;/li&gt;
&lt;li&gt;если нужно установить пакет "глобально", то используйте либо &lt;code&gt;pip install --user ...&lt;/code&gt;,
либо прибегните к помощи пакетного менеджера операционной системы;&lt;/li&gt;
&lt;li&gt;никогда не используйте &lt;code&gt;sudo pip ...&lt;/code&gt; — считайте, что это табу.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Да, виртуальные окружения — определенно не самая удобная часть разработки на
Python, и уж точно не самая простая тема, к этому просто нужно привыкнуть.
Несколько раз повторил, выработал привычку — в целом, ничего сложного.
Кроме того, экосистема Python развивается очень быстро, и я надеюсь,
что скоро правильная установка пакетов
и управление виртуальными окружениями станут намного легче.
Уже сейчас можно пользоваться такими инструментами,
которые в некоторой мере прячут от пользователя виртуальные окружения:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://python-poetry.org/"&gt;poetry&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pypa/pipenv"&gt;pipenv&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Стабильных вам зависимостей и кода без багов!&lt;/p&gt;
&lt;h2&gt;Полезно почитать:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Документация: &lt;a href="https://docs.python.org/3/library/venv.html"&gt;https://docs.python.org/3/library/venv.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="blog"></category><category term="python"></category><category term="pip"></category><category term="venv"></category><category term="virtualenv"></category></entry></feed>