AlexKorablev.ruhttps://www.alexkorablev.ru/2024-02-26T10:00:00+05:00Александр Кораблев о разработке ПО, ИТ-индустрии и Python.Практический пример использования Protocol2024-02-26T10:00:00+05:002024-02-26T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2024-02-26:/2024/02/26/protocol/<p>Представьте себе ситуацию: у вас есть микросервисы, в каждом своя конфигурация со своим набором параметров; есть библиотечный код, который получает и использует объекты конфигураций. Представили? А теперь в этот код надо завести подсказки типов. И тут представили? Давайте проведем мысленный эксперимент и попробуем это сделать. Вариантов в целом несколько.</p>
<p>Представьте себе ситуацию: у вас есть микросервисы, в каждом своя конфигурация со своим набором параметров; есть библиотечный код, который получает и использует объекты конфигураций. Представили? А теперь в этот код надо завести подсказки типов. И тут представили? Давайте проведем мысленный эксперимент и попробуем это сделать. Вариантов в целом несколько.</p>
<ul>
<li>Базовый класс конфигурации с еще одним ветвлением: <ul>
<li>затаскивать все возможные параметры; </li>
<li>затаскивать только общие для микросервисов параметры. </li>
</ul>
</li>
<li>Использовать протоколы для нотации типов. </li>
</ul>
<p>Затаскивать все возможные варианты параметров в один базовый класс — гарантированный путь к помойке... Затаскивать только общие параметры для микросервисов — получать кучу красного цвета от pyright в общем коде. </p>
<blockquote>
<p>Вопрос, зачем нужны конфиги в библиотечном коде, отличный. Правильный ответ: там конфиги не нужны. Там нужны правильные вызовы... Но... Давайте для мысленного эксперимента предположим, что мы знатно косякнули во время написания кода и у нас вместо красивых вызовов в библиотечном коде куча использований объектов конфигураций. <em>Важно:</em> любое совпадение с реальным косяком в реальном коде случайно ;)</p>
</blockquote>
<h2>Почему Protocol?</h2>
<p>Использование протоколов — лучший вариант без переписывания интерфейсов классов и функций, без модификации вызовов. Кодовая база уже есть и работает, хочется обойтись инкрементальными улучшениями и не погибнуть в рефакторинге.</p>
<p>Протоколы — очень гибкий вариант. Он позволяет добиться еще одной неочевидной выгоды: библиотечный код становится более независимым. Основной недостаток: для большой базы потребует приличного объема кода.</p>
<p>Тут есть противоречие? Я не хочу переписывать код, но мне нужно написать код для внедрения протоколов. На самом деле противоречия нет:
- <strong>я оставлю код с бизнес-логикой без изменений;</strong>
- я оставлю интерфейсы классов, методов и функций без изменений;
- я напишу в основном дополнительный код для тайп-хинтов.</p>
<p>Возможно, я выкину несколько лишних импортов, что позволит разделить библиотечный код на меньшие независимые узлы.</p>
<h2>Что было?</h2>
<p>Попробую показать, с какой структурой кода начинаем. Дерево проекта выглядит как-то так.</p>
<ul>
<li>lib<ul>
<li>base_config.py</li>
<li>helper_a.py</li>
<li>helper_b.py</li>
</ul>
</li>
<li>services<ul>
<li>service_a<ul>
<li>config.py</li>
<li>app.py</li>
</ul>
</li>
<li>service_b<ul>
<li>config.py</li>
<li>app.py</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><code>base_config.py</code> содержит в себе класс, описывающий базовые конфиги, которые есть во всех сервисах.</p>
<div class="highlight"><pre><span></span><code><span class="c1"># lib/base_config.py</span>
<span class="nd">@dataclass</span>
<span class="k">class</span> <span class="nc">BaseConfig</span><span class="p">:</span>
<span class="n">db_connection_str</span><span class="p">:</span> <span class="nb">str</span>
<span class="o">...</span>
</code></pre></div>
<p>В <code>helper_a.py</code> может лежать код, который требует конфига сервиса.</p>
<div class="highlight"><pre><span></span><code><span class="c1"># lib/helper_a.py</span>
<span class="kn">from</span> <span class="nn">base_config</span> <span class="kn">import</span> <span class="n">BaseConfig</span>
<span class="k">def</span> <span class="nf">do_work</span><span class="p">(</span><span class="n">config</span><span class="p">:</span> <span class="n">BaseConfig</span><span class="p">):</span>
<span class="k">with</span> <span class="n">db_connection</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">db_connection_str</span><span class="p">)</span> <span class="k">as</span> <span class="n">connection</span><span class="p">:</span>
<span class="o">...</span>
<span class="o">...</span>
</code></pre></div>
<p>Вроде бы все ок. Но вот появилась функция <code>do_more_work</code>, которая используется в сервисах, где есть Redis. Таких сервисов может быть много, но не обязательно все сервисы проекта.</p>
<div class="highlight"><pre><span></span><code><span class="c1"># lib/helper_b.py</span>
<span class="kn">from</span> <span class="nn">base_config</span> <span class="kn">import</span> <span class="n">BaseConfig</span>
<span class="k">def</span> <span class="nf">do_more_work</span><span class="p">(</span><span class="n">config</span><span class="p">:</span> <span class="n">BaseConfig</span><span class="p">):</span>
<span class="n">redis</span> <span class="o">=</span> <span class="n">get_redis_connection</span><span class="p">(</span>
<span class="n">redis_creds</span><span class="o">=</span><span class="n">config</span><span class="o">.</span><span class="n">redis_creds</span> <span class="c1"># not in BaseConfig</span>
<span class="p">)</span>
<span class="o">...</span>
</code></pre></div>
<p>Придется упаковывать коннект к Redis в общий конфиг для всех сервисов? Не обязательно. Если готовы мириться с тем, что Pyright или другой анализатор подсвечивает вызовы как ошибку, то и переписывать ничего не нужно. Я бы переписал. И вот тут как раз приходят на помощь протоколы.</p>
<h2>Используем Protocol</h2>
<p>Вместо <code>BaseConfig</code> в каждом случае я делаю класс-наследник от <code>Protocol</code>.</p>
<div class="highlight"><pre><span></span><code><span class="c1"># lib/helper_a.py</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Protocol</span>
<span class="k">class</span> <span class="nc">DoWorkConfigProtocol</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
<span class="n">db_connection_str</span><span class="p">:</span> <span class="nb">str</span>
<span class="k">def</span> <span class="nf">do_work</span><span class="p">(</span><span class="n">config</span><span class="p">:</span> <span class="n">DoWorkConfigProtocol</span><span class="p">):</span>
<span class="k">with</span> <span class="n">db_connection</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">db_connection_str</span><span class="p">)</span> <span class="k">as</span> <span class="n">connection</span><span class="p">:</span>
<span class="o">...</span>
<span class="o">...</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="c1"># lib/helper_b.py</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Protocol</span>
<span class="k">class</span> <span class="nc">DoMoreWorkConfigProtocol</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
<span class="n">redis_creds</span><span class="p">:</span> <span class="nb">str</span>
<span class="k">def</span> <span class="nf">do_more_work</span><span class="p">(</span><span class="n">config</span><span class="p">:</span> <span class="n">DoMoreWorkConfigProtocol</span><span class="p">):</span>
<span class="n">redis</span> <span class="o">=</span> <span class="n">get_redis_connection</span><span class="p">(</span>
<span class="n">redis_creds</span><span class="o">=</span><span class="n">config</span><span class="o">.</span><span class="n">redis_creds</span> <span class="c1"># not in BaseConfig</span>
<span class="p">)</span>
<span class="o">...</span>
</code></pre></div>
<p>Я убрал зависимость в хелперах от <code>BaseConfig</code>. Я подсказал анализатору питона, что я жду в <code>config</code> в каждом конкретном случае.</p>
<p>Стал не нужен класс <code>BaseConfig</code>. Смысла в нем больше нет. Где-нибудь в сервисах, когда я буду делать вызовы библиотечных функций, мне анализатор подскажет, все ли в порядке с моим конфигом или мне нужно добавить к нему пропущенный параметр. А главное, он подскажет, что именно мне нужно добавить.</p>
<p>В реальных проектах редко бывает так, что можно ограничиться одним протоколом. Чаще всего нужна какая-то иерархия протоколов. Новые свойства будут добавляться по мере раскручивания стека вызовов или иерархии объектов. Чем глубже по стеку вызов, тем меньше свойств описано в протоколе. Аналогично для классов: в базовом классе минимальный набор, дальше по иерархии протоколы добавляют свойства.</p>
<p>Повторюсь, я бы предпочел явную передачу параметров. Но мы договорились: работаем с тем кодом, что есть. Так что в коде будет что-то вроде такого:</p>
<div class="highlight"><pre><span></span><code><span class="c1"># lib/helper_a.py</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Protocol</span>
<span class="k">class</span> <span class="nc">DoWorkConfigProtocol</span><span class="p">(</span><span class="n">Protocol</span><span class="p">):</span>
<span class="n">db_connection_str</span><span class="p">:</span> <span class="nb">str</span>
<span class="k">def</span> <span class="nf">do_work</span><span class="p">(</span><span class="n">config</span><span class="p">:</span> <span class="n">DoWorkConfigProtocol</span><span class="p">):</span>
<span class="k">with</span> <span class="n">db_connection</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">db_connection_str</span><span class="p">)</span> <span class="k">as</span> <span class="n">connection</span><span class="p">:</span>
<span class="o">...</span>
<span class="o">...</span>
<span class="c1"># lib/helper_b.py</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Protocol</span>
<span class="kn">from</span> <span class="nn">lib</span> <span class="kn">import</span> <span class="n">helper_a</span>
<span class="k">class</span> <span class="nc">DoMoreWorkConfigProtocol</span><span class="p">(</span>
<span class="n">helper_a</span><span class="o">.</span><span class="n">DoWorkConfigProtocol</span><span class="p">,</span>
<span class="n">Protocol</span>
<span class="p">):</span>
<span class="n">redis_creds</span><span class="p">:</span> <span class="nb">str</span>
<span class="k">def</span> <span class="nf">do_more_work</span><span class="p">(</span><span class="n">config</span><span class="p">:</span> <span class="n">DoMoreWorkConfigProtocol</span><span class="p">):</span>
<span class="n">redis</span> <span class="o">=</span> <span class="n">get_redis_connection</span><span class="p">(</span>
<span class="n">redis_creds</span><span class="o">=</span><span class="n">config</span><span class="o">.</span><span class="n">redis_creds</span> <span class="c1"># not in BaseConfig</span>
<span class="p">)</span>
<span class="n">do_work</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
<span class="o">...</span>
</code></pre></div>
<h2>Заключение</h2>
<p>Работа с протоколами довольно запутанная. Есть много моментов, на которые стоит обращать внимание. Не могу сказать, что я добился полного их понимания. Мне помогает опыт работы с Java. Протокол — практически один в один интерфейс в Java. И используется преимущественно для тех же целей.</p>
<p>Не могу оставить вас без полезных ссылок для дальнейшего изучения:</p>
<ul>
<li>Лучшее описание протоколов, что я встречал, — это статья <a href="https://mypy.readthedocs.io/en/stable/protocols.html">Protocols and structural subtyping</a> из документации к mypy.</li>
<li>Не обойтись без <a href="https://peps.python.org/pep-0544/">PEP 544</a> — описание протоколов, как они приняты и реализованы в Python.</li>
</ul>
<p>А вы используете в своем коде протоколы? Как? Было бы интересно услышать ваше мнение.</p>У Kаждого Cвой SCRUM2023-11-28T10:00:00+05:002023-11-28T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2023-11-28:/2023/11/28/scrum/<p>У каждого свой SCRUM. Он описан в книгах как стройный, понятный и простой процесс. Участники семинаров и тренингов утверждают, что применять его легко. Однако, в реальной жизни, SCRUM каждого человека не похож ни на пример из книг или тренингов, ни на процессы коллег… за редкими исключениями. Почему? Не знаю. Куда интереснее выяснить, что с этим делать и нужно ли это.</p>
<p>У каждого свой SCRUM. Он описан в книгах как стройный, понятный и простой процесс. Участники семинаров и тренингов утверждают, что применять его легко. Однако, в реальной жизни, SCRUM каждого человека не похож ни на пример из книг или тренингов, ни на процессы коллег… за редкими исключениями. Почему? Не знаю. Куда интереснее выяснить, что с этим делать и нужно ли это.</p>
<h2>TLTR</h2>
<ul>
<li>SCRUM эффективен, когда желания бизнеса и разработки совпадают и они готовы играть по этим правилам. </li>
<li>SCRUM определяет четкие зоны ответственности и правила для скрам-команды. Если попытаться изменить их, это может привести к проблемам. </li>
<li>Если вы устанавливаете сроки для разработчиков, определяете исполнителей задач и не ведете бэклог, то у вас нет SCRUM, даже при наличии спринтов и ежедневных митингов. </li>
<li>В вашей команде есть скрам-мастер, даже если он не выделен формально. Один из членов команды выполняет эту функцию. Это занимает у него время и, возможно, вызывает лишний стресс. </li>
<li>Наем скрам-мастера показывает разработке, что компания готова играть по правилам и ожидает долгосрочного применения SCRUM. </li>
<li>При работе с большой командой следует разделить ее на несколько подгрупп и использовать LeSS или “Scrum of Scrums”.</li>
</ul>
<p>Я не знаю, какой SCRUM можно считать идеальным. Я не являюсь сертифицированным скрам-мастером. Я всего лишь разработчик. Эта статья - моя попытка понять, каким должен быть SCRUM, зачем вам нужен скрам-мастер и в каких случаях SCRUM не работает. Повторяю, это мнение разработчика, участника скрам-команды, а не консультанта.</p>
<h2>Скрам может быть любым?</h2>
<p>Я бы сравнил SCRUM с каркасом фахверкового дома. В Германии такие дома до сих популярны. В основе такого дома лежит каркас, промежутки в котором заполняются подходящим материалом - кирпичом или глиной с соломой.</p>
<p>SCRUM работает примерно по такому же принципу. Есть каркас фреймворка, а есть его заполнение, которое может меняться в зависимости от потребностей команды и специфики проекта. Бэклог должен быть упорядочен и понятен. Как именно его упорядочивать и описывать конкретные элементы - решать команде. Во время ретроспективы делаются выводы на будущее после прошедшего спринта, но как именно проводить ретроспективу и оформлять эти выводы - тоже решает команда. Таким образом, каждый пункт, каждая активность во фреймворке уникальны в своей реализации.</p>
<p>Во время собеседования я не только проверяю знание алгоритмов. Я также беседую с кандидатом о его опыте работы. Один из ключевых вопросов: как организованы текущие процессы в его команде. Часто кандидат говорит, что у них используется скрам: есть спринты и ежедневные митинги. Хорошо, продолжаем:</p>
<ul>
<li>как составляется бэклог? </li>
<li>кто и каким образом принимает задачи в работу? </li>
</ul>
<p>И вот здесь начинаются интересные моменты…</p>
<p>Часто у команды нет бэклога или бэклог содержит нечто странное, не упорядоченное:</p>
<ul>
<li>Задачи не выбираются из бэклога. </li>
<li>Важная задача прячется где-то внизу списка. </li>
<li>Проработка задач оставляет желать лучшего. “Сделать хорошо” - это максимумально подробное описание задач в "бэклоге". </li>
<li>Не разработка определяет, как должны быть выполнены задачи. Они попадают в руки разработчиков уже “готовыми” (см. предыдущий пункт).</li>
</ul>
<h2>Чем грозит отсутствие порядка в бэклоге?</h2>
<p>Бардак в бэклоге приводит к следующим проблемам:</p>
<ul>
<li>формальные спринты; </li>
<li>выбор задач по привлекательности, а не по важности для бизнеса; </li>
<li>длительное планирование с распределением ответственности.</li>
</ul>
<p>Формальный спринт - это когда следующее планирование запланировано через две недели, без каких-либо конкретных действий. Команда не знает, что является самым важным для бизнеса на этот период. Сложно понять, куда приложить усилия в первую очередь.</p>
<p>Поэтому начинают выбирать задачи по привлекательности: самое важное неизвестно, приоритеты постоянно меняются. Если выбрать понятную и интересную задачу, можно показать хотя бы немного готового кода.</p>
<p>Если бонусная система в компании привязана к индивидуальным результатам сотрудников, можно гарантировать, что разработчики будут выбирать задачи с максимальным вкладом в их оценку, а не на достижение целей бизнеса.</p>
<p>Еще одна проблема - назначение исполнителей. Менеджер, который не является владельцем продукта, начинает определять, кто будет выполнять ту или иную задачу. В дополнение к задаче приходят сроки, которые слабо связаны с ее сложностью.</p>
<blockquote>
<p>Вы сталкивались с ситуацией, когда встречу для приемки (а не для получения обратной связи, как это часто бывает) определенной фичи, которая все еще находится в активной разработке, назначают на определенную дату без ведома разработчиков?</p>
</blockquote>
<h2>Обратная связь с пользователями важна</h2>
<p>Обратная связь от пользователей - еще одна важная часть скрама. Если команда не сможет или не захочет максимально приблизиться к конечному пользователю продукта, скрам не сработает. Это довольно сильное утверждение, но его можно считать одним из основных принципов.</p>
<p>Предположим следующую ситуацию: у нас есть заказчики с позицией «сделайте, мы посмотрим». Есть пользователи, заинтересованные в результате, но не являющиеся заказчиками, поэтому их мнение не учитывается. И есть команда разработчиков, которая работает над фичей Y в течение Х спринтов. Заказчик говорит «похоже». Пользователи говорят «этим невозможно пользоваться».</p>
<p>Команде разработчиков приходится начинать все сначала. Тот факт, что пользователи сами не знают, чего хотят, практически непреложен, и по своей строгости может соперничать с физическими законами. Можно с ним не соглашаться, но действует он неотвратимо, как гравитация, и яблоко все равно упадет тебе на голову.</p>
<blockquote>
<p>Пользователи, конечно, знают, что им нужно. Но они стараются помочь разработчикам и передать свое видение на понятном, как им кажется, разработчикам языке. Если вернуться к началу обсуждения и разобраться с вопросом «какую проблему мы решаем?», то пользователи и разработчики смогут быстро найти общий язык.</p>
</blockquote>
<p>Чем быстрее создается что-то рабочее, тем лучше. Чем быстрее получается обратная связь, тем лучше. Необязательно привязываться к демо. Можно ускорять релиз-циклы. Хорошо организованный процесс сбора метрик может заменить классические демо с приглашением пользователей.</p>
<p>Можно и комбинировать подходы. И лучше это делать. В сложных продуктах ведь есть все - от тестовых запусков до A/B-тестирования на реальных пользователях.</p>
<p>Важно проводить регулярные встречи, на которых можно обсудить результаты, оценить, достигнута ли цель спринта и наметить вектор работы на следующий спринт. Это полезно для команды, стейкхолдеров и пользователей (или лиц, максимально приближенных к ним).</p>
<h2>Каждый пишет свой сервис? У вас не SCRUM</h2>
<p>Мне несколько раз встречались случаи, когда кто-то из членов команды владел собственным участком кода, своим сервисом, в который никто больше не мог вносить изменения. Это не сочетается со скрамом. Получается своего рода матрешка, команда в команде, и бэклог внутри бэклога :) Возникает путаница.</p>
<h2>Нужен ли вообще SCRUM?</h2>
<p>Нужен ли нам скрам вообще? Не знаю. В отличие от тренеров, продающих тренинги, я не стану утверждать, что это лучший или единственный метод организации рабочего процесса. Этот метод хорошо работает при определенных условиях, и главное, он требует активного участия большого числа людей.</p>
<p>Я также не настаиваю на строгом соблюдении всех требований фреймворка. Может быть, это и не плохо. На практике всё получается так, как получается. Однако я отмечу, что большинство ключевых моментов стоит иметь в виду и, если возможно, использовать.</p>
<p>От команды разработки требуется готовность работать над продуктом, а не только над самыми интересными или прибыльными задачами. Часто наиболее полезные задачи оказываются скучными, рутинными и не творческими, но именно они могут принести наибольшую пользу.</p>
<p>Успех скрама зависит от желания бизнеса участвовать в процессе. Необходимо назначить ответственного за разработку, который будет определять приоритеты функций, их объем и сможет удерживать коллег за пределами команды от попыток вмешаться в середину спринта со своими пожеланиями.</p>
<h2>Коммуникация важна</h2>
<p>Как правило, разработчики, несмотря на свою репутацию замкнутых людей, охотно взаимодействуют со стейкхолдерами различных функций. Разработчикам необходима открытая и прямая коммуникация. Схема “скажи своему менеджеру, он передаст менеджеру смежной команды, тот передаст своему менеджеру, а уже тот - нужному человеку” не работает. Работает схема “напиши Васе, он знает все”.</p>
<p>Такая коммуникация во многом определяется желанием бизнеса. Так же, как и отношение команды разработки к скраму зависит от бизнеса. Хотя команда может инициировать скрам, если бизнесу это не нужно, скрам не будет работать.</p>
<p>Пока я видел, лучший способ продемонстрировать, что скрам в компании воспринимают серьезно, - это нанять скрам-мастера. Как я уже упоминал, он в любом случае уже будет в команде. Скрам-мастер - это не должность, а роль. И эта роль кому-то должна быть отведена. Независимо от того, хочет он этого или нет. Однако, если “актер” не желает исполнять обязанности мастера, либо процесс не запустится, либо этот человек уйдет.</p>
<h2>Заключение</h2>
<p>Я работал в нескольких командах, заявлявших, что они работают по скрам. Только последняя команда следовала практикам фреймворка в достаточной мере, чтобы ее можно было назвать скрам-командой. Предыдущим командам не доставало различных элементов.</p>
<p>В то же время, команды работали достаточно согласованно и достигали целей проекта. Сейчас мне кажется, что мы могли бы двигаться к целям быстрее. Впрочем, в ретроспективе обычно кажется, что что-то можно было сделать лучше, эффективней.</p>
<p>Скрам предоставляет большую свободу разработчикам. Четкое фокусирование на самом важном позволяет с полным основанием послать тех, кто пытается влезть с чем-то менее важным для проекта в данный момент. Может быть на время спринта, но это позволяет успеть сделать хоть что-то важное, закончить пусть самую маленькую фичу, выпустить пусть минимальное улучшение.</p>Использование typing.Generic в Python2022-02-18T10:00:00+05:002022-02-18T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2022-02-18:/2022/02/18/generics-in-python/<p>Я работаю над проектом с довольно большой кодовой базой. Проект с историей.
Некоторые части наша команда написала задолго до аннотаций типов. Мы до сих пор добавляем
их в наш легаси код и улучшаем существующие подсказки. Стоит эта игра свеч? Определенно.
Наши пользователи - разработчики. Они открывают наш код в PyCharm ежедневно. И они надеются,
что он поможет им решить их задачи максимально быстро и просто.</p>
<p>Я работаю над проектом с довольно большой кодовой базой. Проект с историей.
Некоторые части наша команда написала задолго до аннотаций типов. Мы до сих пор добавляем
их в наш легаси код и улучшаем существующие подсказки. Стоит эта игра свеч? Определенно.
Наши пользователи - разработчики. Они открывают наш код в PyCharm ежедневно. И они надеются,
что он поможет им решить их задачи максимально быстро и просто.</p>
<p>Могу уверенно сказать, что существует корреляция между точностью автодополнения кода и как
быстро работают разработчики. Наша цель не только добавить более точный
статический анализ или автодополнение, но и не сломаем им их боевой код.</p>
<p>Один из моих коллег добавил аннотацию типов, которая выглядит так:</p>
<div class="highlight"><pre><span></span><code><span class="c1"># file name: type_cast_0.py</span>
<span class="k">class</span> <span class="nc">A</span><span class="p">:</span>
<span class="n">a</span> <span class="o">=</span> <span class="s1">'a'</span>
<span class="k">class</span> <span class="nc">B</span><span class="p">(</span><span class="n">A</span><span class="p">):</span>
<span class="n">b</span> <span class="o">=</span> <span class="s1">'b'</span>
<span class="k">class</span> <span class="nc">DoSomethingWithA</span><span class="p">:</span>
<span class="n">_class</span> <span class="o">=</span> <span class="n">A</span>
<span class="k">def</span> <span class="nf">do</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="n">A</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_class</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">DoSomethingWithB</span><span class="p">(</span><span class="n">DoSomethingWithA</span><span class="p">):</span>
<span class="n">_class</span> <span class="o">=</span> <span class="n">B</span>
</code></pre></div>
<p>PyCharm не видит проблем в этом коде. Его анализатор показывает зеленую галочку. Mypy также не находит никаких проблем:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>mypy<span class="w"> </span>type_cast_0.py
Success:<span class="w"> </span>no<span class="w"> </span>issues<span class="w"> </span>found<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="nb">source</span><span class="w"> </span>file
</code></pre></div>
<p>Но если добавить вот такой код, который использует <code>DoSomethingWithB</code>...</p>
<div class="highlight"><pre><span></span><code><span class="c1"># file name: type_cast_1.py</span>
<span class="kn">from</span> <span class="nn">type_cast_0</span> <span class="kn">import</span> <span class="n">DoSomethingWithB</span>
<span class="nb">print</span><span class="p">(</span><span class="n">DoSomethingWithB</span><span class="p">()</span><span class="o">.</span><span class="n">do</span><span class="p">()</span><span class="o">.</span><span class="n">b</span><span class="p">)</span>
</code></pre></div>
<p>PyCharm теперь показывает warning: <code>Unresolved attribute reference 'b' for class 'A'</code>.
И Mypy помечает этот кусок кода ошибкой.</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>mypy<span class="w"> </span>type_cast_1.py
type_cast_1.py:4:<span class="w"> </span>error:<span class="w"> </span><span class="s2">"A"</span><span class="w"> </span>has<span class="w"> </span>no<span class="w"> </span>attribute<span class="w"> </span><span class="s2">"b"</span>
Found<span class="w"> </span><span class="m">1</span><span class="w"> </span>error<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="m">1</span><span class="w"> </span>file<span class="w"> </span><span class="o">(</span>checked<span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="nb">source</span><span class="w"> </span>file<span class="o">)</span>
</code></pre></div>
<p>Попробуем это исправить. Ниже моя первая попытка: наивный подход к дженерикам в Питоне.</p>
<div class="highlight"><pre><span></span><code><span class="c1"># file name: type_cast_2.py</span>
<span class="c1">#...</span>
<span class="n">TV</span> <span class="o">=</span> <span class="n">tp</span><span class="o">.</span><span class="n">TypeVar</span><span class="p">(</span><span class="s1">'TV'</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">DoSomethingWithA</span><span class="p">(</span><span class="n">tp</span><span class="o">.</span><span class="n">Generic</span><span class="p">[</span><span class="n">TV</span><span class="p">]):</span>
<span class="n">_class</span><span class="p">:</span> <span class="n">tp</span><span class="o">.</span><span class="n">Type</span><span class="p">[</span><span class="n">TV</span><span class="p">]</span> <span class="o">=</span> <span class="n">A</span>
<span class="k">def</span> <span class="nf">do</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="n">TV</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_class</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">DoSomethingWithB</span><span class="p">(</span><span class="n">DoSomethingWithA</span><span class="p">):</span>
<span class="n">_class</span> <span class="o">=</span> <span class="n">B</span>
</code></pre></div>
<p>PyCharm не показывает никаких ошибок или предупреждений. Mypy все еще не нравится мой код.</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>mypy<span class="w"> </span>type_cast_3.py
type_cast_2.py:17:<span class="w"> </span>error:<span class="w"> </span>Incompatible<span class="w"> </span>types<span class="w"> </span><span class="k">in</span><span class="w"> </span>assignment<span class="w"> </span><span class="o">(</span>expression<span class="w"> </span>has<span class="w"> </span><span class="nb">type</span><span class="w"> </span><span class="s2">"Type[A]"</span>,<span class="w"> </span>variable<span class="w"> </span>has<span class="w"> </span><span class="nb">type</span><span class="w"> </span><span class="s2">"Type[TV]"</span><span class="o">)</span>
Found<span class="w"> </span><span class="m">1</span><span class="w"> </span>error<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="m">1</span><span class="w"> </span>file<span class="w"> </span><span class="o">(</span>checked<span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="nb">source</span><span class="w"> </span>file<span class="o">)</span>
</code></pre></div>
<p>Интересно… Попробуем поменять <code>TV = tp.TypeVar('TV')</code> на <code>TV = tp.TypeVar('TV’, bound=A)</code>.
Такая же ошибка. Становится интереснее…</p>
<p>Официальная документация не сильно помогает. В ней всего пара примеров <a href="https://docs.python.org/3/library/typing.html#typing.Generic">использования Generics</a>,
но ничего, что даст ключ к исправлению проблемы. К счастью,
есть <a href="https://mypy.readthedocs.io/en/stable/generics.html">прекрасный раздел</a> о
Generics в документации mypy.</p>
<p>Для моего примера, код может выглядеть как-то так.</p>
<div class="highlight"><pre><span></span><code><span class="c1"># file name: type_cast_6.py</span>
<span class="c1"># ...</span>
<span class="k">class</span> <span class="nc">DoSomethingWith</span><span class="p">(</span><span class="n">tp</span><span class="o">.</span><span class="n">Generic</span><span class="p">[</span><span class="n">TV</span><span class="p">]):</span>
<span class="n">_class</span><span class="p">:</span> <span class="n">tp</span><span class="o">.</span><span class="n">Type</span><span class="p">[</span><span class="n">TV</span><span class="p">]</span>
<span class="k">def</span> <span class="nf">do</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="n">TV</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_class</span><span class="p">()</span>
</code></pre></div>
<p>А вот пример его использования.</p>
<div class="highlight"><pre><span></span><code><span class="c1"># file name: type_cast_7.py</span>
<span class="kn">from</span> <span class="nn">type_cast_6</span> <span class="kn">import</span> <span class="n">DoSomethingWith</span><span class="p">,</span> <span class="n">B</span>
<span class="nb">print</span><span class="p">(</span><span class="n">DoSomethingWith</span><span class="p">[</span><span class="n">B</span><span class="p">]()</span><span class="o">.</span><span class="n">do</span><span class="p">()</span><span class="o">.</span><span class="n">b</span><span class="p">)</span>
</code></pre></div>
<p>Mypy не видит никаких проблем. PyCharm показывает зеленую галочку.</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>mypy<span class="w"> </span>type_cast_6.py
Success:<span class="w"> </span>no<span class="w"> </span>issues<span class="w"> </span>found<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="nb">source</span><span class="w"> </span>file
$<span class="w"> </span>mypy<span class="w"> </span>type_cast_7.py
Success:<span class="w"> </span>no<span class="w"> </span>issues<span class="w"> </span>found<span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="nb">source</span><span class="w"> </span>file
</code></pre></div>
<p>К сожалению, попытка выполнить этот код завалится с исключением.</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>python<span class="w"> </span>type_cast_7.py
...
AttributeError:<span class="w"> </span><span class="s1">'DoSomethingWith'</span><span class="w"> </span>object<span class="w"> </span>has<span class="w"> </span>no<span class="w"> </span>attribute<span class="w"> </span><span class="s1">'_class'</span>
</code></pre></div>
<p>В питоне нет возможности использовать TypeVar так же, как можно использовать дженерики в Java, на пример.
Я не могу присвоить <code>TV</code> переменной <code>_class</code> и ожидать, что питон во заменит переменную типа на реальный класс
во время выполнения. Другими словами, если использовать <code>_class: tp.Type[TV] = TV</code> в <code>type_cast_6.py</code>,
я получу <code>TypeError: 'TypeVar' object is not callable</code>.</p>
<p>Что бы этого избежать я добавил подклассы для <code>DoSomethingWith</code>.</p>
<div class="highlight"><pre><span></span><code><span class="c1"># file name: type_cast_8.py</span>
<span class="c1"># ...</span>
<span class="k">class</span> <span class="nc">DoSomethingWithA</span><span class="p">(</span><span class="n">DoSomethingWith</span><span class="p">):</span>
<span class="n">_class</span> <span class="o">=</span> <span class="n">A</span>
<span class="k">class</span> <span class="nc">DoSomethingWithB</span><span class="p">(</span><span class="n">DoSomethingWith</span><span class="p">):</span>
<span class="n">_class</span> <span class="o">=</span> <span class="n">B</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="c1"># file name: type_cast_9.py</span>
<span class="kn">from</span> <span class="nn">type_cast_8</span> <span class="kn">import</span> <span class="n">DoSomethingWithB</span>
<span class="nb">print</span><span class="p">(</span><span class="n">DoSomethingWithB</span><span class="p">()</span><span class="o">.</span><span class="n">do</span><span class="p">()</span><span class="o">.</span><span class="n">b</span><span class="p">)</span>
</code></pre></div>
<p>Не особенно элегантное решение, но оно работает.</p>
<p>В этом посте много примеров кода. Я их порезал. Полные примеры можно найти в специальном репозитории
на <a href="https://github.com/avkorablev/code_4_blog/tree/main/type_cast">моем аккаутне в GitHub</a>.</p>Для чего Python лучше не использовать?2019-11-22T10:00:00+05:002019-11-22T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2019-11-22:/2019/11/22/what-is-python-not-good-for/<p>На Quora относительно недавно задали вопрос "What is Python not good for?" Я бы хотел сказать, что питон хорош для всего. Но это неправда. Питон во многих областях второй. Не так много областей, где питон — номер один.</p>
<p>На Quora относительно недавно задали вопрос "What is Python not good for?" Я бы хотел сказать, что питон хорош для всего. Но это неправда. Питон во многих областях второй. Не так много областей, где питон — номер один.</p>
<p>К примеру, Go сейчас гораздо больше подходит для веб-разработки, чем питон. Его довольно легко выучить, его гораздо легче деплоить. В Go есть достаточное количество батареек для любого веб-приложения. Тем не менее питон остаётся второй опцией. Даже в области микроконтроллеров питон будет моим вторым выбором после C благодаря MicroPython. Технически это не питон, то он довольно близок, чтобы считать его питоном.</p>
<p>В машинном обучении и больших данных питон действительно номер один без особых конкурентов. Для этого есть несколько причин. Под питон есть библиотеки практически для всего: батарейки включены. Для него есть оптимизированные библиотеки для вычислений и манипуляции данными. И последнее, питон - это язык-клей, который позволяет соединять разнородные системы в одно целое.</p>
<p>Для чего же питон не так хорош? Я бы назвал три области: UI (десктопные приложения), мобильную разработку и разработку игр.</p>
<p>Все три области, которые я выбрал, так или иначе про взаимодействие конечного пользователя с приложением. Для всех этих областей можно сделать приемлемое по качеству приложение на питоне. Но оно этого не стоит. Во всяком случае для продакшена.</p>
<h2>User Interface</h2>
<p>Какой лучший язык для UI? Зависит от платформы. Но питон не появится в списке ни для одной. Для веба, во всяком случае пока, выбора нет. JavaScript - единственный вариант. WebAssembly всё ещё находится на ранней стадии. И насколько я знаю, там нет поддержки питона. Если говорим про десктоп, то Swift для Mac и C# для Windows. Linux? C/C++. Нужно что-то кроссплатформенное? Используйте C/C++.</p>
<p>Я не говорю, что для питона нет GUI библиотек. Они есть. И их можно использовать, если нужно сделать что-то для себя или для других разработчиков. Они смогут потратить на решение проблем с ним, если ваше приложение будет им очень нужно.</p>
<p>Но обычные пользователи не очень-то заботятся как следует приложение стилю платформы. Пока оно работает хорошо, они даже не заметят, что вы используете не нативные элементы управления. Но если оно будет требовать каких-то усилий по установке или обновлению, они найдут что-то другое.</p>
<p>И это большая проблема для питона. Довольно сложно собрать один исполняемый файл с GUI приложением на питоне. Ещё сложнее это приложение обновлять. И оно этого не стоит в большинстве случаев.</p>
<h2>Мобильная разработка</h2>
<p>Есть Kivy. И это хорошая библиотека... для персональных проектов или чего-то не сильно важного для бизнеса. Почему? Она очень хрупкая. Доступ к камере или любой другой функции телефона требует бинарной зависимости. Эти зависимости обновляются с разной скоростью, разными людьми и часто несовместимы между собой. У многих дополнительных библиотек нет майнтейнеров.</p>
<p>Для мобильной разработки надо смотреть на нативные приложения. Вторым вариантом будет Cordova или что-то похожее (в эту категорию попадают React Native, Xamarin и ещё куча других кроссплатформенных решений). Я считаю, что для некоторых приложений Cordova (Ionic) - лучший выбор.</p>
<p>Но в любом случае, питон не войдёт в список технологий разработки мобильного приложения. Ни для какого бизнеса, ни для какого приложения.</p>
<h2>Разработка игр</h2>
<p>Основная проблема с использованием питона для разработки игр не в том, что нет зрелых фреймворков. Они есть. Но они не популярные, они не кроссплатформенные. Это значит, что ваша игра будет только на ПК. Хотите другие платформы? Будьте добры разработать игру ещё раз, но уже не на питоне.</p>
<p>Маркетинг игр - занятие сложное. Найти издателя во многих случаях лучшее решение. Но питон может стать непреодолимым препятствием: издатели просто не подпишут контракт. Им ведь придётся вложиться в портирование игры на более подходящую технологию, с которой они умеют работать.</p>
<h2>Вместо вывода</h2>
<p>Я использую питон практически для всего. Я даже подумываю о написании музыки с его помощью (батарейки есть даже для этого!). Тем не менее, я не начну разработку мобильного приложения на питоне. Во всяком случае того, которое планируется опубликовать в сторах.</p>
<p>А что вы думаете? Для чего питоне не так хорошо?</p>Подсказки типов в Python2019-05-24T10:00:00+05:002019-05-24T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2019-05-24:/2019/05/24/python-typing-hints-list/<p>Мой небольшой список источников по typehints в Питоне, которые я использую в повседневной практике.</p>
<p>Мой небольшой список источников по typehints в Питоне, которые я использую в повседневной практике.</p>
<p>Относительно недавно я сменил проект. Теперь я работаю над DWH и ETL-процессами. Проект большой. В нем сравнимо кода с предыдущим, но разработчиков гораздо больше. Я убежден, что для этого проекта подсказки типов даже нужнее. Дело не только в количестве разработчиков. </p>
<p>Особенность DWH/ETL-проектов - наличие разнообразных источников, мест хранения и потребителей данных. Отсюда довольно сильная фрагментированность проекта. И если не добавить "клея", он развалится на отдельные скрипты.</p>
<p>Когда я влился в проект я стал потихоньку добавлять описания типов в те куски кода с которыми я работал и считал важными. Постепенно подключились другие коллеги. Набралась критическая масса типизированного кода, когда мы решили сделать эту практику обязательной (не для всего проекта, до для значительно его части).</p>
<p>Меня попросили собрать список из самых необходимых и полезных в повседневной практике ресурсов по подсказкам типов в Python. Особые требования к этому списку:</p>
<ul>
<li>минимальное количество ссылок</li>
<li>только то, что можно использовать в ежедневной работе</li>
<li>максимально коротко и сжато (вот тут я провалился и не нашел практически ничего - все ссылки ведут на довольно длинные статьи и статьи документации)</li>
</ul>
<p>Вот мой список.</p>
<ul>
<li><em><a href="https://realpython.com/python-type-checking/">The Ultimate Guide to Python Type Checking</a></em></li>
</ul>
<p>Огромный учебник от Real Python. Покрывается весь спектр вопросов связанных с типами: от теории до настройки и запуска mypy. Это действительно полный гид по типам в питоне. Лучшее место для старта. Из минусов этого ресурса: он местами излишне многословен.</p>
<ul>
<li><em><a href="https://mypy.readthedocs.io/en/latest/cheat_sheet.html">Type hints cheat sheet</a></em></li>
</ul>
<p>Подсказки с примерами описаний типов от создателей mypy. Очень удобно пользоваться: покрыты все базовые случаи с примерами. Когда я только начинал знакомиться с типами, заглядывал сюда через раз. Но тут только базовые простые примеры. Если нужно что-то продвинутое, то здесь этого нет.</p>
<ul>
<li><em><a href="https://docs.python.org/3/library/typing.html">Support for type hints — Python documentation</a></em></li>
</ul>
<p>Документация в Python - одна из причин моей любви к языку. Документация для библиотеки typing - выше всяких похвал. Большая часть вопросов решается здесь. Зачастую надо полистать ее немного, что бы найти подходящий паттерн для описания типа в том или ином случае. </p>
<ul>
<li><em><a href="https://www.python.org/dev/peps/pep-0484/">PEP 484 (Type Hints)</a></em> </li>
<li><em><a href="https://www.python.org/dev/peps/pep-0483/">PEP 483 (The Theory of Type Hints)</a></em></li>
</ul>
<p>Иногда не хватает документации. Тогда наступает время PEP 484 и PEP 483. В мой практике это вообще единственные PEPы, в которые я бы заглядывал после принятия для поиска решения текущих задач. Они бывают очень полезны для прояснения логики, лежащей в основе системы подсказок типов. Без этого бывает сложно найти подходящую идею, как описать ту или иную часть кода.</p>
<p>Пожалуй это все ссылки, которые я использую с завидной регулярностью. Иногда по поводу особо сложных случаев приходится заглядывать на StackOverflow. Но как обычно со StackOverflow, голову выключать не стоит. Иногда правильный и подходящий ответ далеко не самый популярный или выбранный правильным.</p>
<p>Ничего по этим ссылкам нет по поводу внедрения подсказок в существующем проекте. Я об этом писал в конце 2017 года. Могу только сослаться на эту статью: <a href="https://www.alexkorablev.ru/2017/12/11/typing-ci/">Как внедрить mypy в проекте на Python 2.7</a>.</p>Codingame: Clash of Code2019-04-26T10:00:00+05:002019-04-26T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2019-04-26:/2019/04/26/clash-of-code/<p>В начале года я <a href="https://www.alexkorablev.ru/2019/01/02/2019-plans/">писал</a>, что хочу сделать большой и интересный проект с <a href="https://www.codingame.com">Codingame</a>: создать и обучить бота для LEGENDS OF CODE & MAGIC. В очередной раз, когда я прокрастинировал начало этого проекта, я наткнулся на <a href="https://www.codingame.com/multiplayer/clashofcode">Clash of Code!</a>: быстрые мини-соревнования по кодированию на этом же сайте. Попробовав поиграть, я могу сказать что, это отличный способ отвлечься от рабочей рутины, попутно потренировав свои навыки.</p>
<p>В начале года я <a href="https://www.alexkorablev.ru/2019/01/02/2019-plans/">писал</a>, что хочу сделать большой и интересный проект с <a href="https://www.codingame.com">Codingame</a>: создать и обучить бота для LEGENDS OF CODE & MAGIC. В очередной раз, когда я прокрастинировал начало этого проекта, я наткнулся на <a href="https://www.codingame.com/multiplayer/clashofcode">Clash of Code!</a>: быстрые мини-соревнования по кодированию на этом же сайте. Попробовав поиграть, я могу сказать что, это отличный способ отвлечься от рабочей рутины, попутно потренировав свои навыки.</p>
<h2>Формат Clash of Code</h2>
<p>Эти мини соревнования строятся по такому принципу:</p>
<ul>
<li>есть простая алгоритмическая задача, которая решается минут за 5;</li>
<li>есть ограничение по времени в 15 минут;</li>
<li>есть случайно выбранное условие победы:<ul>
<li>решить первым;</li>
<li>написать самое короткое решение;</li>
<li>понять что вообще нужно сделать по тестам.</li>
</ul>
</li>
</ul>
<p>С такими дополнительными ограничениями сложность задачи повышается существенно. Мне понравилось больше всего писать самое короткое решение: приходится перетряхнуть свои знания о стандартной библиотеке и вспомнить трюки синтаксиса Python.</p>
<h2>Зачем?</h2>
<p>Единственная положительная сторона, которую я вижу в этом - отвлечься от работы, переключиться на что-нибудь, освежиться. Никакого другого практического смысла в этих упражнениях нет.</p>
<p>С другой стороны, отвлечься на что-то ограниченное по времени - здорово. На 15 минут выключился из работы, таймер прозвенел, вернулся обратно. Согласитесь, такое с Facebook редко срабатывает ;).</p>
<p>Попробуйте сами, опыт довольно интересный. А я обещаю начать обещанный проект с ботом до распада последнего протона во вселенной. Клянусь! :)</p>Переименование при импорте2019-03-18T10:00:00+05:002019-03-18T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2019-03-18:/2019/03/18/import-aliases/<p>В Python есть замечательная возможность присваивать свои имена при импорте (<code>import foo.bar.baz as fbb</code>). Так можно легко избежать конфликтов имен при импорте и повысить читаемость кода, если требуется. Но этой же возможностью можно легко превратить хороший код в абсолютно нечитаемый. В последнее время я несколько раз встречался с этим: попадались мне, в основном, странные сокращения и непонятные имена для алиасов.</p>
<p>В Python есть замечательная возможность присваивать свои имена при импорте (<code>import foo.bar.baz as fbb</code>). Так можно легко избежать конфликтов имен при импорте и повысить читаемость кода, если требуется. Но этой же возможностью можно легко превратить хороший код в абсолютно нечитаемый. В последнее время я несколько раз встречался с этим: попадались мне, в основном, странные сокращения и непонятные имена для алиасов.</p>
<p>Продемонстрирую проблему на примере сокращений. Хороший пример придумать с ними проще.</p>
<div class="highlight"><pre><span></span><code> <span class="kn">from</span> <span class="nn">facebook</span> <span class="kn">import</span> <span class="n">utils</span> <span class="k">as</span> <span class="n">fbu</span>
<span class="kn">from</span> <span class="nn">twitter</span> <span class="kn">import</span> <span class="n">units</span> <span class="k">as</span> <span class="n">tu</span>
</code></pre></div>
<p>Отлично. Конфликта имен избежали. Но что такое <code>tu</code>? Если встретить такое имя в коде, то придется совершать дополнительные действия, что бы понять что автор имел ввиду. гораздо лучше если будет что-то вроде такого:</p>
<div class="highlight"><pre><span></span><code> <span class="kn">from</span> <span class="nn">facebook</span> <span class="kn">import</span> <span class="n">utils</span> <span class="k">as</span> <span class="n">facebook_utils</span>
<span class="kn">from</span> <span class="nn">twitter</span> <span class="kn">import</span> <span class="n">units</span> <span class="k">as</span> <span class="n">twitter_utils</span>
</code></pre></div>
<p>Уж теперь-то никаких вопросов в коде не возникнет. Конечно имена немного длиннее, но кто сейчас экономит байтики? Я бы использовал эту формулу импорта в подобном случае даже, если импортируется одна из библиотек с утилитами: читаемость кода будет выше.</p>
<p>Оказалось, что обосновать читаемость кода не так-то просто обосновать. Обычно на претензии к сокращениям отвечают, что можно в импорте посмотреть, что это значит и откуда прилетело. Безусловно можно. В IDE вообще никаких проблем с этим нет: ткнул в нужное место, перешел к определению, почитал, вернулся обратно. </p>
<p>Но большая часть чтений кода происходит в PR на github или подобной системе. Читается маленький дифф, где может просто не быть нужного импорта. Приходится “раскручивать” файл, что бы добраться до нужной строчки. Никакой помощи в навигации по коду тут нет. Остается только экономить время читающего используя осмысленные переименования.</p>
<p>Каково ваше мнение? Стоит вообще переживать по этому поводу и отправлять PR на доработку в подобных случаях?</p>Пожалуйста, не используйте else2019-01-29T10:00:00+05:002019-01-29T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2019-01-29:/2019/01/29/if-else/<p>Я не люблю <code>else</code>, когда обе ветки содержат return. Всегда можно написать код без этого придатка для <code>if</code>. Такой код будет компактнее, проще читаться и лучше выглядеть. Так зачем же многие пишут это никчемный <code>else</code>?</p>
<p>Я не люблю <code>else</code>, когда обе ветки содержат return. Всегда можно написать код без этого придатка для <code>if</code>. Такой код будет компактнее, проще читаться и лучше выглядеть. Так зачем же многие пишут это никчемный <code>else</code>?</p>
<p>Делая ревью на код одного из коллег, я указал на то, что его код можно упростить, убрав <code>esle</code>. Ответом была ссылка на PEP8, где такое поведение определяется допустимым. Код был примерно таким:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">get_magic_number</span><span class="p">(</span><span class="n">use_magic</span><span class="p">):</span>
<span class="k">if</span> <span class="n">use_magic</span><span class="p">:</span>
<span class="k">return</span> <span class="n">calculate_with_magic</span><span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">return</span> <span class="n">calculate_with_science</span><span class="p">()</span>
</code></pre></div>
<p>С точки зрения работоспособности, этому коду нечего предъявить. Меня, как читателя, такой код заставляется остановиться и задуматься. </p>
<ul>
<li>Что возвращает эта функция? </li>
<li>Всегда я выхожу из любой ветки?</li>
</ul>
<p>Даже на таких коротких функциях я останавливаюсь и думаю, все ли с ними хорошо. А ведь такими короткими обе ветки бывают не всегда.</p>
<p>У меня всегда возникает вопрос, почему нельзя отказаться от <code>else</code>? Плюсы, мне кажется, очевидны:</p>
<ul>
<li>Избавимся от одной ненужной строки.</li>
<li>Уберем один отступ для одной из веток. </li>
<li>Получим дефолтный return для функции.</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">get_magic_number</span><span class="p">(</span><span class="n">use_magic</span><span class="p">):</span>
<span class="k">if</span> <span class="n">use_magic</span><span class="p">:</span>
<span class="k">return</span> <span class="n">calculate_with_magic</span><span class="p">()</span>
<span class="k">return</span> <span class="n">calculate_with_science</span><span class="p">()</span>
</code></pre></div>
<p>Будет читаться код лучше? Да. Будет какая-нибудь разница для интерпретатора? Нет. Зачем тогда использовать бессмысленную конструкцию там, где она ненужна? PEP8, как и “потом может понадобиться” не являются весомыми аргументами в пользу <code>else</code>.</p>
<p>Что думаете? Это все вкусовщина или мои претензии обоснованы?</p>Планы на 20192019-01-02T10:00:00+05:002019-01-02T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2019-01-02:/2019/01/02/2019-plans/<p>Сегодня второе января 2019. Самое время поделиться планами на год для этого блога. Я хочу написать как минимум 12 постов. Есть две темы, которые мне интересны на данный момент: соревнования на CodinGames и Kaggle.</p>
<p>Сегодня второе января 2019. Самое время поделиться планами на год для этого блога. Я хочу написать как минимум 12 постов. Есть две темы, которые мне интересны на данный момент: соревнования на CodinGames и Kaggle.</p>
<h3>CodinGames</h3>
<p>Несколько постов я планирую посвятить соревнованию <a href="https://www.codingame.com/multiplayer/bot-programming/legends-of-code-magic">LEGENDS OF CODE & MAGIC</a>. Я выбрал его из-за своей любви к настольным и карточным играм. Несколько лет назад я играл в Magic the Gathering. Было бы круто написать бота для подобной игры. Тем более, что у меня есть идея сделать что-то подобное AlphaGo, или как минимум использовать подобный подход для того, чтобы пробиться в легендарную лигу.</p>
<h3>Kaggle</h3>
<p>Вторая интересная тема - Kaggle. Я хочу улучшить свои скилы в машинном обучении. Но у меня не так много задач с ML на работе. Так что участие в соревнованиях на Kaggle - практически единственный способ прокачаться. Для поддержания мотивации я буду добиваться звания Kaggle Master.</p>
<p>Основная причина - прокачать практические знания. Сейчас у меня больше теоретических знаний по вопросу. Я хочу конвертировать их в практику. Но поскольку у меня есть бюджет в $20-25 в месяц на сервера, с картинками я связываться не планирую.</p>
<h3>Всякое прочее</h3>
<p>Помимо Kaggle и CodinGames я буду писать обзоры книг и посты связанные с рабочими темами, если что-нибудь интересное случиться, конечно. Также у меня есть планы по автоматизации некоторых рутинных вещей. Может быть я напишу пару постов об этом.</p>
<p>Таковы мои планы по постам на 2019 год. С Новым Годом!</p>Мобильное приложение на Kivy и Python? Мм… Не сейчас2018-10-25T22:00:00+05:002018-10-25T22:00:00+05:00akorablevtag:www.alexkorablev.ru,2018-10-25:/2018/10/25/kivy-mobile/<p>Есть несколько GUI фреймворков на Python. Большинство из них только для десктопных приложений. Kivy - редкое исключение. С использованием этого фреймворка можно, в теории, строить мобильные приложения. К сожалению, он не готов к продакшену. Вы вынуждены жонглировать версиями библиотек чтобы заставить это работать. Так что даже сидя в футболке с надписью <code>print("Python is my favorite language")</code>, я должен признать, что он не подходит для мобильной разработки.</p>
<p>Есть несколько GUI фреймворков на Python. Большинство из них только для десктопных приложений. Kivy - редкое исключение. С использованием этого фреймворка можно, в теории, строить мобильные приложения. К сожалению, он не готов к продакшену. Вы вынуждены жонглировать версиями библиотек чтобы заставить это работать. Так что даже сидя в футболке с надписью <code>print("Python is my favorite language")</code>, я должен признать, что он не подходит для мобильной разработки.</p>
<p>Ok. Также жонглировать версиями надо и в случае веб-приложения. Но все-таки тогда в 99% случаев жонглировать надо библиотеками Python или же есть контроль над окружением. В целом вы можете построить docker-образ с любыми версиями, какие необходимы. Но с Kivi вносит кучу бинарных зависимостей, которые должны быть установлены с помощью специфичных для платформ инструментов. И в целом вы не сможете контролировать окружение. И это меняет все.</p>
<p>Недавно я решил сделать маленькое приложение для чтения QR кода на Android. Я использую Python каждый день, так что я решил что нет причин не воспользоваться Kivy. И через два часа у меня было десктопное приложение, которое работало довольно хорошо.</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">kivy.app</span> <span class="kn">import</span> <span class="n">App</span>
<span class="kn">from</span> <span class="nn">kivy.lang</span> <span class="kn">import</span> <span class="n">Builder</span>
<span class="kn">from</span> <span class="nn">zbarcam.zbarcam</span> <span class="kn">import</span> <span class="n">zbarcam</span>
<span class="n">DEMO_APP_KV_LANG</span> <span class="o">=</span> <span class="s2">"""</span>
<span class="s2">BoxLayout:</span>
<span class="s2"> orientation: 'vertical'</span>
<span class="s2"> ZBarCam:</span>
<span class="s2"> id: zbarcam</span>
<span class="s2"> code_types: 'qrcode', 'ean13'</span>
<span class="s2"> Label:</span>
<span class="s2"> size_hint: None, None</span>
<span class="s2"> size: self.texture_size[0], 50</span>
<span class="s2"> text: 'test: ' + ', '.join([str(symbol.data) for symbol in zbarcam.symbols])</span>
<span class="s2">"""</span>
<span class="k">class</span> <span class="nc">DemoApp</span><span class="p">(</span><span class="n">App</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">build</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="n">Builder</span><span class="o">.</span><span class="n">load_string</span><span class="p">(</span><span class="n">DEMO_APP_KV_LANG</span><span class="p">)</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
<span class="n">DemoApp</span><span class="p">()</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</code></pre></div>
<p>В действительности, я потратил 5 минут на поиск и копирование этого сниппета и 1 час 55 минут чтобы заставить его работать. Я предполагал, что это должно занять 10-15 минут максимум. Все из-за бинарных зависимостей, версия zbarcam из kivy-garden была слишком старой и не работала. Еще два дня я потратил на попытки сборки проекта под Android. Безрезультатно. Я так и не смог найти работающую пару версий Kivy и Cython.</p>
<p>Я решил остановиться на этом. Я люблю Python, но есть более стабильные технологии для того, чтобы сделать такое приложение. Даже Cordova и JS подходят для подобной задачи лучше: они более зрелые и стабильные. Xamarin — другая хорошая альтернатива. Не используйте технологию для не очень подходящих для нее задач только из-за того, что знаете ее.</p>Использовать Optional в абстрактных классах или нет?2018-09-06T10:00:00+05:002018-09-06T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2018-09-06:/2018/09/06/optional-or-not/<p>Подсказки типов в Python необязательны: вы вольны выбирать пользоваться или ими или нет. Но начав включать их в свой код, точно возникнут определенныме трудности аннотирования функций или переменных. Эта статья — моя точка зрения на один специфический случай.</p>
<p>Подсказки типов в Python необязательны: вы вольны выбирать пользоваться или ими или нет. Но начав включать их в свой код, точно возникнут определенныме трудности аннотирования функций или переменных. Эта статья — моя точка зрения на один специфический случай.</p>
<p>К примеру, имеется иерархия классов, которая выглядит похожей на ту, что представлена в коде ниже. Это, может быть, какой-то маппинг в базу или иерархия команд. Самая важная часть в этом примере то, что имеется один абстрактный класс и несколько реальных классов, его наследников. Не так важно помечен ли этот абстрактный класс специальным образом или нет. Я просто предполагаю, что он используется в качестве такового.</p>
<div class="highlight"><pre><span></span><code> <span class="k">class</span> <span class="nc">A</span><span class="p">:</span>
<span class="n">arrt</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">class</span> <span class="nc">B</span><span class="p">(</span><span class="n">a</span><span class="p">):</span>
<span class="n">arrt</span> <span class="o">=</span> <span class="s1">'B'</span>
</code></pre></div>
<p>Как вы заметили, у класса есть один атрибут, который должен быть строкой во всех реальных классах. И нет никакого вменяемого значения для этого атрибута в абстрактном классе. Использовать пустую строку также нельзя.
Есть два варианта добавления подсказки по типам, кроме <code>Any</code>, конечно: использовать <code>Optional</code> или нет. Давайте обсудим первый вариант.</p>
<div class="highlight"><pre><span></span><code> <span class="k">class</span> <span class="nc">A</span><span class="p">:</span>
<span class="n">arrt</span> <span class="o">=</span> <span class="kc">None</span> <span class="c1"># type: Optional[str]</span>
</code></pre></div>
<p>Выглядит хорошо. Но есть одна проблема: значение атрибута может быть или строкой или <code>None</code>. Такой класс будет соответствовать по типам:</p>
<div class="highlight"><pre><span></span><code> <span class="k">class</span> <span class="nc">C</span><span class="p">(</span><span class="n">A</span><span class="p">):</span>
<span class="n">attr</span> <span class="o">=</span> <span class="kc">None</span>
</code></pre></div>
<p>Но это не то поведение, которое требовалось. Нашей целью было избежать этого.
Второй вариант без <code>Option</code> работает много лучше. </p>
<div class="highlight"><pre><span></span><code> <span class="k">class</span> <span class="nc">A</span><span class="p">:</span>
<span class="n">arrt</span> <span class="o">=</span> <span class="kc">None</span> <span class="c1"># type: str</span>
</code></pre></div>
<p>Если описать типы так, то класс <code>C</code> не пройдет проверку mypy: как раз необходимый нам результат.
Подсказки типов в Python вещь дополнительная. Динамическая природа и легаси код вызывают проблемы время от времени. <code>Optional</code> — иногда хороший выбор, но не всегда.</p>Coders Strike Back Post-mortem2018-07-12T10:00:00+05:002018-07-12T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2018-07-12:/2018/07/12/coders-strike-back/<p>За последние несколько месяцев я потратил порядка 40 часов в выходные и по вечерам на то, чтобы написать бота способного пройти трассу <a href="https://www.codingame.com/multiplayer/bot-programming/coders-strike-back">Coders Strikes Back</a> быстрее соперников. За эти 40 часов я умудрился набить порядочное количество шишек, выбраться в легендарную лигу и даже занять там пристойное, на мой взгляд, место. Данный пост — post mortem этого проекта.</p>
<p>За последние несколько месяцев я потратил порядка 40 часов в выходные и по вечерам на то, чтобы написать бота способного пройти трассу <a href="https://www.codingame.com/multiplayer/bot-programming/coders-strike-back">Coders Strikes Back</a> быстрее соперников. За эти 40 часов я умудрился набить порядочное количество шишек, выбраться в легендарную лигу и даже занять там пристойное, на мой взгляд, место. Данный пост — post mortem этого проекта.</p>
<p>Важная оговорка, бо́льшая часть пути была проделана с ботами на Python, но заключительные этапы были пройдены на C++. Не думаю, что дело в Python, скорее всего, я не заметил кучу ошибок в своем коде, которые было лень исправлять, но об этом ниже.</p>
<h2>CodinGames</h2>
<p>Для начала должен сказать пару слов о сайте, где происходит соревнование — CodinGames. Это сборник задач по программированию, оформленных в виде игр: где-то нужно решать головоломки, где-то обыгрывать AI, где-то состязаться с написанными другими людьми ботами. Таких задач и соревнований на сайте довольно много разного уровня, различной направленности. Объединяет их одно — они оформлены в виде игр, где «герой» или «герои» управляются программно.</p>
<p>По сути, все игры и загадки на сайте — замаскированные программистские задачки. Думаю бо́льшую часть из них можно в том или ином виде найти на сайтах типа HackerRank. Там они будут сформулированы более явно. В этом состоит главное преимущество CodinGames: тратить время на то, чтобы разобраться почему твой бот такой медленный гораздо приятнее и интереснее, чем разбираться из-за почему решение вылетает на 17-м тесте, а на 20-м падает по таймауту.</p>
<p>На мой взгляд, CodinGames и HackerRank решают немного разные задачи. HackerRank — это больше подготовка к традиционным интервью. Тут важнее научиться решению стандартных алгоритмических задач, сформулированных так, как их формулируют на интервью. Несмотря на то что никаких лимитов по времени на HackerRank нет, часто чувствую себя на нем как на интервью, как будто меня кто-то подгоняет («У вас всего час на решение этой задачи»).</p>
<p>CodinGames — это поиск новых подходов к решению задачи. У меня не такой большой опыт работы с этим сайтом, но внутренний таймер здесь не запускался ни разу, это приводило к тому, что я засиживался за поиском решений дольше, чем планировал. Пазлы в виде игры дают какую-то внутреннюю свободу: гораздо проще согласиться с тем, что ты застрял и пойти погуглить подходы к решению. С одной стороны, хочется найти решение самому. С другой, такая свобода привела к тому, что я пробовал добиться результата большим количеством способов, чем делал это с любой задачей на HackerRank, которую я когда-либо решал.</p>
<h2>Coders Strikes Back</h2>
<p>Coders Strikes Back хорошо иллюстрирует разницу между HackerRank и CodinGames. В отличие от задач на HackerRank не существует одного правильного способа написать код для этого соревнования. По мере поисков вдохновения я выяснил, что хороших результатов добавились участники использующие разные подходы: от набора эвристик до нейронных сетей.</p>
<p>В этой игре ваша задача провести кар через контрольные точки несколько кругов. Сделать это нужно быстрее соперника. Само соревнование разделено на лиги. Из лиги в лигу переход осуществляется, если ваш ИИ наберет больше очков, чем ИИ боса в гонках с другими участниками лиги (при этом необязательно выигрывать у самого боса).</p>
<p>От лиги к лиге меняются правила: добавляется ускорение, щит, появляется второй бот... ИИ боса с каждой лигой становится все лучше. Но если честно, до серебряной лиги написание бота на эвристиках не должно вызывать особых затруднений. Да на этих этапах сочинять что-то более сложное, вообще, не имеет смысла. Самый полезный совет на этих этапах: забудьте про оппонента, улучшайте качество прохождения дистанции. Это сделать не так сложно. </p>
<h2>Золотая лига</h2>
<p>Добравшись до золотой лиги, я столкнулся с уже серьезным сопротивлением: долгое время не удавалось набрать даже близко столько очков, сколько набирал босс лиги. Наивный подход приносил места в районе 2 тыс. за счет того, что в золотой лиге много брошенных и сломанных ботов, которые способны сделать 3–4 хода, а потом отваливаются по таймауту.</p>
<p>Я не хотел заморачиваться со сложными алгоритмами и планировал добить свой бот до приличного уровня одними эвристиками. По мере того как мой код становился все более развесистым, настроение падало. Тогда после нескольких часов гугления я нашел интересную эвристику: координата цели = координата следующего чекпоинта — утроенная скорость по соответствующей оси.</p>
<p>Таким образом, у меня получился очень компактный бот, который тем не менее выдавал место в районе 300.</p>
<div class="highlight"><pre><span></span><code><span class="n">laps</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="nb">input</span><span class="p">())</span>
<span class="n">cp_count</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="nb">input</span><span class="p">())</span>
<span class="n">checkpoints</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">cp_count</span><span class="p">):</span>
<span class="n">checkpoint_x</span><span class="p">,</span> <span class="n">checkpoint_y</span> <span class="o">=</span> <span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="n">j</span><span class="p">)</span> <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">input</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">()]</span>
<span class="n">checkpoints</span><span class="o">.</span><span class="n">append</span><span class="p">((</span><span class="n">checkpoint_x</span><span class="p">,</span> <span class="n">checkpoint_y</span><span class="p">))</span>
<span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="p">):</span>
<span class="n">fp_x</span><span class="p">,</span> <span class="n">fp_y</span><span class="p">,</span> <span class="n">fp_vx</span><span class="p">,</span> <span class="n">fp_vy</span><span class="p">,</span> <span class="n">fp_angle</span><span class="p">,</span> <span class="n">fp_next_cp_id</span> <span class="o">=</span> <span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="n">j</span><span class="p">)</span> <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">input</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">()]</span>
<span class="n">sp_x</span><span class="p">,</span> <span class="n">sp_y</span><span class="p">,</span> <span class="n">sp_vx</span><span class="p">,</span> <span class="n">sp_vy</span><span class="p">,</span> <span class="n">sp_angle</span><span class="p">,</span> <span class="n">sp_next_cp_id</span> <span class="o">=</span> <span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="n">j</span><span class="p">)</span> <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">input</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">()]</span>
<span class="nb">print</span><span class="p">(</span>
<span class="s2">"</span><span class="si">{}</span><span class="s2"> </span><span class="si">{}</span><span class="s2"> </span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
<span class="n">checkpoints</span><span class="p">[</span><span class="n">fp_next_cp_id</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">-</span> <span class="mi">3</span><span class="o">*</span><span class="n">fp_vx</span><span class="p">,</span>
<span class="n">checkpoints</span><span class="p">[</span><span class="n">fp_next_cp_id</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="o">-</span> <span class="mi">3</span><span class="o">*</span><span class="n">fp_vy</span><span class="p">,</span>
<span class="s2">"BOOST"</span>
<span class="p">)</span>
<span class="p">)</span>
<span class="nb">print</span><span class="p">(</span>
<span class="s2">"</span><span class="si">{}</span><span class="s2"> </span><span class="si">{}</span><span class="s2"> </span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
<span class="n">checkpoints</span><span class="p">[</span><span class="n">sp_next_cp_id</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">-</span> <span class="mi">3</span><span class="o">*</span><span class="n">sp_vx</span><span class="p">,</span>
<span class="n">checkpoints</span><span class="p">[</span><span class="n">sp_next_cp_id</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="o">-</span> <span class="mi">3</span><span class="o">*</span><span class="n">sp_vy</span><span class="p">,</span>
<span class="s2">"BOOST"</span>
<span class="p">)</span>
<span class="p">)</span>
<span class="nb">input</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">()</span>
<span class="nb">input</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">()</span>
</code></pre></div>
<p>Усложнение логики только ухудшало результат. Нужен был более продвинутый подход. Тогда я нашел несколько статей с описанием еще одного способа: формирование большого количества планов полета на следующие несколько ходов, выбор из низ оптимального.</p>
<ul>
<li><a href="https://www.codingame.com/blog/genetic-algorithms-coders-strike-back-game/">GENETIC ALGORITHMS IN CODERS STRIKE BACK GAME</a></li>
<li><a href="https://www.codingame.com/blog/coders-strike-back-pb4608s-ai-rank-3rd/">CODERS STRIKE BACK – PB4608’S AI (RANK: 3RD)</a></li>
<li><a href="http://files.magusgeek.com/csb/csb_en.html">Coders Strikes Back</a></li>
</ul>
<p>Помимо самой генерации плана, что довольно просто, требовалась модель, которая бы предсказывала положение ботов после каждого хода. Последняя статья из списка довольно хорошо рассказывает, что нужно сделать для воплощение в жизнь этой модели.</p>
<p>К сожалению, реализация в лоб на питоне не позволяет составить достаточное количество планов. Мне удалось добиться генерации порядка 60 вариантов без риска таймаутов. Пожалуй, реализация с использованием NumPy дала бы более достойный результат. Но я пошел по пути меньшего сопротивления. К тому времени я уже немного устал от проекта и хотелось его завершить. Так что я вернулся к поиску подходящей заготовки.</p>
<p>Далеко ходить не пришлось. Отличный шаблон, написанный Романом Рингом (Roman Ring), нашелся минут за 20 https://github.com/Inoryy/csb-ai-starter. Совсем немного поколдовав с функцией оценки плана (благо заготовки и идеи на пробу я собрал еще на предыдущем этапе), я легко выскочил в легендарную лигу.</p>
<p>Легендарная лига или вместо заключения</p>
<p>В легендарной лиге практически ничего не меняя, только поправив лимит скорости, я добрался до 40-го места. Поскольку выход в легендарную лигу и был желаемым результатом, я на этом остановился.</p>
<p>Но в целом имея хорошую заготовку для моделирования полетов, можно поколдовать над функцией оценки, попробовать поменять над алгоритмом и добиться еще более впечатляющих результатов.</p>4 полезные статьи о внутреннем устройстве питона2018-03-29T10:00:00+05:002018-03-29T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2018-03-29:/2018/03/29/python-internals/<p>Maybe, knowing Python internals is a thing you should know to be a good Python developer. However, if you want to improve your code it becomes more important. If you know it works under the hood, you write a code with less stupid mistakes and architecture issues.</p>
<p>Maybe, knowing Python internals is a thing you should know to be a good Python developer. However, if you want to improve your code it becomes more important. If you know it works under the hood, you write a code with less stupid mistakes and architecture issues.</p>
<h3>#1 <a href="http://pgbovine.net/cpython-internals.htm">CPython internals: A ten-hour codewalk through the Python interpreter source code</a></h3>
<blockquote>
<p>Here are nine lectures walking through the internals of CPython, the canonical Python interpreter implemented in C. They were from a dynamic programming languages course that I taught in Fall 2014 at the University of Rochester. The format isn't ideal, but I haven't seen this level of detail about CPython presented online, so I wanted to share these videos.</p>
</blockquote>
<h3>#2 <a href="https://dawranliou.com/getting-started-python-internals.html">Getting Started with Python Internals</a></h3>
<p>Getting Started with Python Internals is a summary of Philip Guo's codewalk. The main difference is that in this article Python 3.6 is used.</p>
<h3>#3 <a href="https://eli.thegreenplace.net/2010/06/30/python-internals-adding-a-new-statement-to-python/">Python internals: adding a new statement to Python</a></h3>
<blockquote>
<p>This article is an attempt to better understand how the front-end of Python works. Just reading documentation and source code may be a bit boring, so I'm taking a hands-on approach here: I'm going to add an until statement to Python.</p>
</blockquote>
<p>Eli Bendersky has written many interesting articles about Python internals. You can find them <a href="https://eli.thegreenplace.net/tag/python-internals">here</a></p>
<h3>#4 <a href="https://rushter.com/blog/python-garbage-collector/">Garbage collection in Python: things you need to know</a></h3>
<blockquote>
<p>Usually, you do not need to worry about memory management when the objects are no longer needed Python automatically reclaims the memory from them. However, understanding how GC works can help you write better Python programs.</p>
</blockquote>
<p>Like Eli Bendersky, Artem Golubin has written <a href="https://rushter.com/blog/tags/cpython/">several articles about CPython</a> that worth reading.</p>
<h3>Extra...</h3>
<h4>Pypy:</h4>
<ul>
<li>http://doc.pypy.org/en/latest/interpreter.html</li>
<li>http://doc.pypy.org/en/latest/cpython_differences.html</li>
<li>https://morepypy.blogspot.ru/2017/10/how-to-make-your-code-80-times-faster.html</li>
</ul>
<h4>Another list:</h4>
<ul>
<li>https://github.com/amygdalama/python-internals </li>
</ul>Computer Science Distilled2018-02-28T10:00:00+05:002018-02-28T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2018-02-28:/2018/02/28/computer-science-distilled/<p>Хотите найти небольшую простую книгу о компьютерных науках? Возможно, <a href="https://sourcemaking.com/computer-science-distilled">Computer Science Distilled</a> подойдет. К сожалению, только в том случае, если вы не планируете программировать профессионально. В этом случае, эта книга хороший вводный курс, хороший проводник в мир компьютерных наук. Но если вы профессиональный девелопер или студент, можно смело пропускать эту книгу.</p>
<p>Хотите найти небольшую простую книгу о компьютерных науках? Возможно, <a href="https://sourcemaking.com/computer-science-distilled">Computer Science Distilled</a> подойдет. К сожалению, только в том случае, если вы не планируете программировать профессионально. В этом случае, эта книга хороший вводный курс, хороший проводник в мир компьютерных наук. Но если вы профессиональный девелопер или студент, можно смело пропускать эту книгу.</p>
<blockquote>
<p>It’s a short book, written in plain, basic English. It presents ideas in their simplest form. It focuses on practical aspects of computer science that matter most: everyday things that directly impact the quality of your code.</p>
</blockquote>
<p>В этой книге 8 глав, покрывающих практически все аспекты компьютерных наук. Думаете в ней тысячи страниц? Нет. В книге их всего 170. Так как она может раскрыть все аспекты науки? Никак. Это всего лишь краткое введение в некоторые темы связанные с компьютерами и программированием.</p>
<p>Computer Science Distilled очень похожа на книги для любителей физики. В подобных книгах обычно нет ни формул, ни какой-либо математики, только словесное описание явлений. Главное назначение таких книг - развлечь читателей.</p>
<p>Думаю, главная проблема книги в том, что автор попросту пропускает детальное объяснение алгоритмов. Иногда он дает только графическое объяснение. Иногда нет даже его.</p>
<p>В любом случае, у этой книги есть своя аудитория, но, я надеюсь, не из числа профессиональных разработчиков. </p>Tuple[Callable, Any, ...]2018-01-29T10:00:00+05:002018-01-29T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2018-01-29:/2018/01/29/tuple-type/<p>Подсказки с типами могут сильно помочь в работе с большим проектом на Питоне. Тем не менее, иногда они требуют рефакторинга кода. Я писал об этом в прошлом году <a href="https://www.alexkorablev.ru/2017/12/11/typing-ci/">в этой статье</a>, но тогда я не смог найти хорошего примера иллюстрирующего то, что я хочу сказать.</p>
<p>Подсказки с типами могут сильно помочь в работе с большим проектом на Питоне. Тем не менее, иногда они требуют рефакторинга кода. Я писал об этом в прошлом году <a href="https://www.alexkorablev.ru/2017/12/11/typing-ci/">в этой статье</a>, но тогда я не смог найти хорошего примера иллюстрирующего то, что я хочу сказать.</p>
<p>Взгляните на этот код:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="n">workflow_step</span><span class="p">):</span>
<span class="n">func</span> <span class="o">=</span> <span class="n">step</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">step</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span>
<span class="k">return</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
</code></pre></div>
<p>Что можно понять о типах из этого кусочка? То, что <code>workflow_step</code> - это последовательность, первый элемент которой - <code>Callable</code> object, а остальные элементы, если таковые имеются, аргументы для этого вызываемого объекта. Предположим, что <code>workflow_step</code> - <code>Tuple</code>. Проблема в том, что тип <code>(Tuple) -> Any</code> для этой функции слишком общий и неинформативный. Можем мы вызвать ее с пустым таплом? <code>(1, 2, 3)</code> пройдет?</p>
<p><code>(Tuple[Callable, Any, ...]) -> Any</code> - много лучше. Вот только, такое определение не поддерживается синтаксисом библиотеки типов Питона. В простых случаях, одним из возможных выходов из этой ситуации будет использование типов вроде таких <code>Union[Tuple[Callable], Tuple[Callable, Any]]</code> или чего-то подобного. В более сложных случаях единственным возможным решением будет рефакторинг.</p>
<p>Есть много вариантов, как это сделать. К примеру, для функции из примера я использовал <code>NamedTuple</code>.</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">WorkflowStep</span><span class="p">(</span><span class="n">NamedTuple</span><span class="p">):</span>
<span class="nb">callable</span> <span class="c1"># type: Callable</span>
<span class="n">args</span> <span class="c1"># type: Tuple</span>
<span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="n">workflow_step</span><span class="p">):</span>
<span class="c1"># type: (WorkflowStep) -> Any</span>
<span class="k">return</span> <span class="n">step</span><span class="o">.</span><span class="n">callable</span><span class="p">(</span><span class="n">args</span><span class="o">.</span> <span class="n">args</span><span class="p">)</span>
</code></pre></div>
<p>Возможно, это не лучший кусок кода, который вы видели, тем не менее, он на много информативнее и структурированнее предыдущего варианта.</p>Как внедрить mypy в проекте на Python 2.72017-12-11T10:00:00+05:002017-12-11T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2017-12-11:/2017/12/11/typing-ci/<p>Я многократно писал, что описание типов в питоне помогает в работе с большими и средними проектами. При этом, если внедрять типы, то нужно регулярно делать статический анализ, причем в CI. А вот это уже не так-то просто внедрить. Эта статья - мой рассказ о том какие трудности придется преодолеть в процессе внедрения.</p>
<p>Я многократно писал, что описание типов в питоне помогает в работе с большими и средними проектами. При этом, если внедрять типы, то нужно регулярно делать статический анализ, причем в CI. А вот это уже не так-то просто внедрить. Эта статья - мой рассказ о том какие трудности придется преодолеть в процессе внедрения.</p>
<p>За то время, что я пытаюсь внедрить проверку типов в нашем проекте, я выработал несколько подходов к тому, как это можно сделать максимально быстро и безболезненно. На то, чтобы прийти к этому я убил не один день. Пользуйтесь моим опытом.</p>
<p>Стоит сразу предупредить, что большая часть боли связана с особенностями синтаксиса аннотации типов в Python 2.7. В Python 3.x часть проблем отпадет. Еще одна проблема — примеры. Довольно легко найти подходящие куски кода из реального проекта, но они под NDA и довольно громоздки. Маленькие примеры не показывают всю боль.</p>
<h2>Маленькие кусочки</h2>
<p>Не пытайтесь прописать типы сразу для всего проекта. Во-первых, если удастся сразу за разумное время привести в порядок типы для целого проекта, значит, типы вам, в общем-то, не были нужны. Во-вторых, если типы вам все-таки были нужны, то количество ошибок просто замедлит работу: прогресс не будет виден.</p>
<p>Лучше разбивать проект на небольшие кусочки. В идеале на те, которые легко поправить за один подход. К тому же это сократит время ожидания: проверка типов не мгновенна. К примеру, для меня нормальный размер - 10–15 ошибок за один подход. Так что я проверяю максимум один средний модуль. Не будет ничего страшного если «нарезать» проверку слишком тонко.</p>
<p>Править ошибки лучше снизу вверх: сначала я проверяю подмодули один за другим, потом их модуль родитель.</p>
<h2>Внедрите проверку типов в каждом PR</h2>
<p>Проверить типы для пары файлов не составит труда. Для десятка чуть сложнее, но также не смертельно. Редко какие PR бывают больше. Сделать проверку типов для файлов, затронутых в каждом пуллреквесте, не займет много времени. Но сам процесс описания типов в проекте пойдет заметно быстрее.</p>
<p>Приятный бонус от такого внедрения — быстрый поиск проблемных мест. Не всегда понятно где нужно делать проверку типов в первую очередь, какой модуль привести в порядок первым. Ошибки в измененных файлах PR практически всегда располагаются в небольшом количестве модулей.</p>
<p>Эти самые модули — первые кандидаты на пристальное изучение и аннотирование. В них ведется разработка прямо сейчас.</p>
<h2>Сначала слабые проверки, потом сильные</h2>
<p>Круто, конечно, сразу описать типы максимально подробно. Хочется сделать все сразу без всяких <code>Any</code>. Но вряд ли это реалистично. К тому же может потребоваться рефакторинг. Да, да. Иногда просто невозможно (ну хорошо, практически невозможно) удовлетворить анализатор тем кодом, что есть на текущий момент. Тогда требуется либо переписывать код, либо согласиться, что этот участок не проверяем.</p>
<p>При этом слабые проверки лучше, чем совсем отсутствующие. Более общие типы также лучше совсем отсутствующих. Зачастую указать для переменной или параметру максимально базовый класс лучше, чем мириться с тем, что про тип ничего не известно.</p>
<p>Any также иногда является разумным выходом из положения. Тем более что mypy позволяет настройками ужесточить проверки запретив Any. Так можно будет на следующей итерации более точно определить типы. Но не стоит тратить на точные определения силы в первом проходе. На этом этапе успешное завершение проверок важнее.</p>
<h2>Сначала CI, потом типы</h2>
<p>Еще одна моя ошибка — это внедрение типов до настройки автоматических проверок в CI. mypy на проекте без аннотации типов выдает довольно большое количество ошибок. В основном это «Need type annotation for variable» или присвоение переменной класса значения типа отличного от того, что присвоено ей в базовом типе.</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">Foo</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="n">boo</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">class</span> <span class="nc">Boo</span><span class="p">(</span><span class="n">Foo</span><span class="p">):</span>
<span class="n">boo</span> <span class="o">=</span> <span class="s1">'boo'</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="nx">example01</span><span class="p">.</span><span class="nx">py</span><span class="p">:</span><span class="mi">6</span><span class="p">:</span><span class="w"> </span><span class="nx">error</span><span class="p">:</span><span class="w"> </span><span class="nx">Incompatible</span><span class="w"> </span><span class="nx">types</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="nx">assignment</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="n">boo</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">foo</span> <span class="o">=</span> <span class="p">{}</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="n">example02</span><span class="o">.</span><span class="n">py</span><span class="p">:</span><span class="mi">1</span><span class="p">:</span><span class="w"> </span><span class="n">error</span><span class="p">:</span><span class="w"> </span><span class="n">Need</span><span class="w"> </span><span class="n">type</span><span class="w"> </span><span class="n">annotation</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">variable</span>
<span class="n">example02</span><span class="o">.</span><span class="n">py</span><span class="p">:</span><span class="mi">3</span><span class="p">:</span><span class="w"> </span><span class="n">error</span><span class="p">:</span><span class="w"> </span><span class="n">Need</span><span class="w"> </span><span class="n">type</span><span class="w"> </span><span class="n">annotation</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">variable</span>
</code></pre></div>
<p>Таких мест может быть много. Но с ними никаких проблем обычно не возникает. Аккуратно пройтись и прописать типы займет не больше пары дней для большого проекта.</p>
<p>Но вот если сначала описать типы, а потом внедрять CI, то ошибки будут более сложные.</p>
<p><strong>Во-первых, интерфейсы функций «убегают» от описания типов.</strong></p>
<p>В проектах на Python 2.7, где типы добавляются комментариями, такие ошибки возникают постоянно. С этим не так просто бороться, как кажется на первый взгляд. Понять из списка параметров какого типа они должны быть — сложно.</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">datetime</span>
<span class="k">def</span> <span class="nf">convert_to_timedelta</span><span class="p">(</span><span class="n">step</span><span class="p">):</span>
<span class="c1"># делает что-то и возвращает timedelta</span>
<span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">since</span><span class="p">,</span> <span class="n">until</span><span class="p">,</span> <span class="n">step</span><span class="p">):</span>
<span class="c1"># type: (datetime, datetime) -> Generator[datetime, None, None]</span>
<span class="n">step</span> <span class="o">=</span> <span class="n">convert_to_timedelta</span><span class="p">(</span><span class="n">step</span><span class="p">)</span>
<span class="k">while</span> <span class="n">since</span> <span class="o"><</span> <span class="n">until</span><span class="p">:</span>
<span class="k">yield</span> <span class="n">since</span>
<span class="n">since</span> <span class="o">+=</span> <span class="n">step</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code>example03.py:8: error: Type signature has too few arguments
</code></pre></div>
<p>Какой тип в этом контексте будет у <code>step</code>? Может это быть <code>timedelta</code>? Возможно. Может это быть <code>int</code>? Вполне. Все зависит от реализации <code>convert_to_timedelta</code>. Типы у этой функции еще не описаны, а у нашей уже есть неполное описание. Восстанавливать такие типы сложно.</p>
<p>Конечно, можно снести это описание, или в качестве типа для <code>step</code> выбрать <code>Any</code>. Что, по сути, будет шагом назад. Хотя в особо тяжелых случаях приходится поступать именно так.</p>
<p>Еще сложнее разобраться, когда новый параметр добавился не в конец, а в середину. Возьмем в качестве примера ту же самую функцию. Только в этот раз параметр step появился в середине.</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">since</span><span class="p">,</span> <span class="n">step</span><span class="p">,</span> <span class="n">until</span><span class="p">):</span>
<span class="c1"># type: (datetime, datetime) -> Generator[datetime, None, None]</span>
<span class="n">step</span> <span class="o">=</span> <span class="n">convert_to_timedelta</span><span class="p">(</span><span class="n">step</span><span class="p">)</span>
<span class="k">while</span> <span class="n">since</span> <span class="o"><</span> <span class="n">until</span><span class="p">:</span>
<span class="k">yield</span> <span class="n">since</span>
<span class="n">since</span> <span class="o">+=</span> <span class="n">step</span>
</code></pre></div>
<p>Не обсуждая, почему так произошло - это ведь только пример -, попробуем разобраться может step быть <code>datetime</code> или нет? В принципе, может. Тогда вроде бы <code>(datetime, datetime, datetime) -> datetime</code> - хороший тип для этой функции. После беглого взгляда на функцию возникает сомнение, правильно ли определены типы. </p>
<p>С реально функцией может так не повезти. И скорее всего не повезет. Придется искать ее вызовы и анализировать их, лезть внутрь реализации и отыскивать ключи для понимания типов ее параметров.</p>
<p><strong>Во вторых, без проверки не понятно насколько адекватно назначены типы параметрам.</strong></p>
<p>Не обсуждая, почему так произошло — это ведь только пример — попробуем разобраться может step быть <code>datetime</code> или нет? В принципе, может. Тогда вроде бы <code>(datetime, datetime, datetime) -> datetime</code> - хороший тип для этой функции. После беглого взгляда на функцию возникает сомнение, правильно ли определены типы.</p>
<p>С реально функцией может так не повезти. И, скорее всего, не повезет. Придется искать ее вызовы и анализировать их, лезть внутрь реализации и отыскивать ключи для понимания типов ее параметров.</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">since</span><span class="p">,</span> <span class="n">until</span><span class="p">,</span> <span class="n">step</span><span class="p">):</span>
<span class="c1"># type: (datetime, datetime, int) -> Generator[datetime, None, None]</span>
<span class="n">step</span> <span class="o">=</span> <span class="n">convert_to_timedelta</span><span class="p">(</span><span class="n">step</span><span class="p">)</span>
<span class="k">while</span> <span class="n">since</span> <span class="o"><</span> <span class="n">until</span><span class="p">:</span>
<span class="k">yield</span> <span class="n">since</span>
<span class="n">since</span> <span class="o">+=</span> <span class="n">step</span>
</code></pre></div>
<p>Потом решили, что <code>int</code> — не самый подходящий вариант для <code>step</code>, лучше подойдет <code>timedelta</code>. Во всех вызовах функции <code>foo</code> мы аккуратно поправили <code>int</code> на <code>timedelta</code>. Строчку с преобразованием из <code>int</code> в <code>timedelta</code> выкинули. Но как обычно это случается, забыли поправить тип у соответствующего параметра:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">since</span><span class="p">,</span> <span class="n">until</span><span class="p">,</span> <span class="n">step</span><span class="p">):</span>
<span class="c1"># type: (datetime, datetime, int) -> Generator[datetime, None, None]</span>
<span class="k">while</span> <span class="n">since</span> <span class="o"><</span> <span class="n">until</span><span class="p">:</span>
<span class="k">yield</span> <span class="n">since</span>
<span class="n">since</span> <span class="o">+=</span> <span class="n">step</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="n">example05</span><span class="o">.</span><span class="n">py</span><span class="p">:</span><span class="mi">9</span><span class="p">:</span><span class="w"> </span><span class="n">error</span><span class="p">:</span><span class="w"> </span><span class="n">Unsupported</span><span class="w"> </span><span class="n">operand</span><span class="w"> </span><span class="n">types</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="s2">"datetime"</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="s2">"int"</span><span class="p">)</span>
<span class="n">example05</span><span class="o">.</span><span class="n">py</span><span class="p">:</span><span class="mi">9</span><span class="p">:</span><span class="w"> </span><span class="n">error</span><span class="p">:</span><span class="w"> </span><span class="n">Incompatible</span><span class="w"> </span><span class="n">types</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="n">assignment</span><span class="w"> </span><span class="p">(</span><span class="n">expression</span><span class="w"> </span><span class="n">has</span><span class="w"> </span><span class="n">type</span><span class="w"> </span><span class="s2">"int"</span><span class="p">,</span><span class="w"> </span><span class="n">variable</span><span class="w"> </span><span class="n">has</span><span class="w"> </span><span class="n">type</span><span class="w"> </span><span class="s2">"datetime"</span><span class="p">)</span>
</code></pre></div>
<p>Даже в этом микроскопическом примере намеки <code>mypy</code> на <code>datetime</code> неверны. Правильный вариант будет такой:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">since</span><span class="p">,</span> <span class="n">until</span><span class="p">,</span> <span class="n">step</span><span class="p">):</span>
<span class="c1"># type: (datetime, datetime, timedelta) -> Generator[datetime, None, None]</span>
<span class="k">while</span> <span class="n">since</span> <span class="o"><</span> <span class="n">until</span><span class="p">:</span>
<span class="k">yield</span> <span class="n">since</span>
<span class="n">since</span> <span class="o">+=</span> <span class="n">step</span>
</code></pre></div>
<p>Так что делаем практически по TTD: сначала делаем проверку, добиваемся зеленого цвета, потом добавляем аннотации типов.</p>
<h2>Быть готовым отключать проверку типов для кусков кода</h2>
<p>К сожалению, не для всех библиотек есть описания типов. И хотя сообщество проделало просто гигантскую работу по составлению описаний типов для разных библиотек, не всегда эти описания полные.</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">email.mime.multipart</span> <span class="kn">import</span> <span class="n">MIMEMultipart</span>
<span class="kn">from</span> <span class="nn">email.mime.text</span> <span class="kn">import</span> <span class="n">MIMEText</span>
<span class="k">def</span> <span class="nf">compose_email</span><span class="p">():</span>
<span class="c1"># type: () -> MIMEMultipart</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">MIMEMultipart</span><span class="p">()</span>
<span class="n">msg</span><span class="p">[</span><span class="s1">'Subject'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'Subject'</span>
<span class="n">msg</span><span class="p">[</span><span class="s1">'From'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'test@domain.com'</span>
<span class="n">msg</span><span class="p">[</span><span class="s1">'To'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'test@domain.com'</span>
<span class="n">msg</span><span class="o">.</span><span class="n">attach</span><span class="p">(</span><span class="n">MIMEText</span><span class="p">(</span><span class="s1">'Body'</span><span class="p">))</span>
<span class="k">return</span> <span class="n">msg</span>
</code></pre></div>
<p>При проверке в mypy с ключом <code>--py2</code> выдает следующий набор ошибок:</p>
<div class="highlight"><pre><span></span><code><span class="n">example06</span><span class="p">.</span><span class="nl">py:</span><span class="mh">8</span><span class="o">:</span><span class="w"> </span><span class="nl">error:</span><span class="w"> </span><span class="n">Unsupported</span><span class="w"> </span><span class="n">target</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">indexed</span><span class="w"> </span><span class="n">assignment</span>
<span class="n">example06</span><span class="p">.</span><span class="nl">py:</span><span class="mh">9</span><span class="o">:</span><span class="w"> </span><span class="nl">error:</span><span class="w"> </span><span class="n">Unsupported</span><span class="w"> </span><span class="n">target</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">indexed</span><span class="w"> </span><span class="n">assignment</span>
<span class="n">example06</span><span class="p">.</span><span class="nl">py:</span><span class="mh">10</span><span class="o">:</span><span class="w"> </span><span class="nl">error:</span><span class="w"> </span><span class="n">Unsupported</span><span class="w"> </span><span class="n">target</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">indexed</span><span class="w"> </span><span class="n">assignment</span>
<span class="n">example06</span><span class="p">.</span><span class="nl">py:</span><span class="mh">11</span><span class="o">:</span><span class="w"> </span><span class="nl">error:</span><span class="w"> </span><span class="s">"MIMEMultipart"</span><span class="w"> </span><span class="n">has</span><span class="w"> </span><span class="n">no</span><span class="w"> </span><span class="n">attribute</span><span class="w"> </span><span class="s">"attach"</span>
</code></pre></div>
<p>И дело не в неправильном использовании библиотеки. Просто описание типов для этой библиотеки для Python 2 просто не доделана.</p>
<p>При возникновении подобных ошибок выхода два:</p>
<ul>
<li>Засучить рукава и сделать описание типов самостоятельно.</li>
<li>Отключить проверку типов для некоторых кусков кода.</li>
</ul>
<p>Конечно, было бы неплохо внести свою лепту в сообщество и прописать типы. Но для этого может просто не быть времени — надо же кому-то и фичи делать. В этом случае в библиотеке typing есть декораторы <code>@typing.no_type_check</code> и <code>@typing.no_type_check_decorator</code>, которыми можно выключить из проверки функцию, класс или декоратор. Для отельной строчки есть возможность использовать комментарий: <code># type: ignore</code>.</p>
<h2>То, что возвращает bool</h2>
<p>Давно вы приводили к <code>bool</code> возвращаемое значение функции, даже если используется она именно в таком контексте? Обычно подобные функции выглядят так:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Dict</span><span class="p">,</span> <span class="n">Any</span>
<span class="k">def</span> <span class="nf">predicate</span><span class="p">(</span><span class="n">elem</span><span class="p">):</span>
<span class="c1"># type: (Dict[str, Any]) -> bool</span>
<span class="k">return</span> <span class="n">elem</span> <span class="ow">and</span> <span class="s1">'something'</span> <span class="ow">in</span> <span class="n">elem</span>
</code></pre></div>
<p>В этом случае, тип ожидаемого возвращаемого значения <code>bool</code> для такой функции вполне подходящий. Он в полной мере определяет то, что мы от этой функции ждем. При этом фактический тип возвращаемого значения другой: <code>Union[Dict[str, Any], bool]</code>. Если внедряются типы, то придется переписывать функцию так:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Dict</span><span class="p">,</span> <span class="n">Any</span>
<span class="k">def</span> <span class="nf">predicate</span><span class="p">(</span><span class="n">elem</span><span class="p">):</span>
<span class="c1"># type: (Dict[str, Any]) -> bool</span>
<span class="k">return</span> <span class="nb">bool</span><span class="p">(</span><span class="n">elem</span> <span class="ow">and</span> <span class="s1">'something'</span> <span class="ow">in</span> <span class="n">elem</span><span class="p">)</span>
</code></pre></div>
<p>Хорошо это или плохо — не знаю. Выглядит такой код немного странно. При этом использование функции ограничивается, ее уже не получится использовать и как предикат, и как геттер.</p>
<p>В заключение хочется напомнить, что проверку типов стоит делать только для больших проектов. Внедрение типов для небольшого проекта имеет смысл, только если это библиотека.</p>Что почитать о asyncio. 9 полезных ссылок2017-10-20T10:00:00+05:002017-10-20T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2017-10-20:/2017/10/20/asyncio-articles/<p>Asyncio — полезная библиотека. Но разобраться с ней не так-то просто. Документации по большей части недостаточно. Хочется примеров. Хочется подробных объяснений. Последние несколько выходных я потратил на эксперименты с этой библиотекой. Во время чего в Pocket осели довольно интересные ссылки, которыми я хочу поделиться.</p>
<p>Asyncio — полезная библиотека. Но разобраться с ней не так-то просто. Документации по большей части недостаточно. Хочется примеров. Хочется подробных объяснений. Последние несколько выходных я потратил на эксперименты с этой библиотекой. Во время чего в Pocket осели довольно интересные ссылки, которыми я хочу поделиться.</p>
<p><em>#1 <a href="http://www.giantflyingsaucer.com/blog/?p=5557">Exploring Python 3’s Asyncio by Example</a></em></p>
<p>Короткое введение в asyncio и aiohttp на нескольких небольших примерах. Примеры сделаны максимально простыми. Используется синтаксис с декораторами — статья написана довольно давно. Как отмечает автор, эта статья показывает лишь верхушку айсберга.</p>
<p><em>#2 <a href="https://compiletoi.net/fast-scraping-in-python-with-asyncio/">Fast scraping in python with asyncio</a></em></p>
<p>Описание небольшого примера скрипта, проходящего по страничкам и вынимающего magnet-ссылку на Torrent из тела страницы. Статья также довольно старая. Включил я её в список из-за бонусного трека: с рассказом о том как не убить асинхронными запросами сервер. Хотя я бы использовал возможности aiohttp по ограничению количества запросов.</p>
<p><em>#3 <a href="http://journalpanic.com/post/wrapping-subprocesses-in-asyncio/">Wrapping Subprocesses in Asyncio</a></em></p>
<p>В этой статье рассматривается довольно интересный и необычный кейс для asyncio: выстраивается обработка стандартного вывода подпроцесса. О таком использовании asyncio я бы даже не подумал. Однако, у автора получился довольно интересный код.</p>
<p><em>#4 <a href="http://www.aosabook.org/en/500L/a-web-crawler-with-asyncio-coroutines.html">A Web Crawler With asyncio Coroutines</a> by A. Jesse Jiryu Davis and Guido van Rossum</em></p>
<p>Подробный разбор того, как на Python используя asyncio написать «выкачивалку интернета». На мой взгляд, в этой статье слишком много воды и отступлений (к примеру, на то, чтобы разобрать как работают генераторы). Вообще, эта статья не самое лучшее объяснение как работать с asyncio. Оставлена в списке исключительно из-за автора.</p>
<p><em>#5 <a href="https://pawelmhm.github.io/asyncio/python/aiohttp/2016/04/22/asyncio-aiohttp.html">Making 1 million requests with python-aiohttp</a> by Paweł Miech</em></p>
<p>Автор этой статьи написал свой микро-бенчмарк для aiohttp. И ему удалось выжать скорости в 111 111 запросов в минуту на абсолютно бесполезной задаче. Круто. Но не в этом достоинство статьи. Автор довольно подробно объясняет как пользоваться aiohttp и какие подводные камни есть.</p>
<p><em>#6 <a href="https://www.pythonsheets.com/notes/python-asyncio.html">Python asyncio cheatsheet</a></em></p>
<p>Куски кода с примерами того, как использовать те или иные аспекты библиотеки. Пожалуй, поиски решения своих задач стоит начинать именно отсюда.</p>
<p><em>#7 <a href="http://lucumr.pocoo.org/2016/10/30/i-dont-understand-asyncio/">I don't understand Python's Asyncio</a> by Armin Ronacher</em></p>
<p>В этой статье Армин жалуется на сложность и многообразие концепций asyncio. В этом есть здравое зерно. Хотя мне кажется, большая часть сложности возникает из-за того, что ещё не выработались нормальные подходы использованию библиотеки.</p>
<p><em>#8 <a href="https://hackernoon.com/asyncio-for-the-working-python-developer-5c468e6e2e8e">AsyncIO for the Working Python Developer</a> by Yeray Diaz</em></p>
<p>Ещё один учебник asyncio. В примерах используется новый синтаксис. Все примеры довольно подробно объяснены.</p>
<p><em>#9 <a href="https://medium.com/python-pandemonium/asyncio-coroutine-patterns-beyond-await-a6121486656f">Asyncio Coroutine Patterns: Beyond await</a> by Yeray Diaz</em></p>
<p>Ещё одна статья того же автора. Эта ориентирована на более опытных разработчиков. В ней рассказывается о нескольких паттернах использования asyncio: рекурсивных корутинах, «запусти и забудь» (автор использует термин fire and forget, что является отсылкой к <a href="https://en.wikipedia.org/wiki/Fire-and-forget">неуправляемым реактивным снарядам</a>) и некоторые другие.</p>
<p><em>Bonus:</em> <a href="https://www.datacamp.com/community/tutorials/asyncio-introduction">Asyncio: An Introduction</a> by Mike Driscoll</p>
<p>Ещё один учебник. Вдруг не хватило тех, что я перечислил выше.</p>Поддержка целостности проекта в Python требует усилий2017-10-06T19:00:00+05:002017-10-06T19:00:00+05:00akorablevtag:www.alexkorablev.ru,2017-10-06:/2017/10/06/python-project-integrity/<p>Python позволяет программисту довольно много. Это очень выразительный язык. Его динамическая природа даёт возможность делать элегантные решения, которые довольно сложно представить, к примеру, в Java.</p>
<p>Тем не менее за все надо платить. При росте кодовой базы, проекты на Python начитают требовать всё больше и больше ресурсов на поддержание своей целостности. Без некоторых усилий проект начинает разваливаться на куски.</p>
<p>Python позволяет программисту довольно много. Это очень выразительный язык. Его динамическая природа даёт возможность делать элегантные решения, которые довольно сложно представить, к примеру, в Java.</p>
<p>Тем не менее за все надо платить. При росте кодовой базы, проекты на Python начитают требовать всё больше и больше ресурсов на поддержание своей целостности. Без некоторых усилий проект начинает разваливаться на куски.</p>
<h2>Первая проблема — навигация по коду</h2>
<p>Основная проблемная часть — навигация по коду. На начальном этапе её зачастую приносят в жертву более компактному решению, что впоследствии обязательно приведёт к ряду проблем. Главная из которых — высокая сложность поиска причин ошибок.</p>
<p>Traceback в Python не самый полезный инструмент: добавьте пару декораторов, парочку лямбд, что-нибудь из itertools и можете распрощаться возможностью проследить вызовы функций просто прочитав трейсбек. Придётся вооружиться грепом, принтами и дебаггером. Что приводит к потерям часов на поиск проблемы.</p>
<p>Сложностью отладки никого не удивишь. Да, сложно. Да, долго. Но ведь всё равно доберёмся до проблемы. Гораздо хуже когда затык происходит в момент написания программы. Ты вроде бы вспомнил, как работает тот или иной кусок кода. Для полной картины не хватает буквально пары переходов...</p>
<p>И тогда найденная с трудом ниточка вызовов обрывается где-то посередине, когда IDE сдаётся и не может предложить ничего подходящего для дальнейшего перехода или, наоборот, предлагает слишком много вариантов.</p>
<p>Конечно, подобное случается в самом сложном и запутанном месте системы. Конечно, 3 года назад — это был самый элегантный и понятный кусок системы. Конечно, за это время он оброс «жирком», обрюзг. И уже неважно кто придумал так написать. Разбираться с этим всё равно экстремально сложно.</p>
<h2>Что делать?</h2>
<p>Как с этим бороться? У меня нет хорошего ответа на этот вопрос. Кажется, стоит следовать дзену питона: явное лучше неявного, даже если это означает немного больше кода. Не бойтесь добавить немного "духа Java" в свой проект. Если вернуться к более серьёзному обсуждению, то следуйте двум этим правилам:</p>
<h3>Правило №1: На то, чтобы сломать навигацию по коду, должна быть хорошая причина</h3>
<p>Действительно, навигацию по коду можно сломать миллионом разных способов. Но для этого должна быть очень веская причина. Простого желания сделать красивый и элегантный код недостаточно. Только когда код получается сильно сложнее, тогда можно подумать о более хитром решении.</p>
<p>Главное помнить о том, что код читается гораздо чаще, чем пишется. Сохранить его читаемость, понятность и связность - это первый приоритет.</p>
<h3>Правило №2: Места где ломается навигация по коду должны логгироваться очень тщательно</h3>
<p>Хитрые решения нужно логгировать особо тщательно. В логи должны попадать все существенные детали о том, что и в каком месте сломалось. Проверяете валидность запроса? Логгируйте какой валидатор на каком куске какого запроса сломался. Разбираете XML? Логгируйте на каком куске данных и почему сломались. Неплохо добавлять в логи разного рода id: запроса, сессии, процесса или чего-то ещё в этом роде — это поможет потом выстроить историю работы системы.</p>
<h3>Типы и проверка типов могут помочь, но на них не стоит сильно рассчитывать</h3>
<p>Как дополнительный рубеж обороны можно попробовать использовать аннотации типов. Это помогает IDE (как минимум PyCharm) подсказывать более адекватные переходы. Статический анализ типов позволяет выявлять довольно хитрые ошибки, которые поймать любым другим способом довольно сложно.</p>
<p>Но вводить типизацию кода больно, особенно на большом проекте. Это требует обязательных проверок на каждый PR: помимо тестов ваш сервер CI должен делать ещё и статический анализ типов. Нельзя полагаться на запуски вручную — это закончится тем, что проверки просто не будут делаться.</p>
<h3>Инструменты</h3>
<p>Не могу посоветовать никаких особых инструментов для проверки связности проекта. Да и нет, наверное, таких метрик. Все это на уровне ощущений. Конечно, mypy для проверки типов незаменим. Но в остальном приходится полагаться на своё чутье.</p>
<p>Тесты помогают. Но не маленькие юнит-тесты, а тесты покрупнее, трестирующие взаимодействие нескольких подсистем. Во время написания таких тестов, я довольно часто нахожу куски кода, где связность и простота навигации дают сбой.</p>
<p>Хороший результат даёт код ревью. Чтобы понять чужой код, приходится походить по коду. А ещё лучше когда приходится поискать что-нибудь по проекту. Вот здесь все хитрые решения проявляются во всей красе. Для меня сигналом к тому, что что-то пошло не так, является сильное желание переключиться на ветку в IDE и продолжить ревью там.</p>
<p>Не бойтесь прямолинейных решений, которые просто читать и легко понимаются IDE. Они сохранят и нервы, и время всей команды.</p>Хэш-функция для функции в Python2017-09-12T19:00:00+05:002017-09-12T19:00:00+05:00akorablevtag:www.alexkorablev.ru,2017-09-12:/2017/09/12/function-hash/<p>Пару недель назад один из моих коллег задал вопрос: можно ли использовать функцию в качестве ключей словаря? Да, можно. У каждой функции в Питоне есть хеш. Но как он считается? На основе имени функции? На основе байт-кода? В действительности, хэш считается трансформацией над указателем на объект функции. Тем не менее, не так-то легко отыскать эти расчеты в коде CPython.</p>
<p>Пару недель назад один из моих коллег задал вопрос: можно ли использовать функцию в качестве ключей словаря? Да, можно. У каждой функции в Питоне есть хеш. Но как он считается? На основе имени функции? На основе байт-кода? В действительности, хэш считается трансформацией над указателем на объект функции. Тем не менее, не так-то легко отыскать эти расчеты в коде CPython.</p>
<p>Навигация по C-коду довольно интересный опыт, хотя делать это совсем не просто: все эти макросы делают простой поиск нужных переходов довольно сложным. Конечно, я уверен, что вы сможете с этим справиться и найти все вызываемые функции при запросе хеша для объекта функции.</p>
<p>Но давайте поступим немного по-другому. Мы пройдем всего пару вызовов, что бы понять как CPython работает, а затем поговорим об устройстве CPython, что бы не нужно было искать потерянные вызовы в куче макросов. </p>
<p>Я буду использовать master-ветку официального <a href="https://github.com/python/cpython">репозитория CPython</a>. Вы можете его клонировать или исследовать в браузере. Объект функции описан в файле <a href="https://github.com/python/cpython/blob/9648088e6ccd6d0cc04f450f55628fd8eda3784c/Objects/funcobject.c">funcobject.c</a>. Наиболее интересная нам часть вот эта:</p>
<div class="highlight"><pre><span></span><code><span class="n">PyTypeObject</span><span class="w"> </span><span class="n">PyFunction_Type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="n">PyVarObject_HEAD_INIT</span><span class="p">(</span><span class="o">&</span><span class="n">PyType_Type</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span>
<span class="w"> </span><span class="s">"function"</span><span class="p">,</span>
<span class="w"> </span><span class="k">sizeof</span><span class="p">(</span><span class="n">PyFunctionObject</span><span class="p">),</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span>
<span class="w"> </span><span class="p">(</span><span class="n">destructor</span><span class="p">)</span><span class="n">func_dealloc</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_dealloc */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_print */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_getattr */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_setattr */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_reserved */</span>
<span class="w"> </span><span class="p">(</span><span class="n">reprfunc</span><span class="p">)</span><span class="n">func_repr</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_repr */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_as_number */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_as_sequence */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_as_mapping */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_hash */</span>
<span class="w"> </span><span class="n">function_call</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_call */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_str */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_getattro */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_setattro */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_as_buffer */</span>
<span class="w"> </span><span class="n">Py_TPFLAGS_DEFAULT</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Py_TPFLAGS_HAVE_GC</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_flags */</span>
<span class="w"> </span><span class="n">func_new__doc__</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_doc */</span>
<span class="w"> </span><span class="p">(</span><span class="n">traverseproc</span><span class="p">)</span><span class="n">func_traverse</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_traverse */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_clear */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_richcompare */</span>
<span class="w"> </span><span class="n">offsetof</span><span class="p">(</span><span class="n">PyFunctionObject</span><span class="p">,</span><span class="w"> </span><span class="n">func_weakreflist</span><span class="p">),</span><span class="w"> </span><span class="cm">/* tp_weaklistoffset */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_iter */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_iternext */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_methods */</span>
<span class="w"> </span><span class="n">func_memberlist</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_members */</span>
<span class="w"> </span><span class="n">func_getsetlist</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_getset */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_base */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_dict */</span>
<span class="w"> </span><span class="n">func_descr_get</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_descr_get */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_descr_set */</span>
<span class="w"> </span><span class="n">offsetof</span><span class="p">(</span><span class="n">PyFunctionObject</span><span class="p">,</span><span class="w"> </span><span class="n">func_dict</span><span class="p">),</span><span class="w"> </span><span class="cm">/* tp_dictoffset */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_init */</span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_alloc */</span>
<span class="w"> </span><span class="n">func_new</span><span class="p">,</span><span class="w"> </span><span class="cm">/* tp_new */</span>
<span class="p">};</span>
</code></pre></div>
<p>Это декларация типа функции. Каждая строка помеченная комментарием <code>tp_something</code> - указатель на C функцию, которая делает что-либо специфичное для заданного типа. При этом, если указан 0, то это значит, что будет вызываться унаследованная функция. В нашем случае для <code>tp_hash</code> указан именно 0.</p>
<p>Найти эту функцию, которая будет вызываться по умолчанию, не просто. В файле есть всего одно место где используется структура <code>PyFunction_Type</code>: она фигурирует в качестве параметра вызова макроса <code>PyObject_GC_New</code> внутри <code>PyFunction_NewWithQualName</code>.</p>
<p>Хотя, как я уже говорил, походить по сишному коду может быть интересным занятием, мы воспользуемся коротким путем. Если просмотреть документ <a href="http://ref.readthedocs.io/en/latest/understanding_python/type_system/PyTypeObject.html">"CPython’s PyTypeObject"</a>, то становится более или менее понятно как организован и как работает PyTypeObject. В этом же документе можно узнать, что функцию по умолчанию для <code>tp_hash</code> нужно искать в <code>PyBaseObject_Type.tp_hash</code> и что значение по умолчанию равно <code>_Py_HashPointer</code>.</p>
<p>Теперь мы легко найдем нужную нам функцию:</p>
<div class="highlight"><pre><span></span><code><span class="n">Py_hash_t</span>
<span class="nf">_Py_HashPointer</span><span class="p">(</span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="n">p</span><span class="p">)</span>
<span class="p">{</span>
<span class="w"> </span><span class="n">Py_hash_t</span><span class="w"> </span><span class="n">x</span><span class="p">;</span>
<span class="w"> </span><span class="kt">size_t</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="kt">size_t</span><span class="p">)</span><span class="n">p</span><span class="p">;</span>
<span class="w"> </span><span class="cm">/* bottom 3 or 4 bits are likely to be 0; rotate y by 4 to avoid</span>
<span class="cm"> excessive hash collisions for dicts and sets */</span>
<span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">y</span><span class="w"> </span><span class="o">>></span><span class="w"> </span><span class="mi">4</span><span class="p">)</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="p">(</span><span class="n">y</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="p">(</span><span class="mi">8</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">SIZEOF_VOID_P</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">4</span><span class="p">));</span>
<span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">Py_hash_t</span><span class="p">)</span><span class="n">y</span><span class="p">;</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">x</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">-1</span><span class="p">)</span>
<span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">-2</span><span class="p">;</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">x</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>Как видно, никакого полного имени функции не используется. Хэш функции - это всего лишь значение зависящее от значения указателя на объект функции.</p>
<p>Это было довольно интересное погружение в CPython. Конечно, я использовал "короткий путь". Тем не менее даже он требует некоторых знаний того, как устроен интерпретатор изнутри. Пожалуй лучший способ разобраться с этим посмотреть видео <a href="https://www.youtube.com/playlist?list=PLzV58Zm8FuBL6OAv1Yu6AwXZrnsFbbR0S">CPython Internals</a>.</p>Success in Programming. Обзор Книги2017-08-17T10:00:00+05:002017-08-17T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2017-08-17:/2017/08/17/success-in-programming/<p>Персональный бренд - тема не так часто обсуждаемая среди программистов. В действительности, я знаю всего несколько людей, обладающих сильным персональным брендом, и всего двоих, кто рассказывает о построении личного бренда программистам. Один из них - Фредерик Харпер (Frederic Harper) с его книгой “<a href="https://www.amazon.com/Success-Programming-Recognition-Influence-Personal-ebook/dp/B00MUUI67C">Success in Programming</a>.”</p>
<p>Персональный бренд - тема не так часто обсуждаемая среди программистов. В действительности, я знаю всего несколько людей, обладающих сильным персональным брендом, и всего двоих, кто рассказывает о построении личного бренда программистам. Один из них - Фредерик Харпер (Frederic Harper) с его книгой “<a href="https://www.amazon.com/Success-Programming-Recognition-Influence-Personal-ebook/dp/B00MUUI67C">Success in Programming</a>.”</p>
<h2>Почему персональный бренд важен?</h2>
<p>OK. Вы знаете кого-нибудь, кто бы получил работу в крупной технологической компании без прохождения ада интервью? Я знаю одного. Хедхантеры одной подобной компании умоляли его уделить им минуту. И не для того что бы начать долгий процесс из серии интервью. Нет. Только для того, что бы обсудить с ним над каким бы их проектом он хотел бы работать.</p>
<p>Он в общем-то не гений. Он - умный и опытный разработчик. Тем не менее, есть много более опытных и умных программистов. Так почему же компания так сильно пыталась нанять именно его? Ответ, который я нашел, - у него был сильный персональный бренд. Вот и все.</p>
<p>Насколько мне известно, он работает над своим брэндом уже порядка 9 или 10 лет. У него есть блог, подкаст, великолепный аккаунт на GitHub. Он вкладывается в налаживание контактов.</p>
<p>Да же я, после пары лет блоггинга и нетворкинга, имею более сильные позиции по сравнению со средним бэкенд разработчиком. При этом я не могу сказать, что у меня есть сильный бренд. Тем не менее, я уже получаю первые результаты от работы над ним.</p>
<h2>Success in Programming</h2>
<p>Брэндинг важен. Есть простой путь построить его? И да, и нет. В книге Фредерика вы найдете много советов, как добиться поставленной цели с наименьшими усилиями. При этом, есть один наиболее важный и наиболее трудный момент - найти свою идентичность. Без понимания этой идентичности невозможно построить сильный бренд. Первая половина книги Фредерика посвящена как раз этому.</p>
<p>В первом приближении, ответить нужно всего на два "простых" вопроса:</p>
<ul>
<li>Что вы хотите делать?</li>
<li>Что вы делаете? </li>
</ul>
<p>Оба эти вопроса о ваших целях, желания, стремлениях, мечтах и вашем характере. Мне было не легко найти ответы на эти вопросы. К счастью, в книге есть несколько советов, которые помогут продвинуться по этому пути. К примеру, я написал twitter bio - мою краткую презентацию длиной в 140 символов.</p>
<p>Мои “85% Python, 15% Java, 100% Backend” формируют основу моего бренда. Это не финальная версия, я все еще работаю над ней. Тем не менее, используя эту короткую фразу, я более сфокусирован. Между прочим, ваш бренд это не что-то неизменное. Это не значит, что если сегодня вы продвигаете себя как бэкенд разработчик, то завтра вы не сможете стать программистом под андроид..</p>
<p>В книге очень много трудных вопросов. К счастью, когда ответ на них находится, остальное становится более простым. К примеру, становится более понятно как использовать те же социальные сети. По крайней мере становится ячным как они могут быть полезны для вас.</p>
<p>К сожалению, вторая половина книги не так хороша, как первая. Возможно из-за того, что читая ее, в голове уже есть ответа и понимание что делать дальше. Тем не менее, в этой части рассматривается несколько интересных и полезных тем, к примеру, параграф "как сказать нет" с блок-схемой алгоритма.</p>
<p>Хотя иногда иногда на страницах слишком много Харпера, это действительно полезная книга. Здесь полно пошаговых инструкций для программистов как построить свой персональный бренд.</p>Структуры данных и алгоритмы в Java (2-е издание)2017-08-15T19:00:00+05:002017-08-15T19:00:00+05:00akorablevtag:www.alexkorablev.ru,2017-08-15:/2017/08/15/lafore-java/<p>Для меня основное достоинство книги "<a href="https://www.piter.com/product/struktury-dannyh-i-algoritmy-v-java-klassika-computers-science-2-e-izd">Структуры данных и алгоритмы в Java</a>" - язык которым автор описывает алгоритмы. Он не использует сложный академический язык, приправленный тонной высшей математики. Роберт Лафоре использует простой язык и пытается дать максимально простое объяснение, какое только возможно, каждому алгоритму.</p>
<p>Для меня основное достоинство книги "<a href="https://www.piter.com/product/struktury-dannyh-i-algoritmy-v-java-klassika-computers-science-2-e-izd">Структуры данных и алгоритмы в Java</a>" - язык которым автор описывает алгоритмы. Он не использует сложный академический язык, приправленный тонной высшей математики. Роберт Лафоре использует простой язык и пытается дать максимально простое объяснение, какое только возможно, каждому алгоритму.</p>
<h2>Почему эта книга?</h2>
<p>Эта книга для занятых (или ленивых) студентов или программистов, кто решил повторить алгоритмы перед техническим интервью. Книгу можно просмотреть очень быстро: читаем список идей после каждой статьи, просматриваем примеры кода. Если что-то показалось непонятным, возвращаемся к основному тексту главы.</p>
<p>С другой стороны, если читать книгу от корки до корки делая все упражнения, можно добиться заметного прогресса в освоении материала. К тому же в книге есть не только текстовое описание алгоритмов и их программный код, но и Java приложение для демонстрации их работы. </p>
<p>Как я отмечал выше, еще одна причина читать эту книгу - простой язык. И оригинал, и перевод отличаются максимально ясными объяснениями работы каждого алгоритма. И если после прочтения пары страниц Кнута, вы засыпаете, эта книга для вас. </p>
<h2>Можно что-нибудь улучшить?</h2>
<p>Алгоритмы - очень консервативная тема. Не многое изменилось с того момента, как книга была опубликована. При этом Java изменилась значительно. В ней появились новые синтаксические конструкции. В ней появились новые подходы к написанию хорошего кода. Обновились правила оформления кода.</p>
<p>Поймите меня правильно. Все примеры из книги все еще работающий Java код. Тем не менее, это уже не современная Java. Я не хочу видеть подобный код в продакшене. Почему важно использовать самые свежие подходы в коде примеров? Из-за того, что книги, подобные этой, читают преимущественно студенты и начинающие программисты. Они будут копировать этот стиль в свои проекты.</p>
<p>Возможно, лучше использовать псевдокод в примерах, как это делают Кнут и Кормен. Конечно, это добавит дополнительную сложность для читающих книгу: в этом случае студенту придется переводить псевдокод на обычный язык программирования. В любом случае, это увеличит шансы, что студент выучится использовать его язык программирования правильно.</p>
<h3>Плюсы:</h3>
<ul>
<li>Очень простой язык</li>
<li>Хороший набор алгоритмов</li>
<li>Каждая статья заканчивается списком идей</li>
</ul>
<h3>Минусы:</h3>
<ul>
<li>Довольно старая</li>
<li>Странное форматирование примеров кода</li>
<li>Некоторые алгоритмы рассматриваются без примеров кода, только словесное объяснение</li>
</ul>Горячие клавиши Emacs в русской раскладке2017-06-10T10:00:00+05:002017-06-10T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2017-06-10:/2017/06/10/emacs-got-keys/<p>Думаю вы как и я не можете позволить себе пользоваться только английской раскладкой клавиатуры. Так или иначе для работы приходится использовать обе: и русскую, и английскую. В целом, это не доставляет никаких проблем, пока дело не касается Emacs. Не знаю почему, но в нем горячие клавиши в русской раскладке просто перестают работать. Видимо, сказывается страшное наследие "ASCII - единственно возможный набор символов для всех языков мира". Возможно, есть какие-то другие причины. Тем не менее, в Emacs есть свой довольно странный способ переключение раскладок (Input Method в терминах Emacs): <code>C-\</code>. Я никогда не находил этот вариант приемлемым и переключался на английскую раскладку каждый раз. Но я все-таки нашел способ как избежать этого и заставить горячие клавиши работать вне зависимости от раскладки клавиатуры. </p>
<p>Думаю вы как и я не можете позволить себе пользоваться только английской раскладкой клавиатуры. Так или иначе для работы приходится использовать обе: и русскую, и английскую. В целом, это не доставляет никаких проблем, пока дело не касается Emacs. Не знаю почему, но в нем горячие клавиши в русской раскладке просто перестают работать. Видимо, сказывается страшное наследие "ASCII - единственно возможный набор символов для всех языков мира". Возможно, есть какие-то другие причины. Тем не менее, в Emacs есть свой довольно странный способ переключение раскладок (Input Method в терминах Emacs): <code>C-\</code>. Я никогда не находил этот вариант приемлемым и переключался на английскую раскладку каждый раз. Но я все-таки нашел способ как избежать этого и заставить горячие клавиши работать вне зависимости от раскладки клавиатуры. </p>
<p>Этот сниппет взят с небольшими изменениями из поста написанного <a href="https://twitter.com/novovladimir">Владимиром</a> "<a href="http://reangdblog.blogspot.com/2015/05/emacs.html">Горячие клавиши Emacs в русской раскладке</a>". </p>
<p>Для того чтобы использовать его, вставьте его в конфигурационный файл вашего Емакса:</p>
<div class="highlight"><pre><span></span><code><span class="p">(</span><span class="nb">defun</span><span class="w"> </span><span class="nv">reverse-input-method</span><span class="w"> </span><span class="p">(</span><span class="nv">input-method</span><span class="p">)</span>
<span class="w"> </span><span class="s">"Build the reverse mapping of single letters from INPUT-METHOD."</span>
<span class="w"> </span><span class="p">(</span><span class="k">interactive</span>
<span class="w"> </span><span class="p">(</span><span class="nf">list</span><span class="w"> </span><span class="p">(</span><span class="nv">read-input-method-name</span><span class="w"> </span><span class="s">"Use input method (default current): "</span><span class="p">)))</span>
<span class="w"> </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="k">and</span><span class="w"> </span><span class="nv">input-method</span><span class="w"> </span><span class="p">(</span><span class="nf">symbolp</span><span class="w"> </span><span class="nv">input-method</span><span class="p">))</span>
<span class="w"> </span><span class="p">(</span><span class="k">setq</span><span class="w"> </span><span class="nv">input-method</span><span class="w"> </span><span class="p">(</span><span class="nf">symbol-name</span><span class="w"> </span><span class="nv">input-method</span><span class="p">)))</span>
<span class="w"> </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">((</span><span class="nv">current</span><span class="w"> </span><span class="nv">current-input-method</span><span class="p">)</span>
<span class="w"> </span><span class="p">(</span><span class="nv">modifiers</span><span class="w"> </span><span class="o">'</span><span class="p">(</span><span class="no">nil</span><span class="w"> </span><span class="p">(</span><span class="nv">control</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nv">meta</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nv">control</span><span class="w"> </span><span class="nv">meta</span><span class="p">))))</span>
<span class="w"> </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="nv">input-method</span>
<span class="w"> </span><span class="p">(</span><span class="nv">activate-input-method</span><span class="w"> </span><span class="nv">input-method</span><span class="p">))</span>
<span class="w"> </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="k">and</span><span class="w"> </span><span class="nv">current-input-method</span><span class="w"> </span><span class="nv">quail-keyboard-layout</span><span class="p">)</span>
<span class="w"> </span><span class="p">(</span><span class="nb">dolist</span><span class="w"> </span><span class="p">(</span><span class="nv">map</span><span class="w"> </span><span class="p">(</span><span class="nf">cdr</span><span class="w"> </span><span class="p">(</span><span class="nv">quail-map</span><span class="p">)))</span>
<span class="w"> </span><span class="p">(</span><span class="k">let*</span><span class="w"> </span><span class="p">((</span><span class="nv">to</span><span class="w"> </span><span class="p">(</span><span class="nf">car</span><span class="w"> </span><span class="nv">map</span><span class="p">))</span>
<span class="w"> </span><span class="p">(</span><span class="nv">from</span><span class="w"> </span><span class="p">(</span><span class="nv">quail-get-translation</span>
<span class="w"> </span><span class="p">(</span><span class="nv">cadr</span><span class="w"> </span><span class="nv">map</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">char-to-string</span><span class="w"> </span><span class="nv">to</span><span class="p">)</span><span class="w"> </span><span class="mi">1</span><span class="p">)))</span>
<span class="w"> </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="k">and</span><span class="w"> </span><span class="p">(</span><span class="nf">characterp</span><span class="w"> </span><span class="nv">from</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">characterp</span><span class="w"> </span><span class="nv">to</span><span class="p">))</span>
<span class="w"> </span><span class="p">(</span><span class="nb">dolist</span><span class="w"> </span><span class="p">(</span><span class="nf">mod</span><span class="w"> </span><span class="nv">modifiers</span><span class="p">)</span>
<span class="w"> </span><span class="p">(</span><span class="nf">define-key</span><span class="w"> </span><span class="nv">local-function-key-map</span>
<span class="w"> </span><span class="p">(</span><span class="nf">vector</span><span class="w"> </span><span class="p">(</span><span class="nf">append</span><span class="w"> </span><span class="nf">mod</span><span class="w"> </span><span class="p">(</span><span class="nf">list</span><span class="w"> </span><span class="nv">from</span><span class="p">)))</span>
<span class="w"> </span><span class="p">(</span><span class="nf">vector</span><span class="w"> </span><span class="p">(</span><span class="nf">append</span><span class="w"> </span><span class="nf">mod</span><span class="w"> </span><span class="p">(</span><span class="nf">list</span><span class="w"> </span><span class="nv">to</span><span class="p">)))))))))</span>
<span class="w"> </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="nv">input-method</span>
<span class="w"> </span><span class="p">(</span><span class="nv">activate-input-method</span><span class="w"> </span><span class="nv">current</span><span class="p">))))</span>
<span class="p">(</span><span class="nv">reverse-input-method</span><span class="w"> </span><span class="ss">'russian-computer</span><span class="p">)</span>
</code></pre></div>
<p>Если вы пользуетесь Spacemacs user, то вставьте следующий код в список <code>dotspacemacs-configuration-layers</code> в вашем файле <code>.spacemacs</code>.</p>
<div class="highlight"><pre><span></span><code><span class="p">(</span><span class="nv">reverse-input-method</span>
<span class="w"> </span><span class="nb">:location</span><span class="w"> </span><span class="p">(</span><span class="nv">recipe</span>
<span class="w"> </span><span class="nb">:fetcher</span><span class="w"> </span><span class="nv">github</span>
<span class="w"> </span><span class="nb">:repo</span><span class="w"> </span><span class="s">"avkorablev/reverse-input-method-layer"</span><span class="p">))</span>
<span class="w"> </span><span class="p">)</span>
</code></pre></div>
<p>и этот код в тело функции <code>dotspacemacs/user-config</code></p>
<div class="highlight"><pre><span></span><code><span class="p">(</span><span class="nv">reverse-input-method</span><span class="w"> </span><span class="ss">'russian-computer</span><span class="p">)</span>
</code></pre></div>
<p>Работает идеально. Единственный недостаток обнаружился при пересчете формулы в таблицах OrgTable. На эту операцию используется хоткей <code>C-c-*</code>, а на моем маке <code>*</code> меняет свое положение в русской раскладке.</p>Property в классе наследнике2017-05-30T11:00:00+05:002017-05-30T11:00:00+05:00akorablevtag:www.alexkorablev.ru,2017-05-30:/2017/05/30/property-and-inheritance/<p>Кажется очевидным, что property можно переопределить в классе наследнике и вызвать super внутри этого переопределенного свойства. Вроде бы звучит нормально? Да, так и есть. Тем не менее, когда мой коллега задал мне такой вопрос, я "завис" на некоторое время. Возможно я излишне подозрителен, но каждый раз когда в коде есть какая-то магия, я ожидаю подвоха. Поэтому, я решил немного поэкспериментировать и доказать себе, что проперти работают так как ожидается.</p>
<p>Кажется очевидным, что property можно переопределить в классе наследнике и вызвать super внутри этого переопределенного свойства. Вроде бы звучит нормально? Да, так и есть. Тем не менее, когда мой коллега задал мне такой вопрос, я "завис" на некоторое время. Возможно я излишне подозрителен, но каждый раз когда в коде есть какая-то магия, я ожидаю подвоха. Поэтому, я решил немного поэкспериментировать и доказать себе, что проперти работают так как ожидается.</p>
<div class="highlight"><pre><span></span><code><span class="c1"># -*- coding: utf-8 -*-</span>
<span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">print_function</span><span class="p">,</span> <span class="n">unicode_literals</span><span class="p">,</span> <span class="n">absolute_import</span>
<span class="k">class</span> <span class="nc">Parent</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">prop</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Parent property"</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">Child</span><span class="p">(</span><span class="n">Parent</span><span class="p">):</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">prop</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="nb">super</span><span class="p">(</span><span class="n">Child</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">prop</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Child property"</span><span class="p">)</span>
<span class="n">child</span> <span class="o">=</span> <span class="n">Child</span><span class="p">()</span>
<span class="n">child</span><span class="o">.</span> <span class="n">prop</span>
</code></pre></div>
<p>Работает как и ожидалось.</p>
<div class="highlight"><pre><span></span><code>Parent property
Child property
</code></pre></div>
<p>В действительности, декоратор property всего лишь устанавливает объект особого типа вместо функции (которая, кстати, так же является объектом особого типа) и не трогает внутреннюю структуру класса. Так что подобное поведение ожидаемо и ясно описано в <a href="https://docs.python.org/3/library/functions.html#property">документации</a>. Но кто читает доки?</p>Pyenv на маке2017-05-19T11:00:00+05:002017-05-19T11:00:00+05:00akorablevtag:www.alexkorablev.ru,2017-05-19:/2017/05/19/pyenv/<p>Довольно сложно управляться с несколькими версиями питона на одном ноутбуке. Хотя, это довольно распространенная ситуация. Обычно, у разработчиков несколько проектов требующих разных версия питона. В целом, есть несколько вариантов для решения этой задачи, но моим любимым остается <a href="https://github.com/pyenv/pyenv">pyenv</a> в компании с плагином <a href="https://github.com/pyenv/pyenv-virtualenv">virtualenv</a>. Раньше я использовал "голый" Homebrew, но это далеко не так гибко и удобно. </p>
<p>Довольно сложно управляться с несколькими версиями питона на одном ноутбуке. Хотя, это довольно распространенная ситуация. Обычно, у разработчиков несколько проектов требующих разных версия питона. В целом, есть несколько вариантов для решения этой задачи, но моим любимым остается <a href="https://github.com/pyenv/pyenv">pyenv</a> в компании с плагином <a href="https://github.com/pyenv/pyenv-virtualenv">virtualenv</a>. Раньше я использовал "голый" Homebrew, но это далеко не так гибко и удобно. </p>
<h2>Установка</h2>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>brew<span class="w"> </span>update
$<span class="w"> </span>brew<span class="w"> </span>install<span class="w"> </span>pyenv
$<span class="w"> </span>brew<span class="w"> </span>install<span class="w"> </span>pyenv-virtualenv
</code></pre></div>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="s1">'eval "$(pyenv init -)"'</span><span class="w"> </span>>><span class="w"> </span>~/.bash_profile
$<span class="w"> </span><span class="nb">echo</span><span class="w"> </span><span class="s1">'eval "$(pyenv virtualenv-init -)"'</span><span class="w"> </span>>><span class="w"> </span>~/.bash_profile
</code></pre></div>
<h2>Базовый шаблон использования</h2>
<p>Настоятельно рекомендую делать отдельные виртуальные окружения для каждого проекта. Иногда бывает необходимо делать даже несколько окружений для одного проекта. К примеру, если хочется использовать mypy для проекта на Python 2.</p>
<p>В первую очередь проверяем доступные версии.</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>pyenv<span class="w"> </span>install<span class="w"> </span>--list
</code></pre></div>
<p>Список впечатляет помимо CPython есть PyPy и Stackless Python. Если последней версии в списке нет, надо обновить формулу для Homebrew:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>brew<span class="w"> </span>upgrade<span class="w"> </span>pyenv
</code></pre></div>
<p>Обычно новая версия появляется в списке через день или два.</p>
<p>Подготовка виртуального окружения для проекта делается в 3 шага:</p>
<ol>
<li>Установить подходящую версию Питона, если ее еще не стоит</li>
<li>Сделать виртуальное окружение</li>
<li>Активировать окружение для проекта</li>
</ol>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>pyenv<span class="w"> </span>install<span class="w"> </span><span class="m">3</span>.6.0
$<span class="w"> </span>pyenv<span class="w"> </span>virtualenv<span class="w"> </span><span class="m">3</span>.6.0<span class="w"> </span>my-project-venv
$<span class="w"> </span><span class="nb">cd</span><span class="w"> </span>/to/my/project/folder
$<span class="w"> </span>pyenv<span class="w"> </span><span class="nb">local</span><span class="w"> </span>my-project-venv
</code></pre></div>
<p>Вот и все.</p>
<p>В случае использования PyCharm, надо создать новую Python SDK с путем равным <code>~/.pyenv/versions/my-project-venv</code>.</p>Обзор Soft Skills2017-04-15T13:00:00+05:002017-04-15T13:00:00+05:00akorablevtag:www.alexkorablev.ru,2017-04-15:/2017/04/15/soft-skills/<p>Когда я начал читать <a href="https://www.amazon.com/Soft-Skills-software-developers-manual/dp/1617292397">"Soft Skills" Джона Сонмеза</a> я не знал чего от нее ожидать. С одной стороны, как программисты мы считаем, что нет ничего важнее написания кода. И это правда. Тем не менее, чрезвычайно сложно найти интересный проект без продвижения самого себя. Я сомневался, что кто-нибудь способен объяснить это разработчикам. Не могу сказать, что Джон делает это идеально, но его попытка достаточно хороша, что бы потратить время и прочитать его книгу.</p>
<p>Когда я начал читать <a href="https://www.amazon.com/Soft-Skills-software-developers-manual/dp/1617292397">"Soft Skills" Джона Сонмеза</a> я не знал чего от нее ожидать. С одной стороны, как программисты мы считаем, что нет ничего важнее написания кода. И это правда. Тем не менее, чрезвычайно сложно найти интересный проект без продвижения самого себя. Я сомневался, что кто-нибудь способен объяснить это разработчикам. Не могу сказать, что Джон делает это идеально, но его попытка достаточно хороша, что бы потратить время и прочитать его книгу.</p>
<p>Для начала поговорим об авторе - Джоне Сонмезе. Он был разработчиком, сейчас он скорее тренер, преподаватель и инвестор в недвижимость. Хотя он утверждает, что берется за разработку время от времени. В любом случае, он тот, кто знает индустрию изнутри. К тому же у него довольно интересная картера. Хотя он не был директором ни Microsoft, ни Google, его советы заслуживают того, что бы проверить их работоспособность. Возможно, то что он не был главой гигантской корпорации делает его советы гораздо более понятными и адекватными для большей части программистов.</p>
<p>OK. Он знает кое-что о разработке ПО. Знает он хоть что-нибудь о персональном брендинге и маркетинге? У меня нет ответа на этот вопрос. Перед прочтением книги, я читал несколько постов в блоге Джона. В общем-то, он не пишет ничего экстраординарного. В действительности, после каждого его совета по маркетингу вы можете воскликнуть "Это же очевидно!", "Каждый знает это!". Вместо того, что бы давать сложные и странные советы, Джон добавляет хороший пример к каждому простому и ясному совету, для его доказательства.</p>
<p>Не все главы книги одинаково хороши. Чем лучше автор разбирается в предмете главы, тем чище и понятнее его объяснения, тем более понятные примеры он дает. Секции "Career", "Marketing yourself", "Learning", "Productivity" - великолепны. </p>
<p>Начиная с секции "Financial" вы обнаружите, что некоторые главы уже не так хороши. К примеру, главы о том как торговаться за зарплату и как работать с недвижимостью - очень-очень хороши. В то же самое время, глава о торговле опционами самая противоречивая и опасная в книге. Я хочу сказать, что опционы не так веселы, как о них пишет автор. В действительности, они очень сложны и рискованны для любителей. Конечно, можно научиться пользоваться ими, но вряд ли эту тему стоило включать в книгу для программистов.</p>
<p>Последняя секция рассказывает о физическом и душевном здоровье. Секция "Fitness" - хороша. Джон хорошо сложен. Будет благоразумно внимательно прочитать и начать использовать в своей жизни что он говорит о постановке фитнес-целей, физических упражнениях, беге и диете.</p>
<p>В заключение, хотелось бы ответить на два вопроса. Стоит ли читать эту книгу? Да, стоит. В основном в ней содержатся хорошие разумные советы. Достаточно выбрать один и применить его в жизни, что бы понять стоит ли продолжать. Эта книга лучшее из того, что читал? Конечно нет. Но я нашел всего две книги посвященные персональному продвижению для программистов. Так что у нас просто нет выбора, мы должны прочитать эту книгу.</p>Это MOOC-курс хорош для меня?2017-04-05T17:00:00+05:002017-04-05T17:00:00+05:00akorablevtag:www.alexkorablev.ru,2017-04-05:/2017/04/05/mooc/<p>Сейчас в сети довольно много различных массовых открытых онлайн-курсов (MOOCs). Одни платные, другие бесплатные. Пару лет назад о подобных курсах говорили на каждом углу, что они убьют все другие формы обучения. Этого, разумеется, не произошло. Тем не менее эта форма - полезный и удобный инструмент самообразования, его точно стоит использовать в собственном учебном плане. Правда, стоит для начала выбрать подходящий курс.</p>
<p>Сейчас в сети довольно много различных массовых открытых онлайн-курсов (MOOCs). Одни платные, другие бесплатные. Пару лет назад о подобных курсах говорили на каждом углу, что они убьют все другие формы обучения. Этого, разумеется, не произошло. Тем не менее эта форма - полезный и удобный инструмент самообразования, его точно стоит использовать в собственном учебном плане. Правда, стоит для начала выбрать подходящий курс.</p>
<blockquote>
<p>Все животные равны. Но некоторые животные равны более, чем другие. </p>
<p>Джордж Оруэлл, "Скотный двор"</p>
</blockquote>
<p>Практически все MOOC-курсы имеют одинаковую структуру:</p>
<ol>
<li>Они включают в себя образовательные материалы: видео или текстовые лекции, примеры кода и тому подобное</li>
<li>Они включают в себя упражнения: тесты, задания по программированию</li>
<li>Они обеспечивают связь с преподавателем каким-либо образом</li>
</ol>
<p>С этой точки зрения, все курсы одинаковые. Но недостаточно глубокая проработка или полное отсутствие одного из компонентов делает курс менее ценным, а порой и просто бесполезным. Если лекции не релевантны, даже гениальные упражнения не сделают курс хорошим. И на оборот, набор хороших лекций без упражнений - это всего-лишь набор хороших лекций и не более того. Они не могут дать достаточный объем практического опыта, не оживляют "мертвые" знания лекций.</p>
<p>При этом я говорю о том случае, когда качество материалов хорошее. К сожалению, иногда случается то <a href="https://www.quora.com/What-can-be-some-good-pet-projects-to-learn-Apache-Spark/answer/MukundKumar-Mishra">о чем пишет MukundKumar Mishra</a>:</p>
<blockquote>
<p>As soon as a new thing comes into industry. Many company start claiming real project based training. The problem with beginner is those are not aware of what should be the right course and market is flooded with courses which are based on something industry do not need. Till beginners understand those are cheated the fast growing IT industry moves on and the delay causes irreparable loss.</p>
</blockquote>
<p>Что бы не нарваться на откровенно плохой курс, я выработал набор простых правил и применяю их каждый раз когда думаю присоединиться к новому курсу или нет. </p>
<h2>Выбери правильную платформу</h2>
<p>Я предпочитаю хорошо известные платформы: Udemy, Pluralsight, Coursera и Stepic. Последние два - мои любимчики. Stepic, кстати, содержит курсы в основном на русском языке. Обе платформы имеют более или менее одинаковый набор функций: видео-лекции, тесты и автоматически проверяемые задания по программированию.</p>
<p>Для большинства курсов по программированию эти авто-проверяемые задания очень важны. Они дают студенту быструю обратную связь. Хотя, порой очень сложно написать хорошую систему тестов для проверки заданий, я предпочту курс с плохой автопроверкой курсу вообще без каких либо проверок. Конечно, ручная проверка кода командой преподавателей курса - просто идеальное решение, но такие курсы очень редки. Иногда тестирование может заменить в какой-то мере задания по программированию, но такие курсы для меня менее ценны.</p>
<h2>Выбери правильного автора</h2>
<p>Достаточно просто в этом ключе разбираться с Coursera. Для этой платформы курсы делают колледжи и университеты. Профессоры адаптируют свою курсы, которые они читают студентам, или создают новые для Coursera. Платформа отфильтровывает откровенный мусор. Конечно, здесь можно найти курс, который будет не очень хорош. Но совсем уж халтуры здесь не встречается.</p>
<p>Другие платформы требуют более внимательного изучения авторов. Обычно, я проверяю всю доступную информацию об авторе курса: его опыт, блоги, ссылки на профили в социальных сетях и прочее. Я просматриваю все, что найду. Основная идея - выяснить насколько адекватен автор и насколько хорошо он знаком с предметом.</p>
<p>Не забудьте проверить другие курсы этого автора. Я верю в специализацию. Если автор имеет курсы по всем темам, которые только можно себе представить, я не куплю ни один его курс. Не возможно делать курсы хорошие курсы о Java Spring Framework и о торговле производными ценными бумагами. Я даже сомневаюсь, что кто-либо способен делать адекватные курсы и по фронтенду и по бэкэнду.</p>
<h2>Выбери правильный курс</h2>
<p>Поиск информации о курсе может быть довольно сложным занятием. Иногда, вся доступная информация о курсе, которая есть - это пользовательские оценки и отзывы на платформе. Обычно я читаю пару отзывов с оценкой 5, пару с оценкой 1. Это дает представление о сильных и слабых сторонах курса. Потом я читаю отзывы с оценкой 3 и 4. Они обычно менее эмоциональные, более сбалансированные и более детальные. </p>
<p>Тем не менее, я немного параноик. Этих отзывов мне не достаточно. Даже с популярными платформами для MOOC-курсов я не уверен в качестве пользовательских обзоров. Мне важно подтверждение с какой-либо внешней независимой площадки. Для англоязычных курсов Quora - одно из лучших мест для поиска отзывов о курсе.</p>
<p>В заключение я должен сказать, что эти правила гибкие. Если тема курса очень интересная и узкая, я закрою глаза на то, что об этом курсе еще никто не написал на Quora. Хотя, я обязательно проверю автора в любом случае.</p>Enums в Python2017-02-17T10:00:00+05:002017-02-17T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2017-02-17:/2017/02/17/enums/<p>Enums наименее используемая фича Питона. Как программисты мы предпочитаем использовать странные дикты или списки там, где мы могли бы использовать enum. По большей части это происходит из-за того, что это довольно новая фича и требуется использовать внешнюю библиотеку обратной совместимости, если используется питон версии 2.7. Тем не менее, довольно много случаев, когда использовать enum гораздо удобнее.</p>
<p>Enums наименее используемая фича Питона. Как программисты мы предпочитаем использовать странные дикты или списки там, где мы могли бы использовать enum. По большей части это происходит из-за того, что это довольно новая фича и требуется использовать внешнюю библиотеку обратной совместимости, если используется питон версии 2.7. Тем не менее, довольно много случаев, когда использовать enum гораздо удобнее.</p>
<p>В любом проекте на Python легко найти код, который делает проверки против строковых литералов. Подобные конструкции используются для определения типов постов, ролей клиентов и тому подобное. Конечно этот код меняется со временем. Меняется набор используемых строковых литералов. Это делает такой код трудно поддерживаемым и трудно тестируемым.</p>
<p>К примеру, у нас есть такой код. </p>
<div class="highlight"><pre><span></span><code><span class="k">if</span> <span class="n">a</span><span class="o">.</span><span class="n">type</span> <span class="o">==</span> <span class="s1">'article'</span><span class="p">:</span>
<span class="n">do_something_if_article</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">a</span><span class="o">.</span><span class="n">type</span> <span class="o">==</span> <span class="s1">'review'</span><span class="p">:</span>
<span class="n">do_something_if_review</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">do_something</span><span class="p">()</span>
</code></pre></div>
<p>С ним все в порядке? Не совсем. Что если мы решим поменять имя типа с "review" на "reviews"? В этом случае вместо вызова <code>do_something_if_review</code> мы вызовем <code>do_something</code>. К тому же этот код не так-то легко модифицировать. Имя типа может быть использовано в множестве мест в разных контекстах. Можно ли код улучшить? Легко!</p>
<div class="highlight"><pre><span></span><code><span class="n">ARTICLE_TYPE</span> <span class="o">=</span> <span class="s1">'article'</span>
<span class="n">REVIEW_TYPE</span> <span class="o">=</span> <span class="s1">'review'</span>
<span class="k">if</span> <span class="n">a</span><span class="o">.</span><span class="n">type</span> <span class="o">==</span> <span class="n">ARTICLE_TYPE</span><span class="p">:</span>
<span class="n">do_something_if_article</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">a</span><span class="o">.</span><span class="n">type</span> <span class="o">==</span> <span class="n">REVIEW_TYPE</span><span class="p">:</span>
<span class="n">do_something_if_review</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">do_something</span><span class="p">()</span>
</code></pre></div>
<p>Теперь мы используем константы вместо строковых литералов. Так на много лучше. Мы сократили места, где в случае смены имени типа потребуется редактирование, до одного. Но это никак не решило проблему добавления нового типа: нам потребуется добавить еще одну ветку elif. Надо что-то еще улучшить.</p>
<p>Подобный код можно существенно улучшить с помощью enum:</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">PostType</span><span class="p">(</span><span class="n">Enum</span><span class="p">):</span>
<span class="n">ARTICLE</span> <span class="o">=</span> <span class="n">do_something_if_article</span>
<span class="n">REVIEW</span> <span class="o">=</span> <span class="n">do_something_if_review</span>
<span class="n">OTHER</span> <span class="o">=</span> <span class="n">do_something</span>
<span class="nd">@classmethod</span>
<span class="k">def</span> <span class="nf">of</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">type_name</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">type_name</span><span class="o">.</span><span class="n">upper</span><span class="p">(),</span> <span class="bp">cls</span><span class="o">.</span><span class="n">OTHER</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">do_stuff</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">value</span><span class="p">()</span>
<span class="n">PostType</span><span class="o">.</span><span class="n">of</span><span class="p">(</span><span class="n">a</span><span class="o">.</span><span class="n">type</span><span class="p">)</span><span class="o">.</span><span class="n">do_stuff</span><span class="p">()</span>
</code></pre></div>
<p>Это уже выглядит прилично. К тому же такой код гораздо гибче предыдущих вариантов. Я уверен, в ваших проектах есть множество мест, где подобный подход сильно улучшит качество кода. И у вас нет оправданий не пойти и не поправить эти места!</p>
<p>P.S. <code>pip install enum34</code> для Python 2.7</p>Почему я использую текстовые файлы2017-01-31T10:00:00+05:002017-01-31T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2017-01-31:/2017/01/31/plain-text/<p>Примерно неделю назад я пытался мигрировать блог обратно на WordPress. Я решил дать ему еще один шанс, поскольку у меня есть планы добавлять больше картинок к постам, что достаточно тяжело делать используя <a href="http://docs.getpelican.com/en/stable/">Pelican</a>: очень много ручной работы. В WordPress есть отличные инструменты для управления картинками. К тому же я думал, что WYSIWYG мне подойдет. Я установил его и даже перенес пару постов. Но все же я решил остановиться: форматирование этих двух постов заняло у меня порядка 20 минут.</p>
<p>Примерно неделю назад я пытался мигрировать блог обратно на WordPress. Я решил дать ему еще один шанс, поскольку у меня есть планы добавлять больше картинок к постам, что достаточно тяжело делать используя <a href="http://docs.getpelican.com/en/stable/">Pelican</a>: очень много ручной работы. В WordPress есть отличные инструменты для управления картинками. К тому же я думал, что WYSIWYG мне подойдет. Я установил его и даже перенес пару постов. Но все же я решил остановиться: форматирование этих двух постов заняло у меня порядка 20 минут.</p>
<p>На самом деле, мне гораздо легче использовать текстовые файлы с разметкой для блога. Почему? Так у меня форматирование занимает всего пару минут, даже если в посте много примеров кода. Работа с картинками добавляет к этим минутам еще 5-10. И конечно, я могу работать с набросками постов везде и всегда. И хранить их в git-репозитории, как я поступаю с исходными кодами. Но более важная причина - так я могу гораздо быстрее писать.</p>
<p>Я не знаю почему, но любые WYSIWYG редакторы замедляют мою работу. Так что я предпочитаю "голый" текст любым "rich" форматам. Может быть из-за того, что провел много лет за кодингом, о может быть из-за того, что с WYSIWYG я слишком много внимания уделяю форматированию, а не работе над самим текстом. Тем не мне, я использую текстовые файлы практически везде, где их использование возможно.</p>
<p>К примеру, я перешел с использования Evernote на Simplenote для сбора заметок и работе над черновиками постов. Картинки я сохраняю крайне редко, так что быстрый и простой Simplenote работает для меня идеально. Evernote в последнее время стал слишком большим и медленным. OneNote ведет себя так же. Google Keep - быстрый сервис и походит на хороший выбор, но у него нет API. Из-за этого я выбрал Simplenote. У него есть API, а я хочу иметь возможность забрать все свои заметки с минимальными усилиями и быстро, если потребуется.</p>
<p>Еще одна область, где я использую текстовые файлы - это блоггинг. Как я упоминал выше, я использую текстовые файлы для написания черновиков. Но вместо использования WordPress для публикации, я использую Pelican. Я использовал и тот, и другой. Но в WordPress очень сложно заставить пост выглядеть так как нужно, особенно это касается постов с примерами кода. Markdown и Pelican делают процесс публикации для меня немного проще.</p>
<p>И конечно же я использую <a href="http://orgmode.org">Orgmode</a> и Taskpaper как инструменты планирования. Оба формата чрезвычайно гибки. Я долгое время пользовался Taskpaper в качестве основного инструмента планирования, но в последнее время предпочитаю Orgmode, так как все больше использую Emacs. Не смотря на то, что редакторы добавляют этим форматам "жизни", оба эти формата остаются текстовыми. Так что и тот и другой легко редактировать в любом текстовом редакторе на любой существующей платформе.</p>
<p>И тем не менее, "голый" текст не для всех. Что бы использовать подобные форматы надо быть немного гиком. Практически каждая задача требует построения некоторого процесса работы с файлами. Не каждый готов тратить на это время.</p>Effective Java второе издание2016-12-30T10:00:00+05:002016-12-30T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-12-30:/2016/12/30/effective-java/<p>Если задать вопрос о том какую книгу стоит прочесть о Java, все скажут, что <a href="https://www.amazon.com/Effective-Java-2nd-Joshua-Bloch/dp/0321356683">Effective Java</a>. Эта книга есть во всех списках рекомендованных книг по Java, за исключением, пожалуй, только тех, авторы которых пытаются продать вам свои книги. Есть несколько причин, почему эта довольно старая книга остается такой популярной.</p>
<p>Если задать вопрос о том какую книгу стоит прочесть о Java, все скажут, что <a href="https://www.amazon.com/Effective-Java-2nd-Joshua-Bloch/dp/0321356683">Effective Java</a>. Эта книга есть во всех списках рекомендованных книг по Java, за исключением, пожалуй, только тех, авторы которых пытаются продать вам свои книги. Есть несколько причин, почему эта довольно старая книга остается такой популярной.</p>
<p>Во первых, эта книга учит как писать хороший Java код. Это значит не только быстрый код, но читаемый и поддерживаемый код. Она описывает общие Java паттерны, которые делают ваш код более понятным другим программистам. Книга не рассказывает о том как "хачить" JVM, только о том как использовать ее более эффективно.</p>
<p>Во вторых, хотя автор не расскажет о Stream API или лямбдах (для этого нужно дождаться третьего издания), он рассказывает о других сложных темах, как дженерики, параллельное программирование и сериализация. Практически все из книги можно и нужно применять в своей ежедневной работе.</p>
<p>С другой стороны, книга о Java 6. Есть достаточно много изменений, которые появились в Java 7 и 8, которые не освещены в этой книге. К примеру, Stream API заслуживает того, что бы его описали в книге подобной этой. Надеюсь, что третье издание появится достаточно скоро.</p>
<p>В заключение, Effective Java не такая большая книга. В ней порядка 350 страниц написанных не очень сложным английским. Она стоит усилий на чтение.</p>Как настроить HTTPS для статичного сайта S32016-12-16T17:00:00+05:002016-12-16T17:00:00+05:00Александр Кораблевtag:www.alexkorablev.ru,2016-12-16:/2016/12/16/s3-https-blog/<p>Google с <a href="https://webmasters.googleblog.com/2014/08/https-as-ranking-signal.html">августа 2016</a> использует HTTPS как один из сигналов ранжирования. Amazon раздает своим пользователям бесплатные сертификаты. Эти два фактора заставили меня искать способ включения HTTPS для моего блога на S3. Решение довольно простое: спрятать S3 за CloudFront.</p>
<p>Google с <a href="https://webmasters.googleblog.com/2014/08/https-as-ranking-signal.html">августа 2016</a> использует HTTPS как один из сигналов ранжирования. Amazon раздает своим пользователям бесплатные сертификаты. Эти два фактора заставили меня искать способ включения HTTPS для моего блога на S3. Решение довольно простое: спрятать S3 за CloudFront.</p>
<h2>Прежде чем начать</h2>
<p>CloudFront - это кэширующий прокси сервер. При работе с ним нужно запастись терпением. Каждая ошибка в настройках - это ожидание 20-25 минут, пока исправления раскатятся по серверам.</p>
<p>Мой опыт основан на прикручивании HTTPS к моему блогу на английском. Мой опыт релевантен для любого статичного сайта, который хостится на S3, вне зависимости от того как он сделан: с помощью Jekyll или Pelican, или другим генератором, или вообще руками.</p>
<h2>Шаг #1. Запрашиваем сертификат в ACM</h2>
<p>Перейдите в <a href="https://console.aws.amazon.com/acm/home?region=us-east-1#/">консоль ACM</a> и запросите сертификат. Надеюсь, что у вас есть доступ к почтовым ящикам владельца и контактов домена. Amazon отправит на эти адреса письмо со ссылкой на верификацию.</p>
<p><strong>Важно!</strong> Если используете домен без www (domain.com) как я, запрашивайте сертификат для domain.com без "www." или "*." перед ним.</p>
<h2>Шаг #2. Создаем CloudFront distribution</h2>
<p>Идем в консоль CloudFront и выбираем "Create new Distribution".</p>
<p>Первым делом указываем метод доставки контента. Нужно выбрать Web.</p>
<p><img alt="Delivery method" src="https://www.alexkorablev.ru/images/cloud_front_1.png"></p>
<p>На втором экране выбираем бакет S3 с контентом сайта в поле Origin Domain Name. Возможно следует выставить "Restrict Bucket Access" в Yes. Но я не проверял.</p>
<p><img alt="Origin domain name" src="https://www.alexkorablev.ru/images/cloud_front_2.png"></p>
<p>Мотаем дальше до "Default Cache Behavior Settings". Меняем "Viewer Protocol Policy" на "Redirect HTTP to HTTPS". </p>
<p><img alt="Protocol policy" src="https://www.alexkorablev.ru/images/cloud_front_3.png"></p>
<p>В блоке "Distribution Settings" вводим имя домена в Alternate Domain Names и выбираем Custom SSL Certificate (если ни одного сертификата нет, то возвращаемся к шагу #1).</p>
<p><img alt="Distribution settings" src="https://www.alexkorablev.ru/images/cloud_front_4.png"></p>
<p>И последнее, необходимо ввести "index.html" в поле Default Root Object. </p>
<p><img alt="Default root object" src="https://www.alexkorablev.ru/images/cloud_front_5.png"></p>
<p>Теперь можно включить distribution. Можно сходить попить кофе. Запуск займет 20-25 минут.</p>
<h2>Шаг #3. Меняем зоны в Route 53.</h2>
<p>После диплоя distribution можно менять настройки Route 53. Нужно выставить правильную запись типа A, которая будет указывать на соответствующий URL CloudFront.</p>
<p>Все. Теперь блог работает через HTTPS.</p>
<h2>TODO</h2>
<p>Осталась пара штук, которые надо бы решить:</p>
<ol>
<li>Я не знаю как настроить редирект с www.domain.com на domain.com.</li>
<li>CloudFront - это кэширующий прокси. Вам придется заботиться о инвалидации кэша после обновления контента. Пока я не нашел хорошего варианта для использования вместе с Pelican.</li>
</ol>Делаем Java-подобный Optional в Python2016-11-25T10:00:00+05:002016-11-25T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-11-25:/2016/11/25/optional-in-python/<p>В некоторых случаях использование None может превратить код в нечитаемую и неподдерживаемую кашу. К примеру, если есть необходимость отличить "пустое" значение от отсутствия значения для целочисленного ввода, когда 0 не может использоваться в качестве этого "пустого" значения, обойтись только используя None очень сложно. Одно из возможных значений использовать что-то подобное Optional из Java.</p>
<p>В некоторых случаях использование None может превратить код в нечитаемую и неподдерживаемую кашу. К примеру, если есть необходимость отличить "пустое" значение от отсутствия значения для целочисленного ввода, когда 0 не может использоваться в качестве этого "пустого" значения, обойтись только используя None очень сложно. Одно из возможных значений использовать что-то подобное Optional из Java.</p>
<p>Конечно, Optional не совсем "питонячий путь". В некоторых случаях можно использовать исключения, как предлагается в <a class="reference external" href="http://stackoverflow.com/questions/22992433/is-there-a-python-equivalent-for-scalas-option-or-either">этой дискуссии на StackOverflow</a>.</p>
<p>В моем случае это не работало. Так что я решил сделать свою имплементацию Optional. И в итоге я получил чистый и хорошо читаемый код. После дискуссии с коллегами, мы решили использовать имена из Haskell, так как то, что у меня получилось больше походило на хаскелльный тип Maybe, чем на джавовый Optional. Вот что у меня получилось:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">ABCMeta</span><span class="p">,</span> <span class="n">abstractmethod</span>
<span class="k">class</span> <span class="nc">Maybe</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="n">__metaclass__</span> <span class="o">=</span> <span class="n">ABCMeta</span>
<span class="nd">@abstractmethod</span>
<span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="k">pass</span>
<span class="k">class</span> <span class="nc">Just</span><span class="p">(</span><span class="n">Maybe</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="n">value</span>
<span class="k">def</span> <span class="nf">__nonzero__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="kc">True</span>
<span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">value</span>
<span class="k">class</span> <span class="nc">Nothing</span><span class="p">(</span><span class="n">Maybe</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">__nonzero__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="kc">False</span>
<span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="k">return</span> <span class="n">default</span>
</pre></div>
<p>После интеграции в существующий кода, я могу с уверенностью сказать, что решение работает идеально. Возможно, надо бы добавить еще красивое представление для этих объектов. Но в целом даже без этого с объектами типа Maybe очень удобно работать при отладке: в отличие от None, понятно что из себя представляет то или иное значение.</p>
<p>Если нужно что-то более сложное, попробуйте <a class="reference external" href="https://github.com/billpmurphy/hask">hask</a>. Но будьте готовы к тому, что у них слишком много хаскелля в их питоне.</p>
<p>Если мой подход кажется не самым удачным, можно попробовать и другие. К примеру, почерпнутых из статьи <a class="reference external" href="https://glyph.twistedmatrix.com/2015/09/python-option-types.html">Python Option Types</a>.</p>
Бэктрекин без рекурсии на Python2016-11-03T17:00:00+05:002016-11-03T17:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-11-03:/2016/11/03/python-backtracking/<p>В Питоне нет оптимизации хвостовой рекурсии и достаточно жесткий лимит на рекурсивные вызовы. Это может вызвать затруднения при решении задач с помощью алгоритма бэктрекинга: лимита хватает на поиск решения судоку, но он может оказаться слишком низким, если количество решений для проверки в задаче довольно высоко.</p>
<p>В Питоне нет оптимизации хвостовой рекурсии и достаточно жесткий лимит на рекурсивные вызовы. Это может вызвать затруднения при решении задач с помощью алгоритма бэктрекинга: лимита хватает на поиск решения судоку, но он может оказаться слишком низким, если количество решений для проверки в задаче довольно высоко.</p>
<p>Тем не менее, реализация бэктрекинга с рекурсией на Питоне элегантна, легко читаема и понятна. Отказываться от нее имеет смысл только для "тяжелых" задач.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">backtrack</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">solution</span><span class="p">):</span>
<span class="k">if</span> <span class="n">solution</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">seen_solutions</span><span class="p">:</span>
<span class="k">return</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">reject</span><span class="p">(</span><span class="n">solution</span><span class="p">):</span>
<span class="k">return</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">accept</span><span class="p">(</span><span class="n">solution</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">output</span><span class="p">(</span><span class="n">solution</span><span class="p">)</span>
<span class="k">for</span> <span class="n">child_solution</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">child_solutions</span><span class="p">(</span><span class="n">solution</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">backtrack</span><span class="p">(</span><span class="n">child_solution</span><span class="p">)</span>
</pre></div>
<p>Когда сложность задачи достигнет лимита рекурсивных вызовов, можно переключиться на шаблон без рекурсии. Он уже не так элегантен как рекуррентное решение, но он не так страшен как мог бы быть. К тому же, он использует те же вспомогательные функции, что и его собрат с рекурсией.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">backtrack</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">solution</span><span class="p">):</span>
<span class="n">solution_stack</span> <span class="o">=</span> <span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">child_solutions</span><span class="p">(</span><span class="n">solution</span><span class="p">)]</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="n">current_generator</span> <span class="o">=</span> <span class="n">solution_stack</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
<span class="n">solution</span> <span class="o">=</span> <span class="nb">next</span><span class="p">(</span><span class="n">current_generator</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
<span class="k">while</span> <span class="n">solution</span><span class="p">:</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">reject</span><span class="p">(</span><span class="n">solution</span><span class="p">):</span>
<span class="n">solution</span> <span class="o">=</span> <span class="nb">next</span><span class="p">(</span><span class="n">current_generator</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
<span class="k">continue</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">accept</span><span class="p">(</span><span class="n">solution</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">output</span><span class="p">(</span><span class="n">solution</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">solution_is_complete</span><span class="p">(</span><span class="n">solution</span><span class="p">):</span>
<span class="n">solution_stack</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">child_solutions</span><span class="p">(</span><span class="n">solution</span><span class="p">))</span>
<span class="n">current_generator</span> <span class="o">=</span> <span class="n">solution_stack</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
<span class="n">solution</span> <span class="o">=</span> <span class="nb">next</span><span class="p">(</span><span class="n">current_generator</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
<span class="n">solution_stack</span> <span class="o">=</span> <span class="n">solution_stack</span><span class="p">[:</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">solution_stack</span><span class="p">:</span>
<span class="k">return</span>
</pre></div>
Обзор курса Functional Programming Principles in Scala2016-10-24T17:00:00+05:002016-10-24T17:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-10-24:/2016/10/24/progfun1/<p>Это осенью я закончил курс на Курсере <a class="reference external" href="https://www.coursera.org/learn/progfun1">"Functional Programming Principles in Scala"</a>. Этот курс - часть специализации из 5 курсов "Functional Programming in Scala." Это 6 недельное введение в функциональное программирование и Scala.</p>
<p>Это осенью я закончил курс на Курсере <a class="reference external" href="https://www.coursera.org/learn/progfun1">"Functional Programming Principles in Scala"</a>. Этот курс - часть специализации из 5 курсов "Functional Programming in Scala." Это 6 недельное введение в функциональное программирование и Scala.</p>
<p>Автор курса - Мартин Одерски, создатель языка Scala. Он действительно великий ученый в области компьютерных наук. Тем не менее, от его лекций клонит в сон. В общем-то, с его лекциями все в порядке, просто я не сразу нашел правильный способ прохождения курса: сначала надо пытаться решить практическую задачу, а потом с кучей готовых вопросов смотреть лекции. Так его лекции гораздо полезнее.</p>
<p>Структура курса достаточно обычна. Первая неделя покрывает базовые конструкции Scala и разницу между императивным и функциональным подходами к программированию. Остальные недели покрывают темы, достаточно типичные для курсов по функциональным языкам, с учетом особенностей Scala: функции высшего порядка, абстрактные типы (traits) и классы, сопоставление с образцом и, конечно, как работать со списками и другими коллекциями в Scala.</p>
<p>Каждая неделя курса заканчивается практической задачей. В основном они не сложные. Тем не менее, некоторые задачи требуют определенных усилий для получения наивысшего балла. Отмечу, что даже если все тесты проходят локально, не обязательно все пройдут на серверах Курсеры.</p>
<p>Там больше тестов, чем доступно в шаблоне с заданием. Я настоятельно рекомендую писать дополнительные тесты. Это не всегда легко, но это существенно увеличивает пользу от курса.</p>
<p>И в заключение, если вы решите после этого курса, что Scala не для вас, то пройдите следующий курс в специализации.</p>
Хочется добавить тип локальной переменной? Пора делать рефакторинг!2016-10-06T17:00:00+05:002016-10-06T17:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-10-06:/2016/10/06/local-variable-type/<p><a class="reference external" href="https://www.python.org/dev/peps/pep-0526/">PEP 526</a> добавляет начиная с Python 3.6 опциональную возможность указать тип для локальной переменной. Это более стройная замена комментариям с указанием типа. Это хорошо, но есть одна ловушка: указывание типа в питоне может скрыть проблемы в коде.</p>
<p><a class="reference external" href="https://www.python.org/dev/peps/pep-0526/">PEP 526</a> добавляет начиная с Python 3.6 опциональную возможность указать тип для локальной переменной. Это более стройная замена комментариям с указанием типа. Это хорошо, но есть одна ловушка: указывание типа в питоне может скрыть проблемы в коде.</p>
<p>Эти аннотации придуманы и сделаны в основном для автоматических анализаторов кода. Питон позволяет писать понятный людям код и без дополнительного указания типа переменной. Поэтому, если есть желание добавить к переменной такое определение, вы в беде. Это последнее средство для увеличения читаемости кода, не первое.</p>
<p>Если вы думаете, что аннотация типа для переменной повышает читаемость кода, то попробуйте сначала разобраться в чем причина. В большинстве случаев все сводится к одному из вариантов: либо тип у переменной слишком сложен, либо логика получения этой переменной значения запутана.</p>
<p>В случае сложного типа, можно попробовать определить свой класс или использовать namedtuple, или другие подобные инструменты, позволяющие добавить смысла общим типам.</p>
<p>Во втором случае, нужно попытаться разбить функцию или переписать ее более понятным способом.</p>
<p>К примеру, я очень люблю использовать "map", "sorted", "filter" и другие инструменты из functool. Иногда это приводит к построению таких конструкций, которые изменяют данные до неузнаваемости. К тому же становится очень сложно понять что должно получиться.</p>
<p>Конечно, можно добавить тип для результирующей переменной. Это сделает код немного более понятным. Но будет все еще далек от идеала. Лучше разбить конвейер на более мелкие и понятные куски, дать им хорошие значащие имена. Это действительно сделает код лучше.</p>
MongoDB count могут сильно замедлить API2016-09-23T10:00:00+05:002016-09-23T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-09-23:/2016/09/23/mongo-counters/<p>Иногда команда фронтенда или другие потребители API просят выводить в ручках общее число объектов. Если в качестве хранилища используется MongoDB, постарайтесь избежать этого. Такое поведение ручек может значительно их замедлить.</p>
<p>Иногда команда фронтенда или другие потребители API просят выводить в ручках общее число объектов. Если в качестве хранилища используется MongoDB, постарайтесь избежать этого. Такое поведение ручек может значительно их замедлить.</p>
<p>Я говорю о тех случаях, когда ответ API похож на тот, что расположен ниже, то ждите беды.</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="w"> </span><span class="nt">"total"</span><span class="p">:</span><span class="w"> </span><span class="err">XX</span><span class="p">,</span>
<span class="w"> </span><span class="nt">"limit"</span><span class="p">:</span><span class="w"> </span><span class="err">ZZ</span><span class="p">,</span>
<span class="w"> </span><span class="nt">"offset"</span><span class="p">:</span><span class="w"> </span><span class="err">YY</span><span class="p">,</span>
<span class="w"> </span><span class="nt">"objects"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="err">...</span><span class="p">]</span>
<span class="p">}</span>
</pre></div>
<p>Если коллекция достаточно большая, а запрос достаточно тяжелый, count будет работать непозволительно долго. Запрос find при этом может работать быстро: ему ведь надо вернуть всего-то с десяток объектов. Count будет шерстить весь набор объектов, подходящих под условия запроса.</p>
<p>Иногда добавление дополнительных условий в запрос, которые должны замедлить запрос (к примеру, поиск по регулярному выражению), но при этом сильно сокращают количество подходящих объектов, может даже ускорить ответ от API. Find так и будет возвращать с десяток объектов, а count будет пробегать гораздо меньше объектов.</p>
<p>Так какие же есть решения?</p>
<p>Во первых, если есть возможность поменять формат API, меняйте. Результат со ссылками на предыдущие и следующие результаты (как это делает Facebook API) может оказаться значительно быстрее.</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="w"> </span><span class="nt">"pre"</span><span class="p">:</span><span class="w"> </span><span class="s2">"link-to-previous-x-objects"</span><span class="p">,</span>
<span class="w"> </span><span class="nt">"next"</span><span class="p">:</span><span class="w"> </span><span class="s2">"link-to-next-x-objects"</span><span class="p">,</span>
<span class="w"> </span><span class="nt">"limit"</span><span class="p">:</span><span class="w"> </span><span class="err">x</span><span class="p">,</span>
<span class="w"> </span><span class="nt">"objects"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="err">...</span><span class="p">]</span>
<span class="p">}</span>
</pre></div>
<p>Второй возможных вариант: кэшировать total. Тогда API будет работать примерно как работают поисковые системы: иногда говорить клиенту, что в базе есть больше объектов, чем на самом деле. Во многих случаях это нормально. Особенно когда консистентность весьма условная.</p>
<p>В любом случае, все это имеет значение, если коллекция достаточно большая, объекты приличного размера, а запрос довольно сложный. Во всех остальных случаях, можно делать все, что заблагорассудится. Стоит только не забывать, что сервис может вырасти, а переделывать API потом будет очень сложно и дорого.</p>
Не используйте dict так часто2016-09-09T10:00:00+05:002016-09-09T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-09-09:/2016/09/09/dict-vs-object/<p>У разработчиков на Python наблюдается тенденция использовать дикты там где нужно и там где нет. В основном, это касается передачи данных внутри приложения в виде дикта, вместо создания объекта. Это плохой дизайн, который приводит к целому вороху проблем, самая страшная из которых - такой код невозможно читать.</p>
<p>У разработчиков на Python наблюдается тенденция использовать дикты там где нужно и там где нет. В основном, это касается передачи данных внутри приложения в виде дикта, вместо создания объекта. Это плохой дизайн, который приводит к целому вороху проблем, самая страшная из которых - такой код невозможно читать.</p>
<p>К примеру, есть код, который вызывает внешнее API и получает в ответ JSON, который уже распарсили в dict. Стоит мне отправлять этот словарь дальше или лучше трансформировать его в экземпляр более специфического класса? Оба варианта возможны. Обычно я предпочитаю использовать классы и объекты, хотя это требует определенных усилий и времени: соответствующий класс надо еще написать.</p>
<p>Кроме времени и усилий, этот подход немного (или много, все зависит от задачи и дикта) снизит производительность приложения. Но его преимущества в большинстве случаев перекрывают этот недостаток. Код гораздо легче читать, он получается самодокументированным и поддерживаемым.</p>
<p>Сравните этот пример:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">return_dict</span><span class="p">():</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">responses</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"some_api"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
</pre></div>
<p>И этот:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">SomeObject</span><span class="p">:</span>
<span class="nd">@classmethod</span>
<span class="k">def</span> <span class="nf">from_json</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">json</span><span class="p">):</span>
<span class="n">instance</span> <span class="o">=</span> <span class="bp">cls</span><span class="p">()</span>
<span class="o">//</span> <span class="n">do</span> <span class="n">magic</span>
<span class="k">return</span> <span class="n">instance</span>
<span class="k">def</span> <span class="nf">return_some_object</span><span class="p">():</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">responses</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"some_api"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">SomeObject</span><span class="o">.</span><span class="n">from_json</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">())</span>
</pre></div>
<p>Какой пример легче прочитать и понять? Какой будет легче поддерживать?</p>
<p>Я уверен, что после пары месяцев, полностью забудется что возвращает return_dict. Даже документация не сильно поможет. К тому же, валидировать, что вернула эта функция очень сложно: обычно функция валидирующая дикт выглядит ужасно и представляет из себя гигантский if с кучей ветвлений.</p>
<p>С другой стороны, return_some_object возвращает экземпляр конкретного класса. Если где-то в коде вы получите такой объект, то гораздо проще понять что он из себя представляет, какие данные он содержит и какие методы предоставляет, чем разгадать структуру словаря. И даже если нет ни строчки документации разобраться для чего этот объект нужен довольно просто. В любом случае, гораздо проще работать с экземпляром конкретного класса, а не со словарем.</p>
<p>И последнее, код с классами гораздо проще поддерживать и тестировать. Dict - слишком общий класс и не говорит ничего ни о структуре ни смысле данных, которые содержит. А если словарь содержит еще и вложенные словари (это произойдет практически гарантированно), поддержка кода становится настоящим кошмаром.</p>
Переезд с Python на Java. Первые впечатления2016-08-26T10:00:00+05:002016-08-26T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-08-26:/2016/08/26/java-vs-python/<p>Буквально неделю назад я добавил Java в свой рабочий стек технологий: мы начали делать новый проект с использованием Java Spring Framework. Для меня это интересный вызов. Я никогда раньше не использовал Java, кроме как для маленьких домашних проектиков. Вот мои самые первые впечатления о "лютом энтерпрайзе".</p>
<p>Буквально неделю назад я добавил Java в свой рабочий стек технологий: мы начали делать новый проект с использованием Java Spring Framework. Для меня это интересный вызов. Я никогда раньше не использовал Java, кроме как для маленьких домашних проектиков. Вот мои самые первые впечатления о "лютом энтерпрайзе".</p>
<p>Я не хочу сравнивать Python и Java, как языки программирования. Очевидно, что в Java есть статическая типизация, а в Python нет. Очевидно, что Java более многословна, а Python более компактен. Все это не так интересно. Гораздо интереснее сравнить окружение языков, требуемый ими подход к программированию.</p>
<p>Во первых, Java намного требовательнее к качеству архитектуры и дизайна софта. Питон прощает многие недочеты в проектировании, прежде чем код превращается в нечитаемую и неподдерживаемую кашу. С другой стороны, Java теряет читаемость с огромной скоростью, даже с небольшими просчетами в архитектуре.</p>
<p>Во вторых, Java энтерпрайзного уровня сильно отличается от Java для, к примеру, геймдева или мобильной разработки. Язык-то один и тот же, но подходы к разработке и фреймворки отличаются сильно. Они много больше и сложнее. Я даже стал думать, что Django - это микрофреймворк.</p>
<p>В третьих, огорошило огромное количество магии в Spring Framework. Начать в нем было очень сложно, по сравнению со стартом с любым фреймворком на питоне, который я видел. Мне повезло, у нас в команде, кто занимается этим проектом, только я не имею опыта разработки на спринге. Так что у меня хотя бы есть у кого спросить.</p>
<p>Конечно есть области, где Java делает Python. К примеру, Java гораздо проще деплоить. В java есть такие замечательные тулзы как Maven или Gradle. В целом острой необходимости в такого рода инструментах в питоне нет, пока не надо готовить сборки и пакеты к регулярному деплою.</p>
<p>В заключение хочу сказать, что Java не лучше и не хуже питона. Это другой язык, который требует другого подхода к программированию и других привычек. Вот несколько моих наблюдений:</p>
<ul class="simple">
<li>Меняйте подход к чтению кода. В Java читать тело метода менее важно, чем в Python: заголовок намного информативнее по сравнению с питоном. Я не говорю, что реализация метода не важна. Просто в нее можно заглядывать гораздо реже, чем это требуется в питоне.</li>
<li>Аннотации в Java не равны декораторам в питоне. Они похожи по синтаксису, но работают по разному.</li>
<li>Java заставит вас интенсивно использовать IDE. Расслабьтесь и получайте удовольствие. Это нормально.</li>
<li>Читайте книги. "Effective Java" должна быть в списке трех следующих книг для чтения. Фреймворки Java типа Spring более сложные и трудные для понимания, чем фреймворки для Python. К счастью, для Java гораздо больше качественных обучающих материалов.</li>
</ul>
<p>Я не 100% Java разработчик. Примерно половину времени я все еще программирую на Python. Так что в блоге я буду публиковать посты и о Python, и о Java.</p>
mypy для проекта на Python 2.72016-07-29T10:00:00+05:002016-07-29T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-07-29:/2016/07/29/mypy-python-27/<p>Аннотация типов может сильно помочь при работе над большим проектом на питоне: многие ошибки в дизайне и использовании функций выявляются практически сразу. Конечно для этого требуется использование внешний инструментов для статического анализа, таких как MyPy. Это великолепный инструмент, но он поддерживает третью ветку питона. Поддержка Python 2.7 в нем требует некоторой подготовки.</p>
<p>Аннотация типов может сильно помочь при работе над большим проектом на питоне: многие ошибки в дизайне и использовании функций выявляются практически сразу. Конечно для этого требуется использование внешний инструментов для статического анализа, таких как MyPy. Это великолепный инструмент, но он поддерживает третью ветку питона. Поддержка Python 2.7 в нем требует некоторой подготовки.</p>
<div class="section" id="type-comments-vs-stub-files">
<h2>Type comments vs stub files</h2>
<p>Для начала, нужно определиться как аннотировать типы в коде. Для второй ветки питона есть два варианта: аннотирование в комментариях или использование стаб файлов. Я предпочитаю первый путь. Он информативнее (типы перед глазами) и его легче поддерживать.</p>
<p>К недостатку этого подхода можно отнести то, что необходимо импортировать модуль typing. Это выглядит весьма странно: я добавляю в код комментарии, а модуль я должен импортировать по настоящему. К счастью, когда проект все-таки переедет на Python 3, этот импорт будет смотреться более органично.</p>
<p>Использования стаб файлов оказался не вариантом для основной кодовой базы. Этот подход хорош для внешний библиотек. Но для своего кода необходимость поддерживать еще один набор файлов - перебор.</p>
</div>
<div class="section" id="mypy">
<h2>Запуск MyPy</h2>
<p>MyPy работает под Python 3.5. Потребуется поставить и настроить пару вещей до того, как mypy проверит первые строки кода на второй ветке питона.</p>
<ul class="simple">
<li>Устанавливаем python 3.5 где-нибудь в системе. Я предпочитаю pyenv для этого. Создаем virtualenv и запускаем <code>pip install mypy-lang</code> в нем.</li>
<li>Возвращается к своему виртуальному окружению проекта на python 2. Устанавливаем там бэкпорт библиотеки типов: <code>pip install typing</code></li>
</ul>
<p>Теперь можно проверить установку. Активируем виртуальное окружение mypy и запускаем <code>mypy --py2</code> в папке с нашим проектом.</p>
</div>
<div class="section" id="pycharm">
<h2>Интеграция с PyCharm</h2>
<p>Конечно, PyCharm уже имеет хорошую систему аннотирования и проверки типов. Но она работает не только с синтаксисом, описанным в PEP, но и с собственным синтаксисом от JetBrains. А самое главное этот синтаксис не понимает mypy, а PyCharm не засунешь в CI. Вот по этому я рекомендую настроить проверку mypy при сохранении файла в IDE.</p>
<ul class="simple">
<li>Переходим Preferences -> Tools -> File Watchers. Добавляем новый Watcher. Назовем его MyPy.</li>
<li>В секции Watcher Settings выбираем Python file в поле File type. Поле Program должно указывать на исполняемый файл mypy executable внутри виртуального окружения. В поле Arguments должно быть следующее: <cite>--py2 -s --check-untyped-defs $FilePath$</cite>. Жмем OK.</li>
</ul>
</div>
Пишем код для Unity3d в Sublime Text 32016-07-22T10:00:00+05:002016-07-22T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-07-22:/2016/07/22/unity3d-in-sublime/<p>Необходимость использовать MonoDevelop - пожалуй худшая часть разработки игр под Unity. Для пользователей Windows теперь есть альтернатива: они могут использовать Visual Studio. Что делать счастливым обладателям маков? Переключаться на Sublime Text.</p>
<p>Необходимость использовать MonoDevelop - пожалуй худшая часть разработки игр под Unity. Для пользователей Windows теперь есть альтернатива: они могут использовать Visual Studio. Что делать счастливым обладателям маков? Переключаться на Sublime Text.</p>
<p>Правда в сети не так много информации о том как это сделать. Я потратил достаточно много времени, что бы разобраться как настроить автодополнение для C# в саблайме. В итоге я нашел достаточно простой путь, требующий всего трех вещей установленных на маке:</p>
<ul class="simple">
<li>git</li>
<li>mono (<code>brew install mono</code>)</li>
<li>Sublime Text</li>
</ul>
<p>Переходим в папку с пакетами Sublime Text (Подсказка: пункт меню Preferences -> Browse Packages... поможет найти нужную папку).</p>
<div class="highlight"><pre><span></span>git<span class="w"> </span>clone<span class="w"> </span>https://github.com/OmniSharp/omnisharp-sublime.git<span class="w"> </span>OmniSharp
<span class="nb">cd</span><span class="w"> </span>OmniSharp/
git<span class="w"> </span>checkout<span class="w"> </span>roslyn
</pre></div>
<p>Добавляем в файл проекта саблайма ссылку на солюшн файл:</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="w"> </span><span class="nt">"folders"</span><span class="p">:</span>
<span class="w"> </span><span class="p">[</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nt">"follow_symlinks"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
<span class="w"> </span><span class="nt">"path"</span><span class="p">:</span><span class="w"> </span><span class="s2">"."</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">],</span>
<span class="w"> </span><span class="nt">"solution_file"</span><span class="p">:</span><span class="w"> </span><span class="s2">"SolutionName.sln"</span>
<span class="p">}</span>
</pre></div>
<p>Перезапускаем Sublime Text.</p>
<p>Наслаждаемся результатом. Теперь для проектов на Unity есть автокомплит и проверка кода.</p>
TrueType шрифты в LibGDX2016-07-14T10:00:00+05:002016-07-14T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-07-14:/2016/07/14/libgdx-ttf/<p>LibGDX прекрасная библиотека. Ее очень легко использовать, хотя в ней есть некоторые аспекты, которые не так просты и понятны. Шрифты - один из примеров. Я провел много часов прежде чем нашел решение, при котором шрифты и на десктопе и в мобильных телефонах выглядят пристойно. Я уже писал, что у меня получилось добиться результатов с <a class="reference external" href="https://www.alexkorablev.ru/2016/03/14/libgdx-fonts/">Distance field</a>, но я ошибался. Заставить хорошо выглядеть удалось только TTF.</p>
<p>LibGDX прекрасная библиотека. Ее очень легко использовать, хотя в ней есть некоторые аспекты, которые не так просты и понятны. Шрифты - один из примеров. Я провел много часов прежде чем нашел решение, при котором шрифты и на десктопе и в мобильных телефонах выглядят пристойно. Я уже писал, что у меня получилось добиться результатов с <a class="reference external" href="https://www.alexkorablev.ru/2016/03/14/libgdx-fonts/">Distance field</a>, но я ошибался. Заставить хорошо выглядеть удалось только TTF.</p>
<p>Забудьте о bitmap-шрифтах. На попытки заставить их выглядеть пристойно на всех девайсах уйдет уйма времени. Результат при этом будет более чем посредственным. Даже не смотря на то, что это более или менее <a class="reference external" href="https://github.com/libgdx/libgdx/wiki/Distance-field-fonts">рекомендованный путь</a>. Но все-таки, если у вас не сотни шрифтов, лучше использовать TrueType Fonts.</p>
<p>TTF дают великолепный результат. Но есть в их использовании один маленький трюк: нужно размер шрифта сделать независимым от размера экрана. Вот сниппет, который готовит, конвертирует и добавляет в скин TTF-шрифт.</p>
<div class="highlight"><pre><span></span><span class="kd">class</span><span class="w"> </span><span class="nc">MyGame</span><span class="p">()</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="n">Game</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">skin</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="n">Skin</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Skin</span><span class="p">()</span>
<span class="w"> </span><span class="kd">override</span><span class="w"> </span><span class="kd">fun</span><span class="w"> </span><span class="nf">create</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">val</span><span class="w"> </span><span class="nv">generator</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">FreeTypeFontGenerator</span><span class="p">(</span><span class="n">Gdx</span><span class="p">.</span><span class="na">files</span><span class="p">.</span><span class="na">internal</span><span class="p">(</span><span class="s">"Roboto-Regular.ttf"</span><span class="p">))</span>
<span class="w"> </span><span class="kd">val</span><span class="w"> </span><span class="nv">param</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">FreeTypeFontGenerator</span><span class="p">.</span><span class="na">FreeTypeFontParameter</span><span class="p">()</span>
<span class="w"> </span><span class="kd">val</span><span class="w"> </span><span class="nv">ratio</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Gdx</span><span class="p">.</span><span class="na">graphics</span><span class="p">.</span><span class="na">width</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="m">960f</span>
<span class="w"> </span><span class="n">param</span><span class="p">.</span><span class="na">size</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="m">48</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">ratio</span><span class="p">).</span><span class="na">toInt</span><span class="p">()</span>
<span class="w"> </span><span class="kd">val</span><span class="w"> </span><span class="nv">robotoFont</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">generator</span><span class="p">.</span><span class="na">generateFont</span><span class="p">(</span><span class="n">param</span><span class="p">)</span>
<span class="w"> </span><span class="n">skin</span><span class="p">.</span><span class="na">addRegions</span><span class="p">(</span><span class="n">TextureAtlas</span><span class="p">(</span><span class="n">Gdx</span><span class="p">.</span><span class="na">files</span><span class="p">.</span><span class="na">internal</span><span class="p">(</span><span class="s">"skin.atlas"</span><span class="p">)))</span>
<span class="w"> </span><span class="n">skin</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">"normaltext"</span><span class="p">,</span><span class="w"> </span><span class="n">robotoFont</span><span class="p">)</span>
<span class="w"> </span><span class="n">skin</span><span class="p">.</span><span class="na">load</span><span class="p">(</span><span class="n">Gdx</span><span class="p">.</span><span class="na">files</span><span class="p">.</span><span class="na">internal</span><span class="p">(</span><span class="s">"skin.json"</span><span class="p">))</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="kd">override</span><span class="w"> </span><span class="kd">fun</span><span class="w"> </span><span class="nf">dispose</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">super</span><span class="p">.</span><span class="na">dispose</span><span class="p">()</span>
<span class="w"> </span><span class="n">skin</span><span class="p">.</span><span class="na">dispose</span><span class="p">()</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>Все очень просто. Работает идеально. Никаких просадок производительности не замечено.</p>
PyCon Россия 20162016-07-07T10:00:00+05:002016-07-07T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-07-07:/2016/07/07/pyconru-2016/<p>PyConRu 2016 завершился. Это конференция была лучшей из пайконов, которые проходили в России. Я уже писал, что мой критерий хорошей конференции - 3 достойных доклада. В этот раз я вполне мог бы составить полную дневную секцию из великолепных докладов (и это из двух дневной конференции!).</p>
<p>PyConRu 2016 завершился. Это конференция была лучшей из пайконов, которые проходили в России. Я уже писал, что мой критерий хорошей конференции - 3 достойных доклада. В этот раз я вполне мог бы составить полную дневную секцию из великолепных докладов (и это из двух дневной конференции!).</p>
<p>It-People проделали невероятную работу. Они нашли отличное место для конференции, пригласили хороших спикеров с интересными докладами. И конечно собрали толпу разработчиков на Python. Особая благодарность Маше Ронахер за то, что она собрала такой состав зарубежных спикеров.</p>
<p>Надеюсь, видео с конференции появится уже скоро. Как только это произойдет, я добавлю его прямо сюда.</p>
Свой итератор поверх enumerate2016-06-27T17:00:00+05:002016-06-27T17:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-06-27:/2016/06/27/logging-iterator/<p>Пару дней назад коллега попросил сделать логгирующий сам себя итератор поверх enumerate. Я попробовал наследоваться напрямую и потерпел неудачу. Я абсолютно забыл как работает магический метод <cite>__new__</cite>. Поскольку я был занят, я пообещал себе разобраться с этой проблемой позже. А ларчик открывался очень просто. 18 строк кода и у меня появилась нужная функциональность.</p>
<p>Пару дней назад коллега попросил сделать логгирующий сам себя итератор поверх enumerate. Я попробовал наследоваться напрямую и потерпел неудачу. Я абсолютно забыл как работает магический метод <cite>__new__</cite>. Поскольку я был занят, я пообещал себе разобраться с этой проблемой позже. А ларчик открывался очень просто. 18 строк кода и у меня появилась нужная функциональность.</p>
<div class="section" id="section-1">
<h2>Изначальная задача</h2>
<p>Сначала стоит объяснить зачем нам вообще понадобился подобный итератор, почему нам не хватило обычного enumerate. Все дело в том, что у нас в проекте очень много задач, построенных по такому шаблону:</p>
<ol class="arabic simple">
<li>Получить пачку объектов из базы</li>
<li>Написать в лог сколько объектов получили</li>
<li>С каждым полученным объектом сделать что-либо, отчитываясь о ходе работы после каждого Х-го объекта</li>
<li>Написать в лог о завершении задачи</li>
</ol>
<p>В питоне это выглядит как-то так:</p>
<div class="highlight"><pre><span></span><span class="n">iterable</span> <span class="o">=</span> <span class="n">get_bunch</span><span class="p">()</span>
<span class="n">total</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">iterable</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"total: </span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">total</span><span class="p">))</span>
<span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">item</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">iterable</span><span class="p">,</span> <span class="n">start</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">func</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"catch exception: </span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">e</span><span class="p">))</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">100</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"done </span><span class="si">{}</span><span class="s2"> of </span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">total</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Done!"</span><span class="p">)</span>
</pre></div>
<p>Вся разница между этими задачами в теле функции <cite>func</cite>, да в сообщениях в лог. Вся эта структура копипастилась раз за разом. Так что мы решили избавиться от этого сделав свой итератор, который бы прятал все лишнее.</p>
</div>
<div class="section" id="section-2">
<h2>Реализация</h2>
<p>Ок. Давайте сделаем класс, который будет наследником <cite>enumerate</cite>. Как я говорил выше, нам придется переопределить метод <cite>__new__</cite>, так как <cite>enumerate</cite> делает это. Согласно документации, если <cite>__new__()</cite> возвращает истанс класса, тогда метод <cite>__init__()</cite> нового инстанса будет вызываться с теми же аргументами. Так что у меня получилась такая реализация:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">LogEnumerate</span><span class="p">(</span><span class="nb">enumerate</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__new__</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">iterable</span><span class="p">,</span> <span class="n">start</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">super</span><span class="p">(</span><span class="n">LogEnumerate</span><span class="p">,</span> <span class="bp">cls</span><span class="p">)</span><span class="o">.</span><span class="fm">__new__</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="n">iterable</span><span class="p">,</span> <span class="n">start</span><span class="p">)</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">iterable</span><span class="p">,</span> <span class="n">start</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">step</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span>
<span class="n">start_message</span><span class="o">=</span><span class="s1">''</span><span class="p">,</span> <span class="n">progress_message</span><span class="o">=</span><span class="s1">''</span><span class="p">,</span> <span class="n">stop_message</span><span class="o">=</span><span class="s1">''</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">progress_message</span> <span class="o">=</span> <span class="n">progress_message</span>
<span class="bp">self</span><span class="o">.</span><span class="n">stop_message</span> <span class="o">=</span> <span class="n">stop_message</span>
<span class="bp">self</span><span class="o">.</span><span class="n">step</span> <span class="o">=</span> <span class="n">step</span>
<span class="bp">self</span><span class="o">.</span><span class="n">total</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">iterable</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">start_message</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">start_message</span><span class="p">))</span>
<span class="k">def</span> <span class="fm">__next__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">i</span><span class="p">,</span> <span class="n">item</span> <span class="o">=</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__next__</span><span class="p">()</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">i</span> <span class="o">%</span> <span class="bp">self</span><span class="o">.</span><span class="n">step</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">progress_message</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">total</span><span class="p">))</span>
<span class="k">return</span> <span class="n">item</span>
<span class="k">except</span> <span class="ne">StopIteration</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">stop_message</span><span class="p">)</span>
<span class="k">raise</span>
</pre></div>
</div>
7 лучших видео с PyCon 20162016-06-15T17:00:00+05:002016-06-15T17:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-06-15:/2016/06/15/pycon-2016-videos/<p>За последние две недели я посмотрел все видео с PyCon 2016. В этот список я добавил 7 лучших выступлений по моему мнению. Я не стал включать ни одного мастер-класса, лайт-толка или кейноута: не они составляют самую интересную часть конференции. Не могу сказать, что остальные видео плохи. Нет. Но в них не достаточно градуса хардкорности или полезности.</p>
<p>За последние две недели я посмотрел все видео с PyCon 2016. В этот список я добавил 7 лучших выступлений по моему мнению. Я не стал включать ни одного мастер-класса, лайт-толка или кейноута: не они составляют самую интересную часть конференции. Не могу сказать, что остальные видео плохи. Нет. Но в них не достаточно градуса хардкорности или полезности.</p>
<h2>Larry Hastings - Removing Python's GIL: The Gilectomy</h2>
<iframe width="560" height="315" src="https://www.youtube.com/embed/P3AyI_u66Bw" frameborder="0" allowfullscreen></iframe>
<h2>Andrey Petrov - See Python, See Python Go, Go Python Go</h2>
<iframe width="560" height="315" src="https://www.youtube.com/embed/CkDwb5koRTc" frameborder="0" allowfullscreen></iframe>
<h2>Chelsea Voss - Oneliner-izer: An Exercise in Constrained Coding</h2>
<iframe width="560" height="315" src="https://www.youtube.com/embed/DsUxuz_Rt8g" frameborder="0" allowfullscreen></iframe>
<h2>Russell Keith-Magee - A tale of two cellphones</h2>
<iframe width="560" height="315" src="https://www.youtube.com/embed/NqdpK9KjGgQ" frameborder="0" allowfullscreen></iframe>
<h2>Glyph - Shipping Software To Users With Python</h2>
<iframe width="560" height="315" src="https://www.youtube.com/embed/5BqAeN-F9Qs" frameborder="0" allowfullscreen></iframe>
<h2>Scott Sanderson, Joe Jevnik - Playing with Python Bytecode</h2>
<iframe width="560" height="315" src="https://www.youtube.com/embed/mxjv9KqzwjI" frameborder="0" allowfullscreen></iframe>
<h2>Matthias Kramm - Python Typology</h2>
<iframe width="560" height="315" src="https://www.youtube.com/embed/IDm_YIQihhs" frameborder="0" allowfullscreen></iframe>Онлайн курс по Clojure2016-06-07T17:00:00+05:002016-06-07T17:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-06-07:/2016/06/07/clojure-mooc/<p>Clojure - интересный lisp-подобный функциональный язык. Если вы фанат круглых скобочек, то программировать на этом языке интересно. К тому же Clojure компилируется в байт-код JVM и транслируется в JavaScript. Вернее в JS транслируется ClojureScript. Над ним работает та же команда, что и над Clojure, так что это все-таки один и тот же язык за исключением некоторой разницы в типах и мелочах, зависящих от среды исполнения. При этом ресурсов для изучения этого языка не так много. Один из них - онлайн-курс <a class="reference external" href="http://iloveponies.github.io/120-hour-epic-sax-marathon/index.html">Functional programming in Clojure</a>.</p>
<p>Clojure - интересный lisp-подобный функциональный язык. Если вы фанат круглых скобочек, то программировать на этом языке интересно. К тому же Clojure компилируется в байт-код JVM и транслируется в JavaScript. Вернее в JS транслируется ClojureScript. Над ним работает та же команда, что и над Clojure, так что это все-таки один и тот же язык за исключением некоторой разницы в типах и мелочах, зависящих от среды исполнения. При этом ресурсов для изучения этого языка не так много. Один из них - онлайн-курс <a class="reference external" href="http://iloveponies.github.io/120-hour-epic-sax-marathon/index.html">Functional programming in Clojure</a>.</p>
<p>Курс во многих отношениях необычный. Первое что бросается в глаза: никаких видео уроков, только текст и упражнения. После прочтения вводного занятия понимаешь, что курс еще более необычный: он сильно завязан на GitHub. Необходимые шаблоны для выполнения заданий нужно форкать на свой аккаунт. Выполненные задания отправляются пулл-реквестом создателю курса, где их оценивает скрипт сборки Travis CI.</p>
<p>В курсе всего 9 основных блоков с упражнениями и 2 дополнительных блока. Основные блоки закрывают темы от основ языка до рекурсии и сверток. Из дополнительных блоков один описывает стиль, второй рассказывает о практическом примере использования Clojure.</p>
<p>Главная ценность курса - упражнения. Их много, они разнообразны и в большей части блоков привязаны к решению одной практической задачи. Часть задач решается сходу, над частью задач надо посидеть довольно долго. Есть задачи повышенной сложности. Они действительно такие. Хотя их постановка может выглядеть предельно просто. К примеру, одно из таких заданий предлагает вывести монотонные куски заданной последовательности. Я бился над этой задачей несколько часов.</p>
<p>Курс не является всеобъемлющим. Это введение, знакомство с языком. Он не знакомит с большей частью библиотеки языка. При этом, зачастую, автор предполагает знакомство студента с какими-то аспектами языка и его библиотеки. В этом основной недостаток курса, который не так-то просто исправить с помощью документации: Clojure своеобразный и непривычный язык, стандартная библиотека и документация к ней такая же своеобразная и необычная.</p>
<p>В целом курс хороший. Его стоит пройти, если есть интерес к языку Clojure или к функциональному программированию.</p>
Как управляться с большими проектами на Python2016-06-01T17:00:00+05:002016-06-01T17:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-06-01:/2016/06/01/big-python-projects/<p>На Hacker News появилась <a class="reference external" href="https://news.ycombinator.com/item?id=11792474">интересная дискуссия</a> о том как управляться с большими Python проектами. Поскольку я причастен к достаточно большому проекту, то я не могу удержаться и не поделиться своим видением на этот вопрос. Я составил список из трех моментов на которые стоит обратить внимание.</p>
<p>На Hacker News появилась <a class="reference external" href="https://news.ycombinator.com/item?id=11792474">интересная дискуссия</a> о том как управляться с большими Python проектами. Поскольку я причастен к достаточно большому проекту, то я не могу удержаться и не поделиться своим видением на этот вопрос. Я составил список из трех моментов на которые стоит обратить внимание.</p>
<div class="section" id="section-1">
<h2>1. Обеспечьте себе безболезненную навигацию по коду</h2>
<p>В большом проекте навигация по коду и автокомплит сильно упрощают разработку и поддержку. Все что ломает навигацию должно подвергаться сомнению. А это значит, что хитрые конструкции и мета-программирование должны быть очень хорошо обоснованы: без них либо не обойтись, либо очень сложно обойтись. В остальных случаях лучше выбирать более очевидные (в том числе для тулзов) пути.</p>
</div>
<div class="section" id="section-2">
<h2>2. Добейтесь хорошего покрытия тестами</h2>
<p>Чем больше тестов, тем лучше. Если после изменений, тесты не проходят - это повод радоваться. Если проходят, надо насторожиться и проверить все ли тесты корректны. CI просто необходим.</p>
<p>Неплохо добавить проверку покрытия тестами. Причем из этой проверки не стоит исключать сами тесты. В них то же может быть приличное количество неиспользуемого кода с которым надо что-то делать.</p>
</div>
<div class="section" id="type-hinting">
<h2>3. Пользуйтесь type hinting</h2>
<p>Во-первых, это отличное подспорье в самодокументировании кода (в Python 3). Во-вторых, это очень хороший способ найти проблемы в интерфейсах функций. Если очень сложно описать что функция принимает и что выдает, то может стоит эту функцию переписать.</p>
<p>Наш проект только готовится переехать на 3-ю ветку питона, так что мы не можем в полной мере использовать type hinting в самих описаниях функций, но мы описываем типы в докстрингах. PyCharm очень сильно помогает в этом.</p>
<p>Этот список - краткий анализ собственного опыта работы на большим проектом на Python. Я не стал включать в него наставления по проектированию архитектуры проекта (компоненты нужно делать слабосвязными и т.п.). Подобные советы, как правило, не очень хорошо работают. Гораздо лучше и проще внедрить то, что легко проверить: не сломалась ли навигация по коду, есть ли тест, есть ли описание типов.</p>
</div>
Программа конференции PyCon Ru 20162016-05-24T10:00:00+05:002016-05-24T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-05-24:/2016/05/24/pycon-ru-2016-content/<p>Чуть больше чем через месяц под Москвой пройдет Pycon Russia. Время определиться с участием еще есть. Тем более, что готова <a class="reference external" href="http://pycon.ru/2016/program/content/">предварительная программа</a>. В хедлайнерах у конференции Raymond Hettinger, Armin Ronacher, Андрей Светлов.</p>
<p>Чуть больше чем через месяц под Москвой пройдет Pycon Russia. Время определиться с участием еще есть. Тем более, что готова <a class="reference external" href="http://pycon.ru/2016/program/content/">предварительная программа</a>. В хедлайнерах у конференции Raymond Hettinger, Armin Ronacher, Андрей Светлов.</p>
<p>На сегодняшний день известны темы 12 докладов:</p>
<ol class="arabic simple">
<li>Путешествие по внутреннему устройству CPython вместе с Александром Кошкиным;</li>
<li>Использование деревьев решения с градиентным бустингом (GBDT) для построения классификатора заявок о ложных срабатываний антивируса от Александра Сибирякова;</li>
<li>Jackie Kazil расскажет о приемах построения успешного open-source проекта;</li>
<li>Андрей Солдатенко построит социальную сеть с помощью Neo4j и Python;</li>
<li>Антон Егоров поделится опытом деплоймента веб-приложения на Python с помощью технологии Docker;</li>
<li>Воркшоп "Property-based testing with Hypothesis" David MacIver;</li>
<li>Сравнение Celery и RQ от Артема Малышева;</li>
<li>Nathaniel Manista обещает: "Come learn how to write code that makes friends without sacrificing function or performance!";</li>
<li>Андрей Сумин поделится опытом эксплуатации питона под нагрузкой;</li>
<li>Надеюсь Иван Цыганов действительно круто расскажет как делать DSL;</li>
<li>Мой коллега Алексей Лавренюк расскажет о Яндекс.Танке и нагрузочном тестировании;</li>
<li>Martin Görner научит делать машинное обучение без необходимости получать степень кандидата наук.</li>
</ol>
<p>Пара слов про саму конференцию. В прошлые годы конференция PyCon Russia проходила под Екатеринбургом. В этот раз место проведения перенесено ближе к Москве, на количестве зарубежных гостей это уже сказалось положительно. Надеюсь, это положительно скажется и на общем количестве участников.</p>
Проект на Kivy. Часть 1. Настройка окружения2016-05-16T17:00:00+05:002016-05-16T17:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-05-16:/2016/05/16/setup-kivy-project/<p><a class="reference external" href="https://kivy.org/#home">Kivy</a> - интересный проект, который позволяет делать кроссплатформенные GUI приложения, включая мобильные. По слухам. Я решил выяснить можно ли использовать его для создания маленьких приложений на питоне. В этой серии статей я попробую поисследовать Kivy на примере клона "конструктора слов" - одного из упражнений для LingvoLeo.</p>
<p><a class="reference external" href="https://kivy.org/#home">Kivy</a> - интересный проект, который позволяет делать кроссплатформенные GUI приложения, включая мобильные. По слухам. Я решил выяснить можно ли использовать его для создания маленьких приложений на питоне. В этой серии статей я попробую поисследовать Kivy на примере клона "конструктора слов" - одного из упражнений для LingvoLeo.</p>
<p>Эта серия - не учебник, а исследовательский лог моих попыток. В результате я планирую получить собранные приложения для трех платформ минимум: Windows, Mac, Android. Будет здорово, если я соберу еще и iOS версию. В качестве основной платформы для разработки я использую Mac. Так что, все инструкции по установке и настройке окружения будут для этой операционной системы.</p>
<div class="section" id="section-1">
<h2>Установка</h2>
<p>Под мак есть два способа поставить Kivy: скачать бандл или установить зависимости из homebrew, а kivy поставить с помощью pip.</p>
<div class="section" id="section-2">
<h3>Первый способ (быстрый):</h3>
<ol class="arabic">
<li><p class="first">Скачать с <a class="reference external" href="http://kivy.org/#download">http://kivy.org/#download</a> Kivy2.7z (использует системный питон 2.7) или Kivy3.7z (включает в себя Python 3.5)</p>
</li>
<li><p class="first">Распаковываем архив</p>
</li>
<li><p class="first">Копируем в приложения</p>
<div class="highlight"><pre><span></span>sudo<span class="w"> </span>mv<span class="w"> </span>Kivy3.app<span class="w"> </span>/Applications/Kivy.app
</pre></div>
</li>
<li><p class="first">Создаем симлинк</p>
<div class="highlight"><pre><span></span>ln<span class="w"> </span>-s<span class="w"> </span>/Applications/Kivy.app/Contents/Resources/script<span class="w"> </span>/usr/local/bin/kivy
</pre></div>
</li>
</ol>
<p>Минус этого способа установки в том, что создается только одно виртуальное окружение на все Kivy проекты. Для маленьких проектов такой способ подойдет, для больших - сомневаюсь. Так что лучше сразу ставить фремворк в свое окружение.</p>
</div>
<div class="section" id="section-3">
<h3>Второй способ (правильный):</h3>
<p>Подразумеваю, что вы пользуетесь <a class="reference external" href="https://github.com/yyuu/pyenv">pyenv</a> и <a class="reference external" href="https://github.com/yyuu/pyenv-virtualenv">pyenv-virtualenv</a>.</p>
<p>Первым делом ставим зависимости.</p>
<div class="highlight"><pre><span></span>brew<span class="w"> </span>install<span class="w"> </span>sdl2<span class="w"> </span>sdl2_image<span class="w"> </span>sdl2_ttf<span class="w"> </span>sdl2_mixer<span class="w"> </span>gstreamer
</pre></div>
<p>Ставим свежий питончик.</p>
<div class="highlight"><pre><span></span>pyenv<span class="w"> </span>install<span class="w"> </span><span class="m">3</span>.5.1
</pre></div>
<p>Иногда установка падает:</p>
<div class="highlight"><pre><span></span>zipimport.ZipImportError:<span class="w"> </span>can<span class="err">'</span>t<span class="w"> </span>decompress<span class="w"> </span>data<span class="p">;</span><span class="w"> </span>zlib<span class="w"> </span>not<span class="w"> </span>available
</pre></div>
<p>В этом случае поможет:</p>
<div class="highlight"><pre><span></span>xcode-select<span class="w"> </span>--install
</pre></div>
<p>Как только свежий питон стоит, создаем виртуальное окружение для нашего проекта.</p>
<div class="highlight"><pre><span></span>pyenv<span class="w"> </span>virtualenv<span class="w"> </span><span class="m">3</span>.5.1<span class="w"> </span>WordConstructor
</pre></div>
<p>Активируем созданное окружение</p>
<div class="highlight"><pre><span></span>pyenv<span class="w"> </span>activate<span class="w"> </span>WordConstructor
</pre></div>
<p>Ставим Cython</p>
<div class="highlight"><pre><span></span>pip<span class="w"> </span>install<span class="w"> </span>-I<span class="w"> </span><span class="nv">Cython</span><span class="o">==</span><span class="m">0</span>.23
</pre></div>
<p>Ставим Kivy</p>
<div class="highlight"><pre><span></span><span class="nv">USE_OSX_FRAMEWORKS</span><span class="o">=</span><span class="m">0</span><span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span>kivy
</pre></div>
<p>Проверим, что все работает. Создадим файлик main.py в папке с проектом с таким содержимым:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">kivy.app</span> <span class="kn">import</span> <span class="n">App</span>
<span class="kn">from</span> <span class="nn">kivy.uix.widget</span> <span class="kn">import</span> <span class="n">Widget</span>
<span class="k">class</span> <span class="nc">WordConstructorGame</span><span class="p">(</span><span class="n">Widget</span><span class="p">):</span>
<span class="k">pass</span>
<span class="k">class</span> <span class="nc">WordConstructorApp</span><span class="p">(</span><span class="n">App</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">build</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="n">WordConstructorGame</span><span class="p">()</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
<span class="n">WordConstructorApp</span><span class="p">()</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</pre></div>
<p>Запустим</p>
<div class="highlight"><pre><span></span>python<span class="w"> </span>main.py
</pre></div>
<p>Если открылось окно с заголовком WordConstructor, все установилось и работает как надо.</p>
</div>
</div>
<div class="section" id="pycharm">
<h2>PyCharm</h2>
<p>Теперь настроим проект в пайчарме.</p>
<p>В Preferences выбираем Project interpreter соответствующий нашему виртуальному окружению.</p>
<img alt="Kivy interpreter" src="https://www.alexkorablev.ru/images/kivy-interpreter.png" />
<p>Для описания интерфейсов в Kivy используется свой язык Kv Design Language. Неплохо бы добавить для него подсветку синтаксиса и автокомплит.</p>
<ol class="arabic simple">
<li>Для этого качаем <a class="reference external" href="https://github.com/Zen-CODE/kivybits/blob/master/IDE/PyCharm_kv_completion.jar?raw=true">https://github.com/Zen-CODE/kivybits/blob/master/IDE/PyCharm_kv_completion.jar?raw=true</a></li>
<li>В PyCharm в меню File -> Import Settings импортируем этот файл.</li>
<li>Удостоверившись, что стоит галочка File types, нажимаем OK.</li>
<li>Перезагружаем PyCharm и наслаждаемся результатом.</li>
</ol>
<p>С настройкой все. В следующей статье обсудим более практические вопросы.</p>
</div>
Модификация параметров под args или kwargs2016-05-04T17:00:00+05:002016-05-04T17:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-05-04:/2016/05/04/args-kwargs/<p>В Python достаточно часто интерфейсы функций частично или полностью "сворачивают" с помощью *args и **kwargs. Это вполне естественно для питонистов, но вызывает вопросы, при переходе с других языков. С самим "сворачиванием" никаких проблем нет. Сложности возникают при модификации аргументов: строятся проверки вокруг args и kwargs, вместо того, что бы "развернуть" интерфейс функции.</p>
<p>В Python достаточно часто интерфейсы функций частично или полностью "сворачивают" с помощью *args и **kwargs. Это вполне естественно для питонистов, но вызывает вопросы, при переходе с других языков. С самим "сворачиванием" никаких проблем нет. Сложности возникают при модификации аргументов: строятся проверки вокруг args и kwargs, вместо того, что бы "развернуть" интерфейс функции.</p>
<p>Представьте себе такой кейс: Есть две библиотеки, одна из которых имеет полностью прописанный интерфейс у функции foo. Вторая определяет функцию boo, которая вызывает foo. Интерфейс boo "свернут" с помощью *args и **kwargs.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">arg1</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">arg2</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="k">pass</span>
<span class="k">def</span> <span class="nf">boo</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="n">foo</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</pre></div>
<p>В коде проекта есть функция do_magic, которая использует boo.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">do_magic</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="n">boo</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</pre></div>
<p>Задача такая: если arg1 в do_magic прилетает со значением по умолчанию, то дальше его нужно отправить со значением True.</p>
<p>Первый вариант решения такой: arg1 может быть либо первым элементом в args, либо быть в kwargs. Значит можно добавить в функцию соответствующую проверку:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">do_magic</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="k">if</span> <span class="s1">'arg1'</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">kwargs</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">args</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
<span class="n">kwargs</span><span class="p">[</span><span class="s1">'arg1'</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">boo</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</pre></div>
<p>Работу мы свою выполнили. Мы учли, что arg1 может быть передан как именованный параметр, так и как не именованный параметр. Функция работает как надо. Но есть путь проще, элегантнее и с меньшим количеством кода.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">do_magic</span><span class="p">(</span><span class="n">arg1</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="n">boo</span><span class="p">(</span><span class="n">arg1</span><span class="o">=</span><span class="n">arg1</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</pre></div>
RoboVM закрылся. LibGDX мигрирует на Multi-OS Engine от Intel2016-04-25T17:00:00+05:002016-04-25T17:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-04-25:/2016/04/25/robovm/<p>RoboVM - проект позволявший запускать Java код на iOS, закрылся. Под ударом оказались все, кто использовал LibGDX для мобильных игр. Замену нашли быстро, LibGDX будет использовать Multi-OS Engine от Intel. Тем не менее, это хороший повод обсудить зависимости в библиотеках.</p>
<p>RoboVM - проект позволявший запускать Java код на iOS, закрылся. Под ударом оказались все, кто использовал LibGDX для мобильных игр. Замену нашли быстро, LibGDX будет использовать Multi-OS Engine от Intel. Тем не менее, это хороший повод обсудить зависимости в библиотеках.</p>
<p>15 апреля пользователи RoboVM получили "письмо счастья": проект закрывается. Судя по объяснению в блоге проекта (<a class="reference external" href="https://robovm.com/robovm-winding-down/">https://robovm.com/robovm-winding-down/</a>), основная причина закрытия - действия Microsoft с Xamarin. В том, что теперь кроссплатформенную мобильную разработку на Java ждут трудные времена, не поспоришь. Xamarin - действительно отличная альтернатива.</p>
<p>Проблема в том, что значительная часть пользователей LibGDX выбрали эту библиотеку отчасти из-за возможности портировать игры на iOS. Этой возможности их лишили. Ребятам из Badlogic Games пришлось быстро искать альтернативу (<a class="reference external" href="http://www.badlogicgames.com/wordpress/?p=3925">http://www.badlogicgames.com/wordpress/?p=3925</a>). Более или менее подходит Intel Multi-OS Engine (<a class="reference external" href="https://software.intel.com/en-us/multi-os-engine">https://software.intel.com/en-us/multi-os-engine</a>). Для нормальной работы нужно еще сделать очень много, но это уже что-то.</p>
<p>Закрытие RoboVM - не повод выкидывать LibGDX из списка возможных технологий для следующего проекта, но хороший повод задуматься над критериями выбора. Я когда выбирал на чем делать игру во главу угла ставил ориентированность на программиста. В этом смысле LibGDX остается лучше многих альтернатив. Да и цели были образовательные. Так что мой выбор не мог изменить даже RoboVM.</p>
<p>Для коммерческой разработки я бы ее уже не стал использовать. Портирование на одну из основных платформ делается не родными средствами, с использованием сторонних инструментов. Это повод посмотреть на другие варианты: Cocos2d-x или даже вернуться к Unity и UE (они, конечно, выжигают дыру в столе моим ноутбуком, но на основные платформы игры билдятся +- стабильно).</p>
Почему я вернулся в PyCharm2016-04-18T17:00:00+05:002016-04-18T17:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-04-18:/2016/04/18/comeback-to-pycharm/<p>В конце января я переключался на Sublime, как основную IDE для Python. Но уже через пару недель я был вынужден вернуться на PyCharm. Последние несколько недель я активно использовал Emacs. Он великолепен. И все-таки для рабочих проектов я снова возвращаюсь к Pycharm.</p>
<p>В конце января я переключался на Sublime, как основную IDE для Python. Но уже через пару недель я был вынужден вернуться на PyCharm. Последние несколько недель я активно использовал Emacs. Он великолепен. И все-таки для рабочих проектов я снова возвращаюсь к Pycharm.</p>
<p>Все три варианта хорошо подходят для работы с питоном. Все три варианта позволяют работать продуктивно. Так что выбирать приходится исходя из слабых сторон каждого из них.</p>
<div class="section" id="pycharm-1">
<h2>Что не так с PyCharm</h2>
<p>Есть несколько причин, почему я периодически пытаюсь сбежать в какой-нибудь другой редактор. В первую очередь, хочется иметь достойный редактор для всех языков программирования. Только в этом режиме я могу "загнать" все свои проекты в одно окружение. Ни PyCharm, ни IntelliJ CE этого обеспечить не способны. Покупать Ultimate для домашних экспериментов смысла не имеет.</p>
<p>Потребление памяти у PyCharm просто ужасно. Связка Pycharm + Vagrant + браузер способны съесть любое количество памяти. Отказаться от вагранта и браузера сложновато.</p>
</div>
<div class="section" id="sublime-emacs">
<h2>Что не так с Sublime и Emacs</h2>
<p>Sublime Text - хороший редактор. Emacs - великолепный редактор. Писать код в любом из этих редакторов быстрее чем в PyCharm (в Emacs значительно быстрее). Навигация по коду и автодополнение кода и там и там работают вполне сносно.</p>
<p>Основная проблема emacs и в меньшей степени sublime - это новое и не знакомое для команды окружение. Довольно часто нужно спросить что-то у коллег. Им обычно гораздо проще подойти и показать на нужную строку кода. Но в emacs не каждый сможет файл открыть, не то, что бы перейти на нужный участок кода. Поскольку наша команда в полном составе использует PyCharm, то гораздо эффективнее в плане командной работы использовать его же.</p>
<p>Еще одна серьезная проблема и Sublime Text и emacs - практически полное отсутствие рефакторинга. Для маленьких проектов - это не так критично. Для средних вполне хватит rope. Но для больших проектов нужен рефакторинг другого порядка. И он есть только в PyCharm.</p>
<p>К сожалению мне так и не удалось найти того идеального инструмента, который бы полностью перекрывал мои потребности. Ни одна IDE не претендует на полное покрытие интересных мне языков программирования. Командная работа накладывает свои ограничения.</p>
<p>Так что, окружение остаётся сложным: PyCharm для рабочих проектов, Idea для Kotlin и Java. Emacs - для заметок, персональных проектов и остального сета языков, не таких требовательных к среде разработки (JS, Clojure, Haskell и т.д.)</p>
</div>
Изменяемы типы в качестве параметров по умолчанию в Python2016-04-11T10:00:00+05:002016-04-11T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-04-11:/2016/04/11/default-params/<p>Почему изменяемые типы не рекомендуется использовать в качестве значений аргументов по умолчанию в Python? Если вы забрались по карьерной лестнице выше джуниора, то наверняка задумывались. И наверняка ответ был таким: "это приводит к странным побочным эффектам". Но я почти наверняка уверен, что только малая часть разработчиков на питоне сделала шаг дальше и разобралась почему такое поведение у языка.</p>
<p>Почему изменяемые типы не рекомендуется использовать в качестве значений аргументов по умолчанию в Python? Если вы забрались по карьерной лестнице выше джуниора, то наверняка задумывались. И наверняка ответ был таким: "это приводит к странным побочным эффектам". Но я почти наверняка уверен, что только малая часть разработчиков на питоне сделала шаг дальше и разобралась почему такое поведение у языка.</p>
<p>Давайте разберемся сначала с самим какими побочными эффектами имеем дело. Напишем вот такую функцию:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">param</span><span class="o">=</span><span class="p">[]):</span>
<span class="nb">print</span><span class="p">(</span><span class="n">param</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">foo</span><span class="p">()</span>
<span class="p">[]</span>
</pre></div>
<p>Сколько ее не вызывай, значение выводимое на экран не изменится. Никаких побочных эффектов. Но если ее модифицировать вот так:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">param</span><span class="o">=</span><span class="p">[]):</span>
<span class="n">param</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">param</span><span class="p">)</span>
</pre></div>
<p>То обнаружим, что значение на экране будет меняться в зависимости от того сколько раз вызвана функция:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="n">foo</span><span class="p">()</span>
<span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="o">>>></span> <span class="n">foo</span><span class="p">()</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">]</span>
<span class="o">>>></span> <span class="n">foo</span><span class="p">()</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">]</span>
</pre></div>
<p>Но мы ведь хотели не этого. Такое поведение функций - один из лидеров разного рода списков "Х самых странных вещей в Python". Хотя, если разобраться с внутренней кухней языка, то все встанет на свои места и "странность" превратится в логичное поведение.</p>
<p>Почему же так получилось? Все из-за того, что функция - это объект, как и все другие сущности в питоне. При создании объекта дефолтные значения параметров упаковались в tuple foo.func_defaults.</p>
<p>Списки в питоне - тип изменяемый. А объект функции создается один раз. Так что объект на который ссылается foo.func_defaults[0] и param внутри функции один и тот же.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">param</span><span class="o">=</span><span class="p">[]):</span>
<span class="nb">print</span><span class="p">(</span><span class="nb">id</span><span class="p">(</span><span class="n">param</span><span class="p">))</span>
<span class="o">>>></span> <span class="n">foo</span><span class="p">()</span>
<span class="mi">4494207600</span>
<span class="o">>>></span> <span class="nb">id</span><span class="p">(</span><span class="n">foo</span><span class="o">.</span><span class="n">func_defaults</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="mi">4494207600</span>
</pre></div>
<p>Правильным будет передача None в качестве значения по умолчанию. А в теле функции уже инициализировать изменяемый объект. А подсказку по типу параметра прописывать в докстринге или, если используется Python 3.5+, то использовать <a class="reference external" href="https://www.python.org/dev/peps/pep-0484/">PEP 0484</a>.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">param</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="w"> </span><span class="sd">"""</span>
<span class="sd"> :type param: list</span>
<span class="sd"> """</span>
<span class="n">param</span> <span class="o">=</span> <span class="p">[]</span> <span class="k">if</span> <span class="n">param</span> <span class="ow">is</span> <span class="kc">None</span> <span class="k">else</span> <span class="n">param</span>
<span class="n">param</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">param</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">foo</span><span class="p">()</span>
<span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="o">>>></span> <span class="n">foo</span><span class="p">()</span>
<span class="p">[</span><span class="mi">1</span><span class="p">]</span>
</pre></div>
<p>Для коротких функций в 5-6 строк добавление проверки на None - замусоривание кода. В этих случаях нужно хорошо подумать оставить эту проверку и обезопасить код или убрать ее. Я за безопасность. Потом делая рефакторинг можно пропустить изменения, которые внесут побочные эффекты.</p>
Обзор курса по Haskell на Stepic.org2016-04-04T10:00:00+05:002016-04-04T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-04-04:/2016/04/04/stepic-haskell-mooc/<p>Как-то я уже упоминал курс <a class="reference external" href="https://stepic.org/course/Функциональное-программирование-на-языке-Haskell-75/syllabus">"Функциональное программирование на языке Haskell"</a>. Наконец могу поделить собственными впечатлениями об этом курсе. Этот курс стоит того, что бы его пройти и сделать (или хотя бы попытаться сделать) все предложенные задания. Лучшего вводного курса по Хаскелю я не встречал. Огромное спасибо Денису Москвину (основной преподаватель) и команде за курс.</p>
<p>Как-то я уже упоминал курс <a class="reference external" href="https://stepic.org/course/Функциональное-программирование-на-языке-Haskell-75/syllabus">"Функциональное программирование на языке Haskell"</a>. Наконец могу поделить собственными впечатлениями об этом курсе. Этот курс стоит того, что бы его пройти и сделать (или хотя бы попытаться сделать) все предложенные задания. Лучшего вводного курса по Хаскелю я не встречал. Огромное спасибо Денису Москвину (основной преподаватель) и команде за курс.</p>
<p>Курс состоит из 5 блоков, каждый из которых раскрывает некоторые аспекты языка: типы данных, списки, монады и т.п. Каждый блок - это набор видео уроков с заданиями где-то на 5-6 часов работы.</p>
<p>Материал подается постепенно. Видео представляет собой в основном демонстрацию работы преподавателя в редакторе кода и запуске каких-либо вычислений в интерпретаторе. Все сопровождается подробными комментариями и разъяснениями. Просмотра видео, по большей части, достаточно для выполнения заданий, которые за ним следуют. Иногда приходится гуглить, что бы найти те или иные функции из стандартной библиотеки, упомянутые в видео. Не хватает исходных текстов примеров, набранных Денисом на видео.</p>
<p>Отдельно нужно отметить количество и качество практических заданий. Их много - в среднем по 4-5 за урок. По большей части в заданиях просят написать кусок кода на Хаскеле. Сложность варьируется очень сильно (причем она даже не сильно зависит от баллов, которые назначены за задачу создателями курса). Но это, скорее, связано с особенностями функционального подхода. Порой поломать голову приходится над задачами в 1 балл больше, чем над задачей в 3 балла.</p>
<p>Курс определенно стоит того, чтобы потратить на него 5 недель своего времени. Хаскель может и не самый востребованный язык программирования, но без условно он оказывает огромное влияние на другие языки. Программисту стоит ознакомиться с его основами.</p>
Масштабируемые шрифты в LibGDX2016-03-14T10:00:00+05:002016-03-14T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-03-14:/2016/03/14/libgdx-fonts/<p><em>Обновление 2016-07-14:</em> <a class="reference external" href="https://www.alexkorablev.ru/2016/07/14/libgdx-ttf/">Я все-таки нашел способ работать с TTF-шрифтами</a>.</p>
<p>Работать со шрифтами в LibGDX оказалось неожиданно сложно. Создать растровый шрифт проблем нет, но вот заставить его масштабироваться под разные разрешения - реальная проблема. Более или менее рабочая практика - использовать Distance field. После этого нужно еще пробросить шрифт в описание скина.</p>
<p><em>Обновление 2016-07-14:</em> <a class="reference external" href="https://www.alexkorablev.ru/2016/07/14/libgdx-ttf/">Я все-таки нашел способ работать с TTF-шрифтами</a>.</p>
<p>Работать со шрифтами в LibGDX оказалось неожиданно сложно. Создать растровый шрифт проблем нет, но вот заставить его масштабироваться под разные разрешения - реальная проблема. Более или менее рабочая практика - использовать Distance field. После этого нужно еще пробросить шрифт в описание скина.</p>
<p>Попытка использовать TTF провалилась. Настроить ее в LibGDX проще простого, но вот результат получается ужасным. Возможно я не до конца разобрался с этим процессом. Если у кого-либо из читателей есть опыт генерации красивых шрифтов из TTF, поделитесь в комментариях.</p>
<p>Я выбрал путь использования Distance field, как предлагается в <a class="reference external" href="https://github.com/libgdx/libgdx/wiki/Distance-field-fonts">документации</a>. Используя Hiero добавляем к шрифту Distance field. Scale - 32. Spread сильно зависит от шрифта. Для Roboto Regular хороший результат получается при 1. Дальше по инструкции: Padding равным 1, X и Y равными -2.</p>
<p>Вот только полученный таким образом шрифт невозможно использовать на прямую ссылаясь на него из json с описание скина. Шрифт придется генерировать программно из-за того, что текстура требует наложения фильтра. На Kotlin получится вот такой код:</p>
<div class="highlight"><pre><span></span><span class="kd">class</span><span class="w"> </span><span class="nc">MyGame</span><span class="p">()</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="n">Game</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">skin</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="n">Skin</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Skin</span><span class="p">()</span>
<span class="w"> </span><span class="kd">override</span><span class="w"> </span><span class="kd">fun</span><span class="w"> </span><span class="nf">create</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">robotoTexture</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Texture</span><span class="p">(</span><span class="n">Gdx</span><span class="p">.</span><span class="na">files</span><span class="p">.</span><span class="na">internal</span><span class="p">(</span><span class="s">"roboto.png"</span><span class="p">),</span><span class="w"> </span><span class="kc">true</span><span class="p">);</span>
<span class="w"> </span><span class="n">robotoTexture</span><span class="p">.</span><span class="na">setFilter</span><span class="p">(</span>
<span class="w"> </span><span class="n">Texture</span><span class="p">.</span><span class="na">TextureFilter</span><span class="p">.</span><span class="na">MipMapLinearNearest</span><span class="p">,</span>
<span class="w"> </span><span class="n">Texture</span><span class="p">.</span><span class="na">TextureFilter</span><span class="p">.</span><span class="na">Linear</span>
<span class="w"> </span><span class="p">);</span>
<span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">robotoFont</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">BitmapFont</span><span class="p">(</span>
<span class="w"> </span><span class="n">Gdx</span><span class="p">.</span><span class="na">files</span><span class="p">.</span><span class="na">internal</span><span class="p">(</span><span class="s">"roboto.fnt"</span><span class="p">),</span>
<span class="w"> </span><span class="n">TextureRegion</span><span class="p">(</span><span class="n">robotoTexture</span><span class="p">),</span>
<span class="w"> </span><span class="kc">false</span>
<span class="w"> </span><span class="p">);</span>
<span class="w"> </span><span class="n">skin</span><span class="p">.</span><span class="na">addRegions</span><span class="p">(</span><span class="n">TextureAtlas</span><span class="p">(</span><span class="n">Gdx</span><span class="p">.</span><span class="na">files</span><span class="p">.</span><span class="na">internal</span><span class="p">(</span><span class="s">"skin.atlas"</span><span class="p">)));</span>
<span class="w"> </span><span class="n">skin</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">"font"</span><span class="p">,</span><span class="w"> </span><span class="n">robotoFont</span><span class="p">);</span>
<span class="w"> </span><span class="n">skin</span><span class="p">.</span><span class="na">load</span><span class="p">(</span><span class="n">Gdx</span><span class="p">.</span><span class="na">files</span><span class="p">.</span><span class="na">internal</span><span class="p">(</span><span class="s">"skin.json"</span><span class="p">));</span>
<span class="w"> </span><span class="p">...</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="kd">override</span><span class="w"> </span><span class="kd">fun</span><span class="w"> </span><span class="nf">dispose</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">super</span><span class="p">.</span><span class="na">dispose</span><span class="p">()</span>
<span class="w"> </span><span class="n">skin</span><span class="p">.</span><span class="na">dispose</span><span class="p">()</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>Результат, который получается я покажу на следующей неделе (очень на это надеюсь). Мой проект подходит к стадии, когда его уже надо показывать и собирать фидбек.</p>
Полезняшка: responses2016-03-09T10:00:00+05:002016-03-09T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-03-09:/2016/03/09/responses/<p>Если вы используете в своем проекте requests, то наверняка сталкиваетесь с проблемами тестирования. На помощь придет библиотека <a class="reference external" href="https://github.com/getsentry/responses">responses</a>, которая позволяет делать моки ответов вызовов requests.</p>
<p>Если вы используете в своем проекте requests, то наверняка сталкиваетесь с проблемами тестирования. На помощь придет библиотека <a class="reference external" href="https://github.com/getsentry/responses">responses</a>, которая позволяет делать моки ответов вызовов requests.</p>
<p>Мы в своем проекте используем эту библиотеку не так давно. Хотя из-за особенности окружения проекта мы ходим много в огромное количество REST API смежных сервисов. И до того, как requests попалась нам на глаза, код тестов представлял из себя нечто невообразимое трудно читаемое.</p>
<p>Пользоваться библиотекой невероятно просто. Для иллюстрации пара примеров из документации:</p>
<div class="section" id="section-1">
<h2>Пример 1</h2>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">responses</span>
<span class="kn">import</span> <span class="nn">requests</span>
<span class="nd">@responses</span><span class="o">.</span><span class="n">activate</span>
<span class="k">def</span> <span class="nf">test_my_api</span><span class="p">():</span>
<span class="n">responses</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">responses</span><span class="o">.</span><span class="n">GET</span><span class="p">,</span> <span class="s1">'http://twitter.com/api/1/foobar'</span><span class="p">,</span>
<span class="n">json</span><span class="o">=</span><span class="p">{</span><span class="s2">"error"</span><span class="p">:</span> <span class="s2">"not found"</span><span class="p">},</span> <span class="n">status</span><span class="o">=</span><span class="mi">404</span><span class="p">)</span>
<span class="n">resp</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'http://twitter.com/api/1/foobar'</span><span class="p">)</span>
<span class="k">assert</span> <span class="n">resp</span><span class="o">.</span><span class="n">json</span><span class="p">()</span> <span class="o">==</span> <span class="p">{</span><span class="s2">"error"</span><span class="p">:</span> <span class="s2">"not found"</span><span class="p">}</span>
<span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">responses</span><span class="o">.</span><span class="n">calls</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span>
<span class="k">assert</span> <span class="n">responses</span><span class="o">.</span><span class="n">calls</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">url</span> <span class="o">==</span> <span class="s1">'http://twitter.com/api/1/foobar'</span>
<span class="k">assert</span> <span class="n">responses</span><span class="o">.</span><span class="n">calls</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">response</span><span class="o">.</span><span class="n">text</span> <span class="o">==</span> <span class="s1">'{"error": "not found"}'</span>
</pre></div>
</div>
<div class="section" id="section-2">
<h2>Пример 2</h2>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">responses</span>
<span class="kn">import</span> <span class="nn">requests</span>
<span class="k">def</span> <span class="nf">test_my_api</span><span class="p">():</span>
<span class="k">with</span> <span class="n">responses</span><span class="o">.</span><span class="n">RequestsMock</span><span class="p">()</span> <span class="k">as</span> <span class="n">rsps</span><span class="p">:</span>
<span class="n">rsps</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">responses</span><span class="o">.</span><span class="n">GET</span><span class="p">,</span> <span class="s1">'http://twitter.com/api/1/foobar'</span><span class="p">,</span>
<span class="n">body</span><span class="o">=</span><span class="s1">'</span><span class="si">{}</span><span class="s1">'</span><span class="p">,</span> <span class="n">status</span><span class="o">=</span><span class="mi">200</span><span class="p">,</span>
<span class="n">content_type</span><span class="o">=</span><span class="s1">'application/json'</span><span class="p">)</span>
<span class="n">resp</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'http://twitter.com/api/1/foobar'</span><span class="p">)</span>
<span class="k">assert</span> <span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
<span class="c1"># outside the context manager requests will hit the remote server</span>
<span class="n">resp</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'http://twitter.com/api/1/foobar'</span><span class="p">)</span>
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">404</span>
</pre></div>
<p>Настоятельно рекомендую попробовать использовать в тестах responses. Местами мы наблюдаю неожиданный эффект: если пользоваться в тестах этой библиотекой, то часть кода, которая использует requests, можно переписать более внятно и компактно без оглядки на то, что какую-то часть нужно будет мокать для тестов.</p>
</div>
В чем разница между итератором и генератором?2016-02-29T10:00:00+05:002016-02-29T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-02-29:/2016/02/29/generator-vs-iterator/<p>Как ни странно, вопрос о разнице между генераторами и итераторами в Python - довольно частый вопрос. В общем-то эти сущности сильно связаны (любой генератор - это итератор), их довольно часто путают, что иногда приводит к недопониманиям.</p>
<p>Как ни странно, вопрос о разнице между генераторами и итераторами в Python - довольно частый вопрос. В общем-то эти сущности сильно связаны (любой генератор - это итератор), их довольно часто путают, что иногда приводит к недопониманиям.</p>
<p>Итератор - более общая концепция. Это объект, у которого определены два метода __next__ и __iter__.</p>
<p>С другой стороны, генератор - это итератор. Но не наоборот. Генератор может получаться использованием yield в теле функции.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">squares</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">stop</span><span class="p">):</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">stop</span><span class="p">):</span>
<span class="k">yield</span> <span class="n">i</span> <span class="o">*</span> <span class="n">i</span>
<span class="n">generator</span> <span class="o">=</span> <span class="n">squares</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span>
</pre></div>
<p>Либо использованием так называемых generator expression. На вроде такого:</p>
<div class="highlight"><pre><span></span><span class="n">generator</span> <span class="o">=</span> <span class="p">(</span><span class="n">i</span><span class="o">*</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">))</span>
</pre></div>
<p>Если посмотреть dir(generator) и в первом и во втором случае, то обнаружим, что оба варианта - итераторы (определены функции __next__ и __iter__).</p>
<p>По большому счету генераторы - это синтаксический сахар. И без них можно писать такие же эффективные программы используя только итераторы. Вот только читаемость и простота кода сильно снизятся. Так что, если не нужно от итератора ничего сверх естественного, то стоит подумать над использованием генератора.</p>
<p>В примерах и объяснениях я использовал Python 3. Для второй версии языка есть незначительные отличия.</p>
Сохраняем JSON в LibGDX2016-02-24T10:00:00+05:002016-02-24T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-02-24:/2016/02/24/json-libgdx/<p>Предыдущую неделю просидел дома. Из-за высокой температуры особо на рабочих задачах сконцентрироваться шансов не было. Но вот сторонними проектами удалось позаниматься. В очередной раз порадовался связке LibGDX и Kotlin.</p>
<p>Предыдущую неделю просидел дома. Из-за высокой температуры особо на рабочих задачах сконцентрироваться шансов не было. Но вот сторонними проектами удалось позаниматься. В очередной раз порадовался связке LibGDX и Kotlin.</p>
<p>Задачка такая: прочитать список объектов из JSON. Я уже приготовился писать много строк кода. Оказалось, что инструменты чтения JSON уже <a class="reference external" href="https://github.com/libgdx/libgdx/wiki/Reading-&-writing-JSON#customizing-serialization">встроены в LibGDX</a>. Это немного странно. Я ожидал, что для этой задачи потребуется сторонняя библиотека. Хотя с другой стороны, скин для UI описывается JSON, так что по сути разработчики LibGDX вытащили используемые хелперы в публичный API.</p>
<p>На Котлин получается такой код для чтения списка в массив:</p>
<div class="highlight"><pre><span></span><span class="kd">data</span><span class="w"> </span><span class="kd">class</span><span class="w"> </span><span class="nc">Word</span><span class="p">(</span><span class="kd">val</span><span class="w"> </span><span class="nv">word</span><span class="p">:</span><span class="w"> </span><span class="kt">String</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">""</span><span class="p">,</span><span class="w"> </span><span class="kd">val</span><span class="w"> </span><span class="nv">translation</span><span class="p">:</span><span class="w"> </span><span class="kt">String</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">""</span><span class="p">)</span>
<span class="kd">fun</span><span class="w"> </span><span class="nf">initWords</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">json</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Json</span><span class="p">()</span>
<span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nv">list</span><span class="p">:</span><span class="w"> </span><span class="n">ArrayList</span><span class="o"><*></span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">json</span><span class="p">.</span><span class="na">fromJson</span><span class="p">(</span><span class="n">ArrayList</span><span class="o">::</span><span class="n">class</span><span class="p">.</span><span class="na">java</span><span class="p">,</span><span class="w"> </span><span class="n">Gdx</span><span class="p">.</span><span class="na">files</span><span class="p">.</span><span class="na">internal</span><span class="p">(</span><span class="s">"words.json"</span><span class="p">))</span>
<span class="w"> </span><span class="n">words</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Array</span><span class="p">(</span><span class="n">list</span><span class="p">.</span><span class="na">size</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="n">i</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">json</span><span class="p">.</span><span class="na">readValue</span><span class="p">(</span><span class="n">Word</span><span class="o">::</span><span class="n">class</span><span class="p">.</span><span class="na">java</span><span class="p">,</span><span class="w"> </span><span class="n">list</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="n">JsonValue</span><span class="p">)})</span>
<span class="p">}</span>
</pre></div>
<p>Вообще это API отлично подходит для создания сейвов. Нужно только поверху навернуть шифрование (иначе игроки будут портить сейвы, а ловить потом такие ошибки очень сложно).</p>
Как вы управляете своими pet-проектами?2016-02-16T10:00:00+05:002016-02-16T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-02-16:/2016/02/16/pet-projects/<p>Сотни моих начинаний остаются недоделанным и даже близко не достигают целей, которые ставились перед проектом. И это при том, что, за частую, основная цель - просто изучить ту или иную технологию.</p>
<p>Сотни моих начинаний остаются недоделанным и даже близко не достигают целей, которые ставились перед проектом. И это при том, что, за частую, основная цель - просто изучить ту или иную технологию.</p>
<p>Самих идей проектов хоть отбавляй. Конечно, далеко не на все есть время. Но самые "сочные" идеи хотелось бы реализовать. Пусть не быстро, но хоть в какое-нибудь разумное время.</p>
<p>Из испробованных приемов у меня более или менее нормально работает выделение времени по утрам. Причем работает где-то половину года, когда светает достаточно рано. Заставить себя выбраться из под одеяла зимой - дохлый номер. Не помогает и какой-нибудь ритуал, который хорошо работает летом. Обычно все скатывается к просмотру какого-нибудь сериала лежа в кровати.</p>
<p>Работа над своими проектами, как награда за хорошую работу - не мой метод. Обычно после хорошего продуктивного рабочего дня делать ничего не хочется. Вернее в конце дня очень сложно сконцентрироваться на чем-либо. Усталость накапливается. В это время только написанием постов в блог заниматься.</p>
<p>Мне более или менее повезло, что обычно бывает несколько свободных часов по субботам. Тогда удается продвинуться хоть немного в каком-либо проекте на ощутимый результат.</p>
<p>Можно, конечно, бросить все сторонние проекты и не заниматься ими вовсе. Это не плохая стратегия. Я достаточно много нового изучаю во время работы. Но все-таки любопытство не унимается и не дает сидеть спокойно. Ведь большую часть проектов хочется сделать именно из любопытства, а не для резюме или ради денег.</p>
<p>Если у вас есть свои сторонние проекты, как вы ими управляете? Какой процент из проектов завершается успешно?</p>
Страсть к смене технологий: с Python на Lua2016-02-08T10:00:00+05:002016-02-08T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-02-08:/2016/02/08/from-python-to-lua/<p>Браян Майер из distelli опубликовал любопытную статью <a class="reference external" href="https://www.distelli.com/blog/using-lua-for-our-most-critical-production-code">Using Lua for Our Most Critical Production Code</a>. В статье расхваливается переход с одной технологии на другую. И как это помогло решить кучу проблем. При этом совсем не упоминается с какими проблемами при этом столкнулись. И совсем нет анализа какие проблемы могут возникнуть в будущем.</p>
<p>Браян Майер из distelli опубликовал любопытную статью <a class="reference external" href="https://www.distelli.com/blog/using-lua-for-our-most-critical-production-code">Using Lua for Our Most Critical Production Code</a>. В статье расхваливается переход с одной технологии на другую. И как это помогло решить кучу проблем. При этом совсем не упоминается с какими проблемами при этом столкнулись. И совсем нет анализа какие проблемы могут возникнуть в будущем.</p>
<p>По большому счету, смена технологии проекта в его середине - обычно очень плохая идея. Программистов надо вытащить из области в которой они разбираются в совершенно незнакомое окружение. Не могу сказать, что это всегда негативно сказывается на команде, но такие риски надо просчитывать. Если человека нанимали за его экспертные знания в Python, будет ли он счастлив писать на Lua?</p>
<p>Вторая опасность которую я вижу - опасность технологическая. Какие подводные камни есть у новой технологии? Хорошо, удалось решить одну проблему в одной области, а как на счет работоспособности технологии в других областях? Каковы границы ее применимости? Покрываются все задачи стоящие перед проектом? Что бы ответить на эти вопросы нужен эксперт в новой технологии. А обычно такого эксперта внутри команды нет. Его нужно либо приглашать со стороны, либо команде принимать на себя всю тяжесть возможных проблем.</p>
<p>Не всегда добавление новой технологии в проект или ее смена - это плохо. Иногда текущая не справляется с задачами проекта. Но обязательно смена должна быть хорошо обоснована с привлечением людей, которые используют предложенную технологию в продакшене.</p>
<p>Вам приходилось работать в командах, где лиды или, самый кошмарный вариант, менеджеры принимали решение добавить "новых" технологий в проект? Как вы с эти справлялись?</p>
В чем главная слабость Python, как языка программирования?2016-01-25T10:00:00+05:002016-01-25T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-01-25:/2016/01/25/python-weaknesses/<p>Я очень люблю Quora.com. В отличие от StackOverflow, здесь можно найти интересные обсуждения, не связанные с конкретными вопросами, а с общими ощущениями от технологий. Недавно наткнулся, к примеру, на такое обсуждение: <a class="reference external" href="https://www.quora.com/What-are-the-main-weaknesses-of-Python-as-a-programming-language">What are the main weaknesses of Python as a programming language?</a></p>
<p>Я очень люблю Quora.com. В отличие от StackOverflow, здесь можно найти интересные обсуждения, не связанные с конкретными вопросами, а с общими ощущениями от технологий. Недавно наткнулся, к примеру, на такое обсуждение: <a class="reference external" href="https://www.quora.com/What-are-the-main-weaknesses-of-Python-as-a-programming-language">What are the main weaknesses of Python as a programming language?</a></p>
<p>В основном Python ругают за тормознутость, сравнивая с компилируемыми языками. Я уже <a class="reference external" href="https://www.alexkorablev.ru/2015/12/14/why-python-so-popular/">писал</a>, что считаю это обвинение надуманным. Для меня скорость разработки гораздо важнее. Для CPU bound задач я бы в первую очередь на алгоритмами подумал, а потом использовал Cython. Да и не так уж много таких задач. А если ваш проект весь такой, то какого лешего вы выбрали скриптовый язык для этого?</p>
<p>Несколько удивило, что лямбды ругают достаточно много. Не нравится, что можно сделать лямбду только с одним выражением. По мне так это очень хорошо. Нет соблазна заворачивать в лямбду слишком сложные конструкции.
Ругают значимые отступы. По мне - чистой воды вкусовщина.</p>
<p>Первый обоснованный наезд - странное поведение списка в параметрах функции.</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">my_list</span><span class="o">=</span><span class="p">[]):</span>
<span class="o">...</span> <span class="n">my_list</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="o">...</span> <span class="k">return</span> <span class="n">my_list</span>
<span class="o">...</span>
<span class="o">>>></span> <span class="n">foo</span><span class="p">()</span>
<span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="o">>>></span> <span class="n">foo</span><span class="p">()</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">]</span>
<span class="o">>>></span> <span class="n">foo</span><span class="p">()</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">]</span>
<span class="o">>>></span>
</pre></div>
<p>Согласен на все сто. Обосновано.</p>
<p>Второй обоснованный наезд - GIL. Но на самом деле не так уж много задач, где можно вляпаться в GIL. И веб это точно не из их числа.</p>
<p>Третий обоснованный наезд - несовместимость 2 и 3 ветки. Появление третьего питона - это большая боль для сообщества. И хотя он появился уже очень-очень давно, до сих пор актуально написание кода на Python 2 с оглядкой на то, что когда-нибудь в светлом будущем будет переезд проекта на Python 3.</p>
<p>Полу обоснованный наезд - отсутствие IDE. Эмм... А для других скриптовых языков есть IDE, которые делают столько же магии, как IDE для Java? Python слишком гибок для IDE. Часто еще на этапе проектирования проекта видно, в каком месте автокомплит отвалится.</p>
<p>А вообще самое важно в языке программирования не синтаксис, а окружение (библиотеки, коммьюнити, проекты, вакансии и т.п.). И с этим у питона все ОК.</p>
Приватных полей в питоне нет2016-01-18T10:00:00+05:002016-01-18T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-01-18:/2016/01/18/pivate-variable/<p>Достаточно часто встречаю вопросы о приватных полях и методах в Python от людей, кто раньше программировал на Java (или С++). Вот только в питоне класс - это не совсем то же, что и в Java. В питоне класс - это просто контейнер, содержимое которого доступно всем.</p>
<p>"Приватные" поля - это всего лишь договоренность …</p><p>Достаточно часто встречаю вопросы о приватных полях и методах в Python от людей, кто раньше программировал на Java (или С++). Вот только в питоне класс - это не совсем то же, что и в Java. В питоне класс - это просто контейнер, содержимое которого доступно всем.</p>
<p>"Приватные" поля - это всего лишь договоренность по именам, которая никак не мешает работать с такими полями из любого места кода. Добавление имени класса к имени переменной - слабая защита.</p>
<p>Из этого вытекает пара правил использования подобных переменных:</p>
<ul class="simple">
<li>Не особо рассчитывайте, что значение "приватной" переменной всегда будет правильным. Проверками в критичных местах системы не стоит пренебрегать.</li>
<li>Либо из имени переменной, либо из кода ее окружающего, либо из документации должно быть понятно что она делает и какие методы доступа к ней использовать. Особенно актуально, если переменная - список или словарь.</li>
</ul>
Я влюбился в Django. Снова. А вы что сделали за новогодние каникулы?2016-01-11T10:00:00+05:002016-01-11T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-01-11:/2016/01/11/fall-in-love-with-django/<p>Еще 31 декабря прошлого года у меня были гигантские планы на каникулы. Как минимум я должен был закончить один из своих сайт проектов. К сожалению, работе удалось уделить всего 4 часа. Но за это время я успел снова влюбиться в Django.</p>
<p>Кажется я где-то уже об этом писал, Django - это …</p><p>Еще 31 декабря прошлого года у меня были гигантские планы на каникулы. Как минимум я должен был закончить один из своих сайт проектов. К сожалению, работе удалось уделить всего 4 часа. Но за это время я успел снова влюбиться в Django.</p>
<p>Кажется я где-то уже об этом писал, Django - это мой проводник в мир питона. Именно благодаря этому фреймворку меня вырвало из хищных лап PHP. И первое время к питону я относился как к досадной необходимости, без которой Django не работает.</p>
<p>Только через пару лет, когда я начал работать настоящим python-разработчиком, я осознал всю прелесть Python. И с удивлением обнаружил, что этот язык не строится вокруг одного только Django.</p>
<p>Последние два года, я работаю с Flask. Это отличный фреймворк с понятной концепцией. Но все-таки мне не хватало того ощущения стройности, которое дает Django.</p>
<p>Из Flask можно сделать "Django". Модуль админки - есть, ORM - есть, формы - есть. Но в отличие от Django модули не так хорошо подогнаны друг к другу. Они не так хорошо работают вместе. И главное по ним сложнее найти документацию, примеры, сниппеты и советы, когда возникают вопросы по их совместной работе.</p>
Постновогоднее. Целеполагательное2016-01-05T10:00:00+05:002016-01-05T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2016-01-05:/2016/01/05/agile-results/<p>Наступил Новый год. Самое время разобраться с целями. Конечно, лучше бы это было сделать еще в конце декабря, но лучше поздно, чем никогда. Сегодня я расскажу, как это делаю я.</p>
<p>Почему-то у меня не работает излюбленный народом метод: в Новогоднюю ночь написать желание на бумажке, бумажку сжечь, пепел положить в …</p><p>Наступил Новый год. Самое время разобраться с целями. Конечно, лучше бы это было сделать еще в конце декабря, но лучше поздно, чем никогда. Сегодня я расскажу, как это делаю я.</p>
<p>Почему-то у меня не работает излюбленный народом метод: в Новогоднюю ночь написать желание на бумажке, бумажку сжечь, пепел положить в бокал шампанского и выпить его. Наверное, все из-за того, что я не пью шампанское. И уже довольно давно. Так что за последние лет 7-8 я перепробовал кучу методик: от жесткого тайм-менеджмента, до GTD.</p>
<p>Последние два года для постановки целей я использую немного модифицированную систему Agile Results. Основная идея этой системы: есть три главные цели на каждый временной интервал: день, неделю, месяц. Годовых целей больше: по три для каждой сферы (карьера, семья, финансы, здоровье и т.д.).</p>
<p>Цели года берутся с потолка: пишем по три самых заветных желания на год в каждую сферу. А цели более низких порядков выстраиваются в иерархию: цели месяца зависят от годовых, недельные - от месячных, дневные - от недельных. Не обходится без примеси текучки и срочных задач, но в целом около половины целей на день работают на перспективу.</p>
<p>Цели для разных временных рамок должны быть разными с разным уровнем детализации. В достижении годовых целей сильно мешает излишняя детализация. К примеру, на 2015 год я ставил цель посмотреть 10 конкретных курсов лекций по технологиям. По факту я посмотрел 12, но всего два курса из тех, что планировал изначально.</p>
<p>Так что годовые планы не то место, где нужно хранить ссылки на видео, которое надо посмотреть, и курсы, которые нужно пройти. С другой стороны дневная цель должна быть максимально конкретной. Что бы не возникало вопросов, что выполнена она или нет.
Важно, все цели ставятся мной по технике SMART. Каждая цель должна быть конкретной, измеримой, достижимой, актуальной и ограниченной по времени (последнее получается автоматически).</p>
<p>Из замеченного, годовые финансовые цели достигаются проще всего. Так что их нужно ставить амбициознее. Пусть в начале года они выглядят немного пугающе.</p>
<p>А какие методики используете вы?</p>
<p><strong>P.S.</strong> Сама по себе такая постановка задач отлично работает и мотивирует. Но иногда нужна дополнительная подпитка, особенно для целей, которые требуют простой ежедневной работы, как изучение английского, на пример. В качестве этой подпитки я использую Habitica.</p>
<p><strong>P.P.S.</strong> Я не люблю работать в потоке. Для профессионального программиста - это не очень хорошее состояние. В долгосрочной перспективе это приведет к выгоранию. Да и код обычно не самый хороший получается. Для нормализации ритма в работе использую Pomodoro.</p>
Django или Flask?2015-12-28T10:00:00+05:002015-12-28T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-12-28:/2015/12/28/django-vs-flask/<p>Решил восстановить свой сайт о настольных играх - VseNastolki.ru. Когда-то там был интернет-магазин, но поскольку теперь на столь серьезный проект меня не хватит, я буду делать всего лишь базу настольных игр. Оформив идею, я как настоящий программист взялся за выбор фреймворка: Flask или Django.</p>
<p>Решил восстановить свой сайт о настольных играх - VseNastolki.ru. Когда-то там был интернет-магазин, но поскольку теперь на столь серьезный проект меня не хватит, я буду делать всего лишь базу настольных игр. Оформив идею, я как настоящий программист взялся за выбор фреймворка: Flask или Django.</p>
<p>Другие фреймворки я не рассматривал. Причина проста: у меня нет опыта работы с ними. Проект хочется закончить. Так что использовать неизвестный фреймворк для этого не хочется. Так что в кандидатах остались только Flask - наш текущий фремворк на работе - и Django - фреймворк из-за которого я попал в мир Python.</p>
<p>На Flask и Django можно сделать практически любой сайт или веб-приложение. И даже не так важно, какой из-них будет выбран. Но все-таки есть области, где Django предпочтительнее, а есть и такие, где Flask подойдет лучше.</p>
<p>Django силен своей стройностью и своей закрытостью. Для всего есть есть "правильный" инструмент, который является частью фреймворка. И если ваш проект хорошо ложится на эти инструменты, если нужно сохранить большое количество данных, редактировать и отображать, то Django - идеальный выбор. Более хитрые манипуляции с данными Django под силу, но требует больше усилий.</p>
<p>Flask - это гибкость. Практически все элементы фреймворка не обязательны. Это плюс, если нужно делать что-то уникальное. Но скорее минус, если вы делаете обыкновенный сайт с большим количеством однородных по структуре документов. Слишком много придется писать руками. С другой стороны, если проект требует что-то переделать в Django, то трудозатраты будут в разы больше, чем реализовать аналогичную фичу на Flask.</p>
<p>А какой фремворк предпочитаете вы?</p>
Деление данных по коллекциям в MongoDB2015-12-21T10:00:00+05:002015-12-21T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-12-21:/2015/12/21/mongo/<p>Не надо лезть в MongoDB с реляционным подходом. Этот тезис вроде бы очевиден, но когда дело доходит до реализации, то возникает множество вопросов. И 100% одним из них будет "как делать join?" Правильный ответ: никак. Да, в 3.2 появился $lookup в агрегациях. Отчасти это замена классическому join из реляционного мира. Но в целом агрегации не самый быстрый в монге инструмент. Лучше когда запрос идет к одной коллекции и это обычный find.</p>
<p>Не надо лезть в MongoDB с реляционным подходом. Этот тезис вроде бы очевиден, но когда дело доходит до реализации, то возникает множество вопросов. И 100% одним из них будет "как делать join?" Правильный ответ: никак. Да, в 3.2 появился $lookup в агрегациях. Отчасти это замена классическому join из реляционного мира. Но в целом агрегации не самый быстрый в монге инструмент. Лучше когда запрос идет к одной коллекции и это обычный find.</p>
<p>Стоит отметить, что использование NoSQL не обходится бесплатно. К примеру, обеспечение консистентности данных встроено в реляционные базы по умолчанию. При использовании MongoDB, задача обеспечить консистентность ложится на программу-клиент.
Будет лучше посмотреть как это происходит на примере с постом в блог. Рассмотрим сначала как его представить в обычной реляционной БД в нормализованном виде, а потом разные варианты представления постов в MondoDB.</p>
<p>Предположим, что нам нужно решить следующие задачи:</p>
<ul class="simple">
<li>выводить список постов с заголовками, категорией и тегами</li>
<li>выводить отдельный пост</li>
<li>выводить список тегов</li>
<li>выводить список категорий</li>
<li>редактировать имена тегов и категорий</li>
</ul>
<p>В реляционной базе, скорее всего, структура БД будет примерно такой:</p>
<ul class="simple">
<li><dl class="first docutils">
<dt>posts</dt>
<dd><ul class="first last">
<li>id</li>
<li>title</li>
<li>body</li>
<li>author_id</li>
<li>category_id</li>
<li>slug</li>
</ul>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt>authors</dt>
<dd><ul class="first last">
<li>id</li>
<li>name</li>
</ul>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt>categories</dt>
<dd><ul class="first last">
<li>id</li>
<li>title</li>
<li>slug</li>
</ul>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt>tags</dt>
<dd><ul class="first last">
<li>id</li>
<li>name</li>
<li>slug</li>
</ul>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt>posts_tags</dt>
<dd><ul class="first last">
<li>post_id</li>
<li>tag_id</li>
</ul>
</dd>
</dl>
</li>
</ul>
<p>Есть ряд вариантов как организовать документы с постами в монге.</p>
<p>Можно хранить данные в одной коллекции:</p>
<ul class="simple">
<li><dl class="first docutils">
<dt>posts</dt>
<dd><ul class="first last">
<li>_id</li>
<li>title</li>
<li>body</li>
<li>author</li>
<li><dl class="first docutils">
<dt>category</dt>
<dd><ul class="first last">
<li>title</li>
<li>slug</li>
</ul>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt>tags</dt>
<dd><ul class="first last">
<li><dl class="first docutils">
<dt>[</dt>
<dd><ul class="first last">
<li>name</li>
<li>slug</li>
</ul>
</dd>
</dl>
</li>
<li>]</li>
</ul>
</dd>
</dl>
</li>
<li>slug</li>
</ul>
</dd>
</dl>
</li>
</ul>
<p>Такая структура хороша тем, что список постов и вывод отдельного поста решается одним запросом. Мы сразу получим все необходимы данные. Список тегов и категорий придется строить с помощью агрегаций. Если их количество не велико, то можно результаты агрегаций просто хранить в памяти или кэшировать. Редактировать имена тегов и категорий можно достаточно простыми апдейтами. Что при наличии индексов сделает эту операцию относительно быстрой.</p>
<p>Такой подход подойдет для относительно небольшой коллекции. На большом количестве документов операции обновления начнут заметно тормозить. К тому же, хоть в данном примере это и маловероятно, если документы у вас сложные и содержат много данных, то можно упереться в предельный размер документа монги. На "живой" базе разрывать документы по отдельным коллекциям весьма сомнительное удовольствие.</p>
<p>Второй вариант - вынести данные тегов и категорию в отдельные коллекции, посты хранят только id тегов. По сути сделать обычную нормализацию немного в монговском стиле: для связи many-to-many тегов и постов не делаем отдельную коллекцию, а храним в постах список id тегов.</p>
<p>Коллекции в этом случае буду выглядеть так:</p>
<ul class="simple">
<li><dl class="first docutils">
<dt>categories</dt>
<dd><ul class="first last">
<li>_id</li>
<li>title</li>
<li>slug</li>
</ul>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt>tags</dt>
<dd><ul class="first last">
<li>_id</li>
<li>name</li>
<li>slug</li>
</ul>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt>posts</dt>
<dd><ul class="first last">
<li>_id</li>
<li>title</li>
<li>body</li>
<li>author</li>
<li>category_id</li>
<li><dl class="first docutils">
<dt>tags</dt>
<dd><ul class="first last">
<li>[tag_id]</li>
</ul>
</dd>
</dl>
</li>
<li>slug</li>
</ul>
</dd>
</dl>
</li>
</ul>
<p>Отлично. Проблему с редактированием категорий и тегов решили. Теперь эта операция занимает константное время.
Построить список тегов и категорий не проблема. Но бесплатного ничего не бывает. Теперь для построения поста нам надо сделать 3 запроса: получить сам пост, получить категорию, получить теги. Аналогичные проблемы придется решать при построении списка постов.</p>
<p>Этот способ подойдет тогда, когда имена категорий и тегов меняются часто, но их количество не очень велико. При этом нет жестких требований к скорости отдачи постов на чтение.</p>
<p>Можно построить еще такую гибридную схему:</p>
<ul class="simple">
<li><dl class="first docutils">
<dt>categories</dt>
<dd><ul class="first last">
<li>_id</li>
<li>title</li>
<li>slug</li>
</ul>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt>tags</dt>
<dd><ul class="first last">
<li>_id</li>
<li>name</li>
<li>slug</li>
</ul>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt>posts</dt>
<dd><ul class="first last">
<li>_id</li>
<li>title</li>
<li>body</li>
<li>author</li>
<li><dl class="first docutils">
<dt>category</dt>
<dd><ul class="first last">
<li>category_id</li>
<li>title</li>
<li>slug</li>
</ul>
</dd>
</dl>
</li>
<li><dl class="first docutils">
<dt>tags</dt>
<dd><ul class="first last">
<li><dl class="first docutils">
<dt>[</dt>
<dd><ul class="first last">
<li>tag_id</li>
<li>name</li>
<li>slug</li>
</ul>
</dd>
</dl>
</li>
<li>]</li>
</ul>
</dd>
</dl>
</li>
<li>slug</li>
</ul>
</dd>
</dl>
</li>
</ul>
<p>По сути, мы добавляем поля, которых нам не хватало во втором варианте, что бы отдавать пост за один запрос. При этом возникают проблемы с редактированием: нужно не только поменять тег в коллекции тегов, но и пройтись по постам и выправить тег там. Но в отличие от первого варианта, в этом легче написать код, который будет выправлять посты асинхронно в фоновом режиме.</p>
<p>Пример с блогом очень искусственный (я бы вообще 10 раз подумал прежде, чем делать это не на реляционной базе), но даже на нем видно, что все подходы к разделению данных по коллекциям требуют написания какого-то количества дополнительного кода. И правильно оценив свою предметную область и объемы данных, можно прикинуть где кода будет меньше, он будет проще и не будет провалов по производительности.</p>
<p><em>PS</em> Понимаю, что я не то что Капитан Очевидность, а даже адмирал Ясен Пень. Но подобные вопросы с MongoDB (да и в целом с NoSQL) возникают с завидной регулярностью.</p>
Почему Python такой популярный, если он такой медленный?2015-12-14T10:00:00+05:002015-12-14T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-12-14:/2015/12/14/why-python-so-popular/<p>На Quora обсуждают <a class="reference external" href="https://www.quora.com/Why-is-Python-so-popular-despite-being-so-slow">почему Python такой популярный, хотя такой медленный</a>. Тема конечно холиварная. Но порассуждать на нее интересно. Ведь Python действительно медленный.</p>
<p>На Quora обсуждают <a class="reference external" href="https://www.quora.com/Why-is-Python-so-popular-despite-being-so-slow">почему Python такой популярный, хотя такой медленный</a>. Тема конечно холиварная. Но порассуждать на нее интересно. Ведь Python действительно медленный.</p>
<p>Медленный, если сравнивать его с C/C++ или Java, или C#. Это факт. Python достаточно медленный скриптовый динамический язык программирования. Любая метрика покажет, что программа на C++ будет работать быстрее. Но есть у языков и другая не менее важная метрика: скорость разработки.</p>
<p>Вот тут динамические языки начинают выигрывать. Разрабатывать на Python, Ruby, JS быстрее, чем на Java. А скорость разработки - это прямая экономия для компании. Сегодня разработка продукта заканчивается только, если проект закрывают. В остальных случаях купить еще один инстанс у амазона дешевле, чем увеличить срок разработки какой-либо фичи на пару недель.</p>
<p>В добавок, высокая производительность - не самоцель. Программа должна решать задачу с приемлемой производительностью. Задач где производительность критичная не так много. И даже тогда правильный алгоритм влияет на скорость больше, чем выбор языка реализации.</p>
<p>Не стоит забывать, что процессор в последнее время далеко не всегда является узким местом. Скорее всего, производительность упрется в жесткий диск или сеть, чем в процессор.</p>
<p>Еще один плюс Python (теперь уже по сравнению "одноклассниками") - его простой и читаемый синтаксис. Я слышал много историй, когда ученые, далекие от программирования, предпочитали делать наброски вычислений на Python. Не из-за наличия SciPy/NumPy, а именно из-за того, что такую программу проще написать и объяснить коллегам. Программистам работающим в командах простой синтаксис то же в плюс.</p>
<p>Конечно "батарейки" важны. За это, пожалуй, любят питон ребята из Data Science. Наверняка, для статистических расчетов и анализа есть более интересные инструменты. Но данные для таких расчетов нужно еще извлечь и подготовить. А потом оказывается, что считать на Python так же удобно.</p>
Sublime Text 3 как Python IDE2015-12-07T10:00:00+05:002015-12-07T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-12-07:/2015/12/07/python-ide/<p>Пока на RealPython разжигают войну IDE и объясняют как настроить под разработку на Python под <a class="reference external" href="https://realpython.com/blog/python/setting-up-sublime-text-3-for-full-stack-python-development/">Sublime</a>, <a class="reference external" href="https://realpython.com/blog/python/vim-and-python-a-match-made-in-heaven/">VIM</a> и <a class="reference external" href="https://realpython.com/blog/python/emacs-the-best-python-editor/">Emacs</a>, я расскажу почему я переехал (пока) на Sublime Text 3, как я его настроил. А также отмечу чего в нем не хватает для счастливой разработки.</p>
<p>Пока на RealPython разжигают войну IDE и объясняют как настроить под разработку на Python под <a class="reference external" href="https://realpython.com/blog/python/setting-up-sublime-text-3-for-full-stack-python-development/">Sublime</a>, <a class="reference external" href="https://realpython.com/blog/python/vim-and-python-a-match-made-in-heaven/">VIM</a> и <a class="reference external" href="https://realpython.com/blog/python/emacs-the-best-python-editor/">Emacs</a>, я расскажу почему я переехал (пока) на Sublime Text 3, как я его настроил. А также отмечу чего в нем не хватает для счастливой разработки.</p>
<p>PyCharm замечательная IDE для Python. Это несомненно, но если нужно разрабатывать full-stack, то им одним не обойтись. На работе это не проблема. Но вот дома покупать лицензию на все продукты JetBrains ради экспериментов накладно. Две недели назад я решил посмотреть альтернативы. Vim и Emacs отвалились из-за их идеологии и сложности в настройке. Eclipse заставляет тормозить даже очень мощные компьютеры, что говорить про мой слабенький домашний лэптоп. По факту в кандидатах остался только Sublime Text.</p>
<p>Он у меня и так открыт постоянно. В нем я веду TODO-листы, в нем пишу в блог. В нем же открываю большую часть файлов, которые не являются частью проекта. При этом больших требований к IDE у меня нет. Нужны в общем-то только эти вещи:</p>
<ul class="simple">
<li>Подсветка синтаксиса</li>
<li>Умный автокомплит</li>
<li>Переход к определениям</li>
<li>Проверка файла на ошибки</li>
<li>Автоимпорт</li>
</ul>
<p>Подсветка синтаксиса в Sublime есть из коробки. Остальное настраивается достаточно легко. Pycharm'овский дебагер легко заменяется.</p>
<p>Мои настройки саблайма похожи на предложенный вариант от RealPython. В качестве базы для IDE я использую <a class="reference external" href="https://packagecontrol.io/packages/Anaconda">Anaconda</a>. Настройки не менял. Только в пользовательских настройках прописал интерпретатор, иначе не работал переход к определению, да выключил линтинг:</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="w"> </span><span class="nt">"anaconda_linting"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
<span class="w"> </span><span class="nt">"python_interpreter"</span><span class="p">:</span><span class="w"> </span><span class="s2">"python"</span>
<span class="p">}</span>
</pre></div>
<p>Анаконда использует pyflakes в качестве линтера. Он работает прекрасно. Но я предпочитаю использовать <a class="reference external" href="https://packagecontrol.io/packages/SublimeLinter-pyflakes">SublimeLinter-pyflakes</a> (требует для своей работы <a class="reference external" href="https://packagecontrol.io/packages/SublimeLinter">SublimeLinter</a>). Подойдет если нужны одинаковые настройки линтеров под разные языки.</p>
<p>Для проверки форматирования я использую <a class="reference external" href="https://packagecontrol.io/packages/SublimeLinter-pep8">SublimeLinter-pep8</a> с такими настройками:</p>
<div class="highlight"><pre><span></span><span class="w"> </span><span class="nt">"pep8"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nt">"@disable"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
<span class="w"> </span><span class="nt">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span>
<span class="w"> </span><span class="nt">"excludes"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span>
<span class="w"> </span><span class="nt">"ignore"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="p">,</span>
<span class="w"> </span><span class="nt">"max-line-length"</span><span class="p">:</span><span class="w"> </span><span class="mi">120</span><span class="p">,</span>
<span class="w"> </span><span class="nt">"select"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span>
<span class="p">},</span>
</pre></div>
<p>Проверка на достаточно большом и сложном проекте показала, что автокомплит и переход к определениям работает не хуже, чем в PyCharm.</p>
<p>Интеграция с Git обеспечивают <a class="reference external" href="https://packagecontrol.io/packages/Git">Git</a> и <a class="reference external" href="https://packagecontrol.io/packages/GitGutter">GitGutter</a>. Первый помогает в простых случаях взаимодействовать с гитом не выходя из редактора, добавляя команды гита в Control Panel. Второй - подсвечивает измененные строки в файле.</p>
<p>По итогам двух недель единственная сложность, которая возникла - разрешение конфликтов мерджа. Инструмент для мерджа в Pycharm незаменим. И пока похоже, что только он сможет заставить меня отказаться от использования Sublime Text.</p>
Flask: что почитать2015-11-30T10:00:00+05:002015-11-30T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-11-30:/2015/11/30/flask-reading-list/<p>Обновляемый список полезных ссылок с материалами по Flask.</p>
<p>Версия: 1.0. Последнее обновление: 30.11.2015.</p>
<p>Обновляемый список полезных ссылок с материалами по Flask.</p>
<p>Версия: 1.0. Последнее обновление: 30.11.2015.</p>
<div class="section" id="section-1">
<h2>Начинающим</h2>
<ul class="simple">
<li><a class="reference external" href="http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world">The Flask Mega-Tutorial</a> - подробный пошаговый учебник по веб-фремворку Flask. Покрывает все основные темы: шаблоны, формы, базу данных, интернационализацию, деплоймент на сервере.</li>
<li><a class="reference external" href="https://youtu.be/DIcpEg77gdE">Flask Workshop</a> - трехчасовое видео от этого же автора. Демонстрация основных возможностей фласка на живом примере.</li>
<li>Еще один учебник <a class="reference external" href="http://simplectic.com/blog/2014/flask-todomvc-part1/">Flask TodoMVC</a>. На этот раз делают не блог, а туду список. Плюс этого учебника в том, что рассматривается не отдельно Flask, а Flask в связке с JS фреймворком <a class="reference external" href="http://backbonejs.org/">Backbone.js</a>.</li>
<li><a class="reference external" href="https://realpython.com/blog/python/flask-by-example-part-1-project-setup/">Flask by Example</a> - учебный пример с Flask, Redis и Angular.</li>
<li>Если еще не устали от учебников и учебных проектов, то посмотрите еще на один от Real Python: <a class="reference external" href="https://realpython.com/blog/python/python-web-applications-with-flask-part-i/">Python Web Applications With Flask</a>. Помимо того, что это хороший подробный и понятный учебник, в нем описывается переход от небольшого приложения к среднему. Во Flask - это не тривиальная задача. А в <a class="reference external" href="https://realpython.com/blog/python/python-web-applications-with-flask-part-iii/">третьей части</a> рассматриваются вопросы тестирования и отладки приложения.</li>
</ul>
</div>
<div class="section" id="section-2">
<h2>Опытным пользователям</h2>
<ul class="simple">
<li>Статья <a class="reference external" href="https://syncano.io/blog/advanced-concepts-flask/">Advanced Concepts in Flask</a> описывает концепции и паттерны, которые необходимы при работе над сложными проектами, большими приложениями: blueprints, contexts.</li>
<li>Предыдущая статья отчасти базируется на докладе Advanced Flask Patterns от Армина Ронахера: <a class="reference external" href="https://speakerdeck.com/mitsuhiko/advanced-flask-patterns-1">слайды</a> и <a class="reference external" href="http://www.youtube.com/watch?v=6CeXt62Dt2A">видео</a>.</li>
<li><a class="reference external" href="https://realpython.com/blog/python/dockerizing-flask-with-compose-and-machine-from-localhost-to-the-cloud/">Dockerizing Flask With Compose and Machine</a> - статья о настройке контейнеров для фласк-приложения. Real Python сделали хорошую "болванку", которая подойдет под большинство проектов (после обработки напильником, конечно).</li>
</ul>
<p>Этот список не окончательный. Я его буду пополнять, если встречу интересную и полезную статью об этом замечательно фреймворке. Если у вас есть ссылки на интересные материалы, пишите в комментариях.</p>
</div>
Первые вречатления от LibGDX2015-11-23T10:00:00+05:002015-11-23T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-11-23:/2015/11/23/libgdx-impressions/<p>Мой первый проект на <a class="reference external" href="https://libgdx.badlogicgames.com/">LibGDX</a> вышел на завершающую стадию. Еще осталось сделать достаточно много для подготовки к запуску, но я уже могу сравнить эту библиотеку с Unity и Unreal Engine.</p>
<p>Мой первый проект на <a class="reference external" href="https://libgdx.badlogicgames.com/">LibGDX</a> вышел на завершающую стадию. Еще осталось сделать достаточно много для подготовки к запуску, но я уже могу сравнить эту библиотеку с Unity и Unreal Engine.</p>
<p>Пару месяцев назад, когда я выбирал на чем делать маленькую 2d игру, я очень долго ходил вокруг Unity и Unreal Engine. С Unity у меня уже был опыт работы: пару поделок я на нем завершил. Основная боль - отсутствие нормальной поддержки 2d. Она есть, но какая-то кривая. Когда-нибудь я обязательно про это напишу, но сегодня оставлю этот тезис без доказательств.</p>
<p>UE я попробовал, но он жутко тормозил и переводил электричество в тепло на моем не самом слабом Macbook Pro. А работа на моем домашнем ноутбуке и вообще превращалась в муку. Да и проблема та же что и с Unity: слабая поддержка 2d.</p>
<p>Тогда мне на глаза попалась библиотека LibGDX:</p>
<ul class="simple">
<li>Поддерживает интересующие меня платформы: Android и десктоп. С помощью костылей можно делать iOS (с помощью <a class="reference external" href="https://robovm.com/">RoboVM</a>) и WebGL.</li>
<li>Основной язык разработки - Java. В общем-то, работает любой JVM язык. К примеру, я быстро пересел на Kotlin.</li>
<li>Хорошая поддержка 2d. И хорошая библиотека для UI.</li>
<li>Полный контроль на кодом.</li>
</ul>
<p>Одни плюсы и никаких минусов... Пока не начнешь работать. В ходе работы выяснилось:</p>
<ul class="simple">
<li>UI разрабатывается быстрее и проще, чем на Unity, но шкурку для него сделать сложнее. Я несколько дней мучился, пока не нашел <a class="reference external" href="http://pimentoso.blogspot.ru/2013/04/libgdx-scene2d-skin-quick-tutorial.html">статью от Pimentoso</a>, где они советуют делать только минимально необходимое. Может это просто я такой тормоз, но мне помогло.</li>
<li>Местами очень странная документация. Одни куски API документированы прекрасно, другие - никак. Хорошо, что у библиотеки большое коммьюнити и найти ответы на большую часть вопросов не проблема.</li>
<li>У библиотеки нет встроенного редактора сцен. Нужно пользоваться внешними инструментами вроде <a class="reference external" href="http://overlap2d.com/">Overlap2d</a>.</li>
<li>На форумах часто жалуются, что с обновлениями библиотеки может отвалиться то, что работало. Но это же пишут про Unity и UE, так что можно этот пункт игнорировать.</li>
</ul>
<p><strong>P.S.</strong> Отвечаю на вопрос, почему не <a class="reference external" href="http://kivy.org">kivy</a> или не <a class="reference external" href="http://www.pygame.org/hifi.html">PyGame</a>. При поиске они практически не появлялись на радарах. Коммьюнити у этих библиотек более чем скромное по сравнению с LibGDX. PyGame вообще выглядит заброшенным.</p>
Конвертирование андроид приложения на Kotlin2015-11-16T10:00:00+05:002015-11-16T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-11-16:/2015/11/16/converting-plaid-to-kotlin/<p>На прошлой неделе наткнулся на очень интересную статью от Антонио Лева: <a class="reference external" href="http://antonioleiva.com/plaid-kotlin-1">Converting Plaid to Kotlin: Lessons learned</a>. В ней автор на примере одного приложения по андроид сравнивает Kotlin и Java. Цель - выявить преимущества и недостатки разработки на Kotlin.</p>
<p>На прошлой неделе наткнулся на очень интересную статью от Антонио Лева: <a class="reference external" href="http://antonioleiva.com/plaid-kotlin-1">Converting Plaid to Kotlin: Lessons learned</a>. В ней автор на примере одного приложения по андроид сравнивает Kotlin и Java. Цель - выявить преимущества и недостатки разработки на Kotlin.</p>
<p>Если посмотреть на числа, которые приводит автор, то по ним видно, что на Kotlin код получается значительно компактнее (на 22% меньше строк, на 27% меньше символов). Это значит, что программировать на Kotlin значительно быстрее, чем на Java. Да и приятнее (но это цифрами не измерить).</p>
<p>Не обошлось без ложки дегтя. За все надо платить. Размер APK и количество вызовов подросло на 14% и 38% соответственно. Хуже, что компиляция после изменения одной строки кода занимает на 190% больше по сравнению с Java.</p>
<p>Возросшее время компиляции - для меня действительно большая потеря времени. Я пришел из скриптовых языков. Мой цикл разработки очень короткий. И эти дополнительные 19 секунд достаточно быстро превращаются в минуты и часы.</p>
<p>С другой стороны, по моим ощущениям, скорость написания кода на Kotlin с лихвой покрывает этот недостаток. А компилятор со временем поправят.</p>
<p>А вы уже пробовали Kotlin под андроид?</p>
Highload++ 20152015-11-11T15:00:00+05:002015-11-11T15:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-11-11:/2015/11/11/highload-2015/<p>На прошлой неделе прошла конференция Highload++ 2015. Конференция большая. Доклады шли в 5 потоков. Казалось бы выбор большой, но если отбросить секцию менеджмента и секцию фронтовой разработки, то останется PostgreSQL на каждом втором докладе. Один из моих коллег так высказался о конференции: "Highload++ 2015 - 40% PostgreSQL, 40% очереди, 20% приправы".</p>
<p>На прошлой неделе прошла конференция Highload++ 2015. Конференция большая. Доклады шли в 5 потоков. Казалось бы выбор большой, но если отбросить секцию менеджмента и секцию фронтовой разработки, то останется PostgreSQL на каждом втором докладе. Один из моих коллег так высказался о конференции: "Highload++ 2015 - 40% PostgreSQL, 40% очереди, 20% приправы".</p>
<p>Не буду рассказывать о всех докладах, которые я посетил. Как и на всякой конференции, было много отстойных докладов. Я расскажу немного о трех, на мой взгляд, самых достойных доклада конференции. Если будете покупать видео с этой конференции, то записи первых двух докладов стоят того, что бы их посмотреть. Про третий доклад разговор отдельный.</p>
<div class="section" id="stack-overflow-it-s-all-about-performance">
<h2>Stack Overflow - It's all about performance!</h2>
<p>Пожалуй самый интересный доклад конференции был от Stack Overflow. Их 150 сайтов крутятся на 8 серверах. У них очень короткий цикл разработки фичь: обычно они укладываются в несколько часов. Как они этого добились:</p>
<ul class="simple">
<li>Full stack-разработчики. Все кто разрабатывает Stack Overflow умеет делать и фронт и бэк;</li>
<li>Отлаженная система раскатывания фичь в прод + использование собственного коммьюнити для тестирования.</li>
</ul>
<p>Так же у них очень интересный подход к кодированию. Поскольку они пишут на .Net, то они не могут не думать о сборщике мусора. Паттерны проектирования - это круто, но скорость важнее. И если какой-то паттерн, хоть сколько удобным он не был для программиста, генерирует слишком много связных объектов, ему не место в коде.</p>
<p>Такой подход требует высококлассных специалистов. А они стоят дорого. Скорее всего, экономически выгоднее было бы не тратить огромные деньги на разработку, а "залить железом". Но похоже такой подход добавляет проекту некоторую изюминку, которая привлекает к сервису таких же гуру, как и те что работают над ним.</p>
</div>
<div class="section" id="docker-badoo">
<h2>Docker в работе: взгляд на его использование в Badoo через год</h2>
<p>До сих пор думаете, что Docker - это панацея? Нет, это всего лишь полезный инструмент со своими багами, сложностями эксплуатации и граблями (куда же без них?). Огромное спасибо Антону, что рассказал о своем опыте эксплуатации Docker в продакшене. А то статьи, которые можно найти, по большей части описывают лишь эксперименты на девелоперских машинках без нагрузки.</p>
<p>Я более чем уверен, что описанные Антоном сложности, которые возникли у них, возникнут в каждом втором проекте на Docker. Сеть, сторадж, мониторинг - есть во всех проектах.</p>
</div>
<div class="section" id="highload">
<h2>Учебный план для highload гуру</h2>
<p>Этого доклада не должно было быть на Highload++. Он ну ни как не вписывался в формат конференции. Тем не менее этот доклад попал в тройку самых интересных докладов. Не смотря на то, что по большей части он состоит из ключевых слов: тем о которых в той или иной степени должен знать любой программист, который хочет расти и развиваться. Слайды доклада доступны <a class="reference external" href="http://shodan.ru/ppt/pulpfiction2.pptx">здесь</a>.</p>
<p>Возможно, единственная причина, почему я добавил этот доклад в тройку, - это то, что идеи и ощущения профессии Андрея Аксенова полностью совпадают с моими. Я так же как он считаю, что программисту нельзя закрываться в своей области, ему просто необходимо знание во многих областях CS. Возможно на уровне "прочитал статью, попробовал написать пару строк".</p>
<p>По сути этот доклад более жесткая и объемная версия моего поста <a class="reference external" href="https://www.alexkorablev.ru/2015/10/26/self-taught-programmers-lack/">о слабых сторонах программистов-самоучек</a>.</p>
</div>
CPython Internals Walk-Through2015-11-02T18:00:00+05:002015-11-02T18:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-11-02:/2015/11/02/cpython-internals-walk-through/<p>Ресурсов по тому как работает стандартная реализация питона, CPython, внутри мало. Пробиваться самому сквозь не самый очевидный код на C - задача для питониста очень сложная. Чтение кода на C требует определенной практики. Серия лекций, прочитанная Филипом Гио, хорошая отправная точка.</p>
<p>Ресурсов по тому как работает стандартная реализация питона, CPython, внутри мало. Пробиваться самому сквозь не самый очевидный код на C - задача для питониста очень сложная. Чтение кода на C требует определенной практики. Серия лекций, прочитанная Филипом Гио, хорошая отправная точка.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/videoseries?list=PLwyG5wA5gIzgTFj5KgJJ15lxq5Cv6lo_0" frameborder="0" allowfullscreen></iframe><p>Филип подробно рассказывает о внутренних структурах питона, проводя студентов от байткода к структурам написанным на C. Темы охваченные курсом:</p>
<ol class="arabic simple">
<li>Байткод и главный цикл интерпретатора</li>
<li>Фреймы, вызовы функций, области видимости</li>
<li>PyObject</li>
<li>Типы данных в питоне</li>
<li>Объект с кодом, объект функции</li>
<li>Итераторы</li>
<li>Классы и объекты</li>
<li>Генераторы</li>
</ol>
<p>Рассказ ведется с большим количеством примеров. Каждое погружение внутрь питона начинается с конкретного кусочка кода на питоне. Потом разбирается получившийся байткод. Дальше уже команда за командой, разбирается реализация команд байткода на C.</p>
<p>Хотелось бы что бы таких курсов было больше. Я бы даже хотел хороший курс на ресурсе вроде Coursera с заданиями. Может такой курс уже есть?</p>
Самые слабые стороны программистов-самоучек2015-10-26T10:00:00+05:002015-10-26T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-10-26:/2015/10/26/self-taught-programmers-lack/<p>Меня огорчает реакция людей, когда на вопрос "как мне стать хорошим программистом?" советуешь прочитать книгу по алгоритмам. Хорошо если каждый десятый спрашивает какую я посоветую. Остальные девять говорят, что им это не нужно. А ведь алгоритмы - это лишь одна из многих проблем у программистов самоучек.</p>
<p>Меня огорчает реакция людей, когда на вопрос "как мне стать хорошим программистом?" советуешь прочитать книгу по алгоритмам. Хорошо если каждый десятый спрашивает какую я посоветую. Остальные девять говорят, что им это не нужно. А ведь алгоритмы - это лишь одна из многих проблем у программистов самоучек.</p>
<p>Я это очень хорошо знаю. Я сам из самоучек. У меня план по закрыванию пробелов в знаниях расписан года на два вперед. Так что я провожу на Coursera часа по 3-4 в неделю только что бы добрать базу. А ведь надо еще и новые технологии изучать, и работать с хорошей эффективностью. И я считаю, что мне еще повезло: моих знаний в математике пока хватает.</p>
<p>Не хочу сказать, что самоучки работают хуже чем те, кто учился в университетах на компьютерных специальностях. Нет. Просто базовые знания сильно ограничивают карьерный рост. Пока ты Junior, ты этого не замечаешь. Но чем выше забираешься, чем за более сложные задачи берешься, тем очевиднее, что простого опыта не достаточно. Не хватает базовых знаний.</p>
<p>Тем более, что сейчас идет этап взросления отрасли. Количество грамотных специалистов становится больше, растет конкуренция. При этом в индустрии достаточно высокие и стабильные зарплаты, а это привлекает всяких левых людей, которых нужно как-то отсеивать. Вот и приходится компаниям менять подход к собеседованиям. А самый дешевый и быстрый способ проверить проф. пригодность программиста - спросить его базовые вещи: Big-O, алгоритмы поиска, деревья.</p>
<ol class="arabic simple">
<li>Алгоритмы и структуры данных</li>
</ol>
<p>Основные алгоритмы и структуры данных надо знать. Неплохо бы оперировать понятием "Big-O" и вообще уметь оценивать сложность алгоритмов. Как минимум это пригодится на собеседовании. Точнее, незнание алгоритмов - верный способ завалить собеседование. Да и в работе пригодится.</p>
<p>Хорошо, что эти знания получить не составит большого труда. Любите книги: на рынке полно книг по алгоритмам на любой вкус. Мне нравятся книги Кормэна и Лафоре. На Coursera настоятельно рекомендую курсы Принстона по алгоритмам <a class="reference external" href="https://www.coursera.org/course/algs4partI">https://www.coursera.org/course/algs4partI</a> и <a class="reference external" href="https://www.coursera.org/course/algs4partII">https://www.coursera.org/course/algs4partII</a> .</p>
<ol class="arabic simple" start="2">
<li>Языки программирования</li>
</ol>
<p>Я не говорю о знании большого количества языков. В общем и целом выучить синтаксис любого языка для программиста не составит особого труда. Я говорю о лексерах, парсерах, интерпретаторах, компиляторах и прочем. Во первых, рано или поздно придется лезть в кишки своего основного языка и смотреть, как он там устроен. Во вторых, эти знания могут пригодится в работе в самом неожиданном месте. На последнем PyCon Кирилл Борисов из Яндекс.Директ рассказывал, как он делает рефакторинг 11 тысяч тестов с помощью синтаксических деревьев.</p>
<p>Толковых ресурсов или книг по данной тематике я не знаю. Разве что могу порекомендовать посмотреть <a class="reference external" href="https://compscicenter.ru/courses/compilers/2013-autumn/">https://compscicenter.ru/courses/compilers/2013-autumn/</a> . А потом уже искать статьи и ресурсы с ответами на конкретные вопросы.</p>
<ol class="arabic simple" start="3">
<li>Парадигмы программирования</li>
</ol>
<p>Типичный выпускник компьютерной специальности уже успел по программировать на разных языках с разными парадигмами. Он уже в курсе функционального и объектного стиля программирования. Некоторые из них пытались использовать логическое программирование. Это безусловно расширяет кругозор и набор доступных способов для решения задач, которые встретятся в дальнейшей карьере.</p>
<p>Самый лучший способ наверстать этот блок - домашние проекты. Если программируете на Python, попробуйте сделать какой-нибудь проект на Haskell (ну или просто какие-нибудь задачки на нем порешайте). Я так неплохо улучшил свой код.</p>
<ol class="arabic simple" start="4">
<li>Архитектура и операционные системы</li>
</ol>
<p>Неплохо бы программисту знать как работает компьютер. Ну хотя бы в общих чертах. Мало ли придется писать расширение для питона на C++. Тогда знания о кэше процессора и как устроена память помогут сделать осознанный выбор между вектором и списком, на пример.</p>
<p>Эти же соображения относятся и к операционным системам, POSIX и другим системным "штукам". Так что добавляем Таненбаума в список книг, которые надо прочитать в ближайшее время.</p>
<p>Из моего опыта и общения с коллегами, эти четыре пункта самые слабые места самоучек. А какие пробелы в знаниях мешают в работе вам?</p>
<p>P.S. Пока готовил пост, обнаружил, что Computer Science Center переносит курсы на <a class="reference external" href="https://stepic.org/users/736914">Stepic</a>. Это круто. Надо будет их обязательно оценить.</p>
Kotlin мой следующий JVM-язык2015-10-19T10:00:00+05:002015-10-19T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-10-19:/2015/10/19/kotlin/<p>Утром, в дополнение к физическим упражнениям, я примерно час программирую. Для этого отлично подходят не очень сложные проекты, которые делаются в кайф. Для меня, это маленькие игры и приложения для Android. Первый подобный проект я написал на Java. Второй стартовал так же на Java. Но я решил его портировать на Kotlin.</p>
<p>Утром, в дополнение к физическим упражнениям, я примерно час программирую. Для этого отлично подходят не очень сложные проекты, которые делаются в кайф. Для меня, это маленькие игры и приложения для Android. Первый подобный проект я написал на Java. Второй стартовал так же на Java. Но я решил его портировать на Kotlin.</p>
<p>Основная проблема Java - ее многословность. Проект очень быстро перестал "влазить" в голову, особенно по утрам. Обычно это означает, что пришло время рефакторинга и код придется дробить, делать его похожим на "лютый энтерпрайз". Тоска. А так как проект делается для удовольствия, то я решил попробовать что-нибудь новенькое и поменять язык разработки.</p>
<p>Что требовалось от языка:</p>
<ol class="arabic simple">
<li>Полная совместимость с JVM</li>
<li>Возможность запускаться на Android</li>
<li>Компактный синтаксис</li>
<li>Простой перенос кода</li>
</ol>
<p>Кандидатов было два: Scala и Kotlin.</p>
<p>Преимущество Scala - коммьюнити. Оно большое. Так же есть много ресурсов где можно изучить язык, включая курсы на Coursera. Но вот синтаксис подкачал. У меня сложилось впечатление, что Scala пытается доказать, что я слишком слабый программист, что бы программировать на ней.</p>
<p>Не сомневаюсь, что это лишь первое впечатление и если потратить на нее чуть больше времени, все будет совсем по другому. Но для утреннего проекта, пока мозг только просыпается, Scala не лучший выбор.</p>
<p>С другой стороны у Kotlin скромное коммьюнити. И практически отсутствуют ресурсы для изучения языка. Есть, правда, <a class="reference external" href="https://github.com/jetbrains/workshop-jb">Коэны</a> (они великолепны!). Понравился синтаксис и идеи заложенные в языке. Есть инструмент для автоматической конвертации из Java в Kotlin. Сконвертировал свой проект на libGDX без проблем. Хотя и с <a class="reference external" href="https://twitter.com/avkorablev/status/654174498741207040">приключениями</a>.</p>
<p>Писать на Kotlin получается быстрее и проще по сравнению с Java. Синтаксис намного более емкий. И по ощущениям, язык больше приспособлен к экспериментам в коде.</p>
<p>3 из 4 требований Kotlin выполняет. Не пробовал собираться под Android. Даже если тут меня ждем облом, текущий проект я все-равно закончу на Kotlin. Я снова начал получать удовольствие от проекта.</p>
Паттерн Singleton2015-10-12T10:00:00+05:002015-10-12T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-10-12:/2015/10/12/singleton/<p>Singleton не так часто встречается в коде на питоне. Есть большая вероятность, что если потребовался синглтон, то в проекте есть проблемы с архитектурой. В первую очередь это касается небольших и средних проектов. Да и в больших проектах синглтон в чистом виде встретишь не часто.</p>
<p>Singleton не так часто встречается в коде на питоне. Есть большая вероятность, что если потребовался синглтон, то в проекте есть проблемы с архитектурой. В первую очередь это касается небольших и средних проектов. Да и в больших проектах синглтон в чистом виде встретишь не часто.</p>
<p>Когда я готовился к этой статье, я планировал написать статью о вариантах реализации паттерна. Но когда я начал исследовать, первым делом я наткнулся на следующий код в Википедии:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Singleton</span><span class="p">:</span>
<span class="k">def</span> <span class="fm">__new__</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
<span class="k">if</span> <span class="ow">not</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="s1">'instance'</span><span class="p">):</span>
<span class="bp">cls</span><span class="o">.</span><span class="n">instance</span> <span class="o">=</span> <span class="nb">super</span><span class="p">(</span><span class="n">Singleton</span><span class="p">,</span> <span class="bp">cls</span><span class="p">)</span><span class="o">.</span><span class="fm">__new__</span><span class="p">(</span><span class="bp">cls</span><span class="p">)</span>
<span class="k">return</span> <span class="bp">cls</span><span class="o">.</span><span class="n">instance</span>
</pre></div>
<pre class="literal-block">
>>> a = Singleton()
>>> b = Singleton()
>>> a is b
True
</pre>
<p>Код, казалось бы, рабочий. Но это не так. Во первых, если нужны разные синглтоны, то придется наследоваться от этого класса. Это вполне нормальное требование для проекта с более или менее сложной логикой. И по моему опыту, если в проекте завелся синглтон, то жди целый выводок синглтонов-наследников. Так вот наследование сломано начисто.</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">NewSingleton</span><span class="p">(</span><span class="n">Singleton</span><span class="p">):</span>
<span class="k">pass</span>
</pre></div>
<pre class="literal-block">
>>> c = NewSingleton()
>>> c is a
True
>>> type(c)
__main__.Singleton
</pre>
<p>Если повезет и будет создан экземпляр в базовом классе, то просто мы получим синглтон базового класса. А если первым будет создан экземпляр наследника... То ничего мы не получим. В новой сессии восстановим иерархию:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Singleton</span><span class="p">:</span>
<span class="k">def</span> <span class="fm">__new__</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
<span class="k">if</span> <span class="ow">not</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="s1">'instance'</span><span class="p">):</span>
<span class="bp">cls</span><span class="o">.</span><span class="n">instance</span> <span class="o">=</span> <span class="nb">super</span><span class="p">(</span><span class="n">Singleton</span><span class="p">,</span> <span class="bp">cls</span><span class="p">)</span><span class="o">.</span><span class="fm">__new__</span><span class="p">(</span><span class="bp">cls</span><span class="p">)</span>
<span class="k">return</span> <span class="bp">cls</span><span class="o">.</span><span class="n">instance</span>
<span class="k">class</span> <span class="nc">NewSingleton</span><span class="p">(</span><span class="n">Singleton</span><span class="p">):</span>
<span class="k">pass</span>
</pre></div>
<p>И попробуем создать экземпляр наследника:</p>
<pre class="literal-block">
>>> с = NewSingleton()
>>> type(c)
NameError: name 'c' is not defined
</pre>
<p>Впрочем, починить наследование достаточно просто:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Singleton</span><span class="p">:</span>
<span class="k">def</span> <span class="fm">__new__</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
<span class="k">if</span> <span class="ow">not</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="s1">'instance'</span><span class="p">)</span> <span class="ow">or</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="bp">cls</span><span class="o">.</span><span class="n">instance</span><span class="p">,</span> <span class="bp">cls</span><span class="p">):</span>
<span class="bp">cls</span><span class="o">.</span><span class="n">instance</span> <span class="o">=</span> <span class="nb">super</span><span class="p">(</span><span class="n">Singleton</span><span class="p">,</span> <span class="bp">cls</span><span class="p">)</span><span class="o">.</span><span class="fm">__new__</span><span class="p">(</span><span class="bp">cls</span><span class="p">)</span>
<span class="k">return</span> <span class="bp">cls</span><span class="o">.</span><span class="n">instance</span>
<span class="k">class</span> <span class="nc">NewSingleton</span><span class="p">(</span><span class="n">Singleton</span><span class="p">):</span>
<span class="k">pass</span>
</pre></div>
<pre class="literal-block">
>>> a = Singleton()
>>> b = Singleton()
>>> a is b
True
>>> c = NewSingleton()
>>> d = NewSingleton()
>>> c is d
True
>>> c is a
False
>>> type(c)
__main__.NewSingleton
>>> type(a)
__main__.Singleton
</pre>
<p>Все прекрасно работает. Выглядит более или менее неплохо. Более того, этот класс может сделать любой класс синглтоном. В питоне же есть множественное наследование. И это круто, значит можно сделать синглтоном любой класс, подмешав ему в предков синглтон.</p>
<p>Хотя можно обойтись и без множественного наследования. Тем более, что не всегда это полезно для кода. Небольшие модификации и наш синглтон превращается в метакласс (я уже писал о своей нелюбви к метаклассам, но иногда метакласс - то что нужно).</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Singleton</span><span class="p">(</span><span class="nb">type</span><span class="p">):</span>
<span class="n">_instances</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">def</span> <span class="fm">__call__</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="k">if</span> <span class="bp">cls</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_instances</span><span class="p">:</span>
<span class="bp">cls</span><span class="o">.</span><span class="n">_instances</span><span class="p">[</span><span class="bp">cls</span><span class="p">]</span> <span class="o">=</span> <span class="nb">super</span><span class="p">(</span><span class="n">Singleton</span><span class="p">,</span> <span class="bp">cls</span><span class="p">)</span><span class="o">.</span><span class="fm">__call__</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="k">return</span> <span class="bp">cls</span><span class="o">.</span><span class="n">_instances</span><span class="p">[</span><span class="bp">cls</span><span class="p">]</span>
<span class="k">class</span> <span class="nc">IAmSingleton</span><span class="p">(</span><span class="n">metaclass</span><span class="o">=</span><span class="n">Singleton</span><span class="p">):</span>
<span class="k">pass</span>
</pre></div>
<pre class="literal-block">
>>> a = IAmSingleton()
>>> b = IAmSingleton()
>>> a is b
True
>>> type(a)
__main__.IAmSingleton
>>> Singleton._instances
{__main__.IAmSingleton: <__main__.IAmSingleton at 0x1053196a0>}
</pre>
<p>Пожалуй не рассмотренным остался только вариант с декоратором. Такой вариант подойдет, когда надо сделать синглтоном какой-то отдельный класс из иерархии. Экзотика, конечно, но для полноты картины нужно рассмотреть и его:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">singleton</span><span class="p">(</span><span class="n">class_</span><span class="p">):</span>
<span class="n">instances</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">def</span> <span class="nf">getinstance</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="k">if</span> <span class="n">class_</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">instances</span><span class="p">:</span>
<span class="n">instances</span><span class="p">[</span><span class="n">class_</span><span class="p">]</span> <span class="o">=</span> <span class="n">class_</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="k">return</span> <span class="n">instances</span><span class="p">[</span><span class="n">class_</span><span class="p">]</span>
<span class="k">return</span> <span class="n">getinstance</span>
<span class="nd">@singleton</span>
<span class="k">class</span> <span class="nc">MyClass</span><span class="p">:</span>
<span class="k">pass</span>
</pre></div>
<pre class="literal-block">
>>> a = MyClass()
>>> b = MyClass()
>>> a is b
True
</pre>
<p>Пользуйтесь любым из приведенных рабочих вариантов в зависимости от проекта. Буду рад увидеть в комментариях ваши реализации этого паттерна.</p>
Тренажер иностранных слов2015-10-05T10:00:00+05:002015-10-05T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-10-05:/2015/10/05/wordteacher/<p>Наконец могу поделиться с вами ссылкой на мое приложение в Google Play: <a class="reference external" href="https://play.google.com/store/apps/details?id=com.wormholejump.wordteacher">Тренажер иностранных слов</a>. И домашний проект выросший из недоумения по поводу отсутствия в LinguaLeo языков кроме английского.</p>
<p>Наконец могу поделиться с вами ссылкой на мое приложение в Google Play: <a class="reference external" href="https://play.google.com/store/apps/details?id=com.wormholejump.wordteacher">Тренажер иностранных слов</a>. И домашний проект выросший из недоумения по поводу отсутствия в LinguaLeo языков кроме английского.</p>
<p>Тренажер позволяет добавить добавить слова на одном из 6 языков в набор, а потом учить их делая одно из двух упражнений: собирать слово из букв или выбирать вариант перевода. Слова запоминаются достаточно эффективно.</p>
<p>Пара слов о технической стороне. Приложение написано на Java. Сделано это специально:</p>
<ol class="arabic simple">
<li>Изучать разработку под определенную платформу лучше используя стандартное SDK этой платформы;</li>
<li>Давно хотелось попробовать поработать с Java еще раз. В прошлый раз у меня не сложились с ней отношения...</li>
</ol>
<p>Java изумительна. Немного болтлива, но это не страшно , если есть добротная IDE. Хорошо, что сейчас не нужно использовать Eclipse. Android Studio - великолепный инструмент.</p>
<p>SDK достаточно логична. Есть в ней, конечно свои заморочки, связанные с платформой. Мне отлично помогли видео от <a class="reference external" href="http://www.youtube.com/user/slidenerd">slidenerd</a>. Самая сложная и трудоемкая часть - это подгонка UI по разные разрешения. Закладывайте времени на эту часть побольше. А потом еще и на два умножьте. Основную часть тренажера я написал часов за 15. Еще 30 потратил на подгонку к разным размерам экранов. И не могу сказать, что получилось идеально.</p>
<p>В общем, пользуйтесь. Если есть вопросы или предложения по улучшению программы, пишите в комментариях.</p>
Неплохой вводный видео-курс по Python2015-09-28T10:00:00+05:002015-09-28T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-09-28:/2015/09/28/python-video-course/<p>Люблю смотреть разные видео-курсы по используемым языкам и технологиям. Причем не только по продвинутым темам, но и по базовым. Еще не было такого курса, в котором не нашлось бы что-нибудь интересное и полезное для меня. О последнем просмотренном мной курсе от Курячего Георгия я хочу немного рассказать. Этот курс - отличное базовое введение в Python2.</p>
<p>Люблю смотреть разные видео-курсы по используемым языкам и технологиям. Причем не только по продвинутым темам, но и по базовым. Еще не было такого курса, в котором не нашлось бы что-нибудь интересное и полезное для меня. О последнем просмотренном мной курсе от Курячего Георгия я хочу немного рассказать. Этот курс - отличное базовое введение в Python2.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/videoseries?list=PLPErILqzuTQr7QJ4rAFJVvzdTEFFPjaGz" frameborder="0" allowfullscreen></iframe><p>Возможно если вы изучали Python в университете, то этот курс покажется вам слабоватым. Но мне, как человеку, который изучал язык и его библиотеку разрознено, по книгам, статьям и отдельным видео, такой курс показался вполне достойным и полностью он оправдал время, потраченное на него.</p>
<p>Во первых, это все-таки университетский курс. Пусть и спецкурс. И как в хорошем курсе, материал подается более или менее системно. Глубина обзора базы языка хорошая. Жаль, что сложные темы, типа генераторов и метаклассов, затронуты вскользь. Но как говорит сам лектор, он не программист и эти в этих темах он сам разбирается не достаточно хорошо.</p>
<p>Как раз то, что лектор не программист - вторая причина посмотреть курс. Судя по его страничке на сайте <a class="reference external" href="https://uneex.ru/FrBrGeorge">https://uneex.ru/FrBrGeorge</a>, он системный администратор, работает в ALTLinux, а еще он решает олимпиадные задачи по программированию со школьниками. Это накладывает свой отпечаток на подачу материала (немного сбивчиво, но просто и понятно) и на его подход к задачам. Георгий любит решать олимпиадные задачи. Это видно по домашним заданиям к курсу.</p>
<p>Рекомендую потратить время и посмотреть этот курс. Несмотря на его недостатки, это отличный вводный курс.</p>
<p>А какие курсы по питону вы смотрели в последнее время и можете порекомендовать?</p>
Итоги конференции PyConRu 20152015-09-21T10:00:00+05:002015-09-21T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-09-21:/2015/09/21/pyconru-2015/<p>В субботу закончилась конференция PyConRu 2015 в Екатеринбурге. Немного остыв, можно подвести некоторые итоги. Конференция получилась хорошей. Как говорит один мой коллега, если на двухдневной конференции было два интересных доклада, то конференция удалась. На пайконе интересных и полезных докладов было больше. Я выделил 4 таких доклада.</p>
<p>В субботу закончилась конференция PyConRu 2015 в Екатеринбурге. Немного остыв, можно подвести некоторые итоги. Конференция получилась хорошей. Как говорит один мой коллега, если на двухдневной конференции было два интересных доклада, то конференция удалась. На пайконе интересных и полезных докладов было больше. Я выделил 4 таких доклада.</p>
<p>Начало положил Гаель Вароко. Если про <a class="reference external" href="http://scikit-learn.org/stable/">scikit</a> было просто интересно послушать, в повседневной работы мы машинным обучением не занимаемся, то рассказ о второй библиотеке <a class="reference external" href="https://pythonhosted.org/joblib/">joblib</a> заставил нас провести порядочное время на ее сайте. Сама библиотека позволяет легко организовывать параллельные вычисления. И нам был очень интересен ответ на вопрос сможем ли мы ее использовать в своем проекте. Не то что бы нам очень надо организовывать параллельные вычисления в своем проекте. Но если найдется библиотека, которая позволит сделать это быстро не превратив код в кашу, то я бы с удовольствием применил ее.</p>
<p>Рассказ об <a class="reference external" href="http://aiohttp.readthedocs.org/en/stable/">Aiohttp</a> от Андрея Светлова разбудил во мне исследователя. Обязательно надо будет сделать какой-нибудь небольшой проект с этой библиотекой.</p>
<p>Наш коллега Валентин Синицын рассказывал об управлении памятью в Python. Было ли это интересно кому-нибудь кроме меня, я не знаю. Я люблю рассказы о том как все устроено внутри языка, его библиотек и т.п. Не всегда хватает времени раскапывать самостоятельно, но не могу отказать себе в удовольствии посмотреть и послушать, что раскопали другие.</p>
<p>Александр Козволский и Алексей Малашкевич спасли второй день конференции. Их рассказ об использовании <a class="reference external" href="http://ponyorm.com/">PonyORM</a> в реальном кейсе показал, что у них получился отличный продукт. В прошлом году их доклад мне показался в разы скучнее. Молодцы ребята! Вот только надо бы разбить core.py на куски (4500 строк - слишком много).</p>
<p>Не все доклады одинаково полезны. Моя премия "худший доклад Pycon 2015" уходит Григорию Петрову за его "50 оттенков кэширования": вода и маркетинг. Перед началом конференции с энтузиазмом ждали что расскажет Александр Швец о Celery. Но оказалось, что у них один сервер, а не кластер. Так что они не наступали на те грабли, разобраться с которыми хотели мы. Единственными, кто наступал на те же грабли, были ребята так же ребята из Яндекса, но из другого проекта.</p>
<p>В общем и целом конференция получилась лучше прошлогодней. Спасибо девчонкам из IT-People за как всегда отличную организацию и выбор места проведения (Иволга мой идеал. Тут дают нормальные обеды, а не "еду для конференций"). Спасибо JetBrains за отличный кофе. Увидимся в следующем году на PyCon 2016.</p>
Почему не я люблю декораторы и метаклассы2015-09-14T10:00:00+05:002015-09-14T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-09-14:/2015/09/14/why-i-hate-metaclasses/<p>Я не люблю декораторы и метаклассы. Почти любое использование декоратора и любое использование метакласса ухудшает читаемость кода. Их трудно использовать без усложнения кода. Они приводят к трудновыявляемым багам. Так что их использование должно быть обосновано. И их не нужно использовать там, где можно легко и безболезненно обойтись без них.</p>
<p>Я не люблю декораторы и метаклассы. Почти любое использование декоратора и любое использование метакласса ухудшает читаемость кода. Их трудно использовать без усложнения кода. Они приводят к трудновыявляемым багам. Так что их использование должно быть обосновано. И их не нужно использовать там, где можно легко и безболезненно обойтись без них.</p>
<p>Если сравнить использование остальных конструкций языка с использованием рогатки, то использование декоратора - это стрельба из пушки, а метаклассы - из системы залпового огня Град. Последствия их неправильного использования влияют на кодовую базу примерно также. Декоратор разламывает несколько функций и методов, метакласс разрывает в клочья несколько классов.</p>
<p>Так как их использовать правильно? И какие основные ошибки? Метаклассы - очень тонкая материя. Для них я не возьмусь давать общие советы. Разве что только один: прежде чем использовать метаклассы, попробуйте решить проблему без них; если не получилось, попробуйте еще раз; и только после этого используйте метаклассы. Ну и перед тем как приступить, сообщите об этом решении остальным членам команды: "Ребята, для этой задачи я вынужден использовать метаклассы."</p>
<p>С декораторами проще. Я стараюсь следовать двум правилам:</p>
<ol class="arabic simple">
<li><strong>Не меняйте интерфейс функции.</strong> Будьте людьми, не добавляйте и не убавляйте количество, не меняйте название и порядок принимаемых функцией параметров. Если так нужно сделать, то декоратор - плохая идея. Для обычной функции лучше использовать functools.partial, для метода - написать в классе еще один метод с нужным интерфейсом.</li>
<li><strong>Не превращайте функцию в класс или класс в функцию.</strong> Кроме редких случаев делать это плохая идея. Если уж надо обернуть функцию в класс декоратором, то переопределите ему <cite>__call__</cite>.</li>
</ol>
<p>И напоследок. Старайтесь делать все проще. Читаемость кода - один из важнейших показателей его качества. Сложные конструкции хороши на собеседовании. В продакшене исповедуйте принцип разумного консерватизма.</p>
Области видимости переменных в Python2015-09-07T10:00:00+05:002015-09-07T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-09-07:/2015/09/07/global-and-local/<p>Часто на собеседованиях задают такие вопросы по Python, которые ставят в тупик даже опытных разработчиков. Вопросы не сложные, просто в повседневной деятельности либо не пользуешься тем о чем спрашивают, либо не обращаешь внимание, настолько это уже вошло в привычку.</p>
<p>Часто на собеседованиях задают такие вопросы по Python, которые ставят в тупик даже опытных разработчиков. Вопросы не сложные, просто в повседневной деятельности либо не пользуешься тем о чем спрашивают, либо не обращаешь внимание, настолько это уже вошло в привычку.</p>
<p>Так что этот цикл постов посвящен вопросам, которые часто задают на собеседованиях по питону, что бы глянуть перед собеседованием, если понадобится. И первый вопрос из этого списка: каковы правила для областей видимости локальных и глобальных переменных в Python.</p>
<p>Посмотрим на такой код:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">foo</span><span class="p">():</span>
<span class="nb">print</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
<span class="n">s</span> <span class="o">=</span> <span class="s2">"I'm global variable"</span>
<span class="n">foo</span><span class="p">()</span>
</pre></div>
<p>Функция ожидаемо напечатает "I'm global variable". В питоне переменные глобальные, если внутри нового неймспейса (внутри функции или класса) не создается локальная переменная с таким же именем. Код ниже напечатает "I'm local variable":</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">foo</span><span class="p">():</span>
<span class="n">s</span> <span class="o">=</span> <span class="s2">"I'm local variable"</span>
<span class="nb">print</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
<span class="n">s</span> <span class="o">=</span> <span class="s2">"I'm global variable"</span>
<span class="n">foo</span><span class="p">()</span>
</pre></div>
<p>Глобальные переменные можно использовать внутри вложенного неймспейса, если только не использовать их в левой части оператора присваивания, либо объявлять их внутри неймспейса с ключевым словом global.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">foo2</span><span class="p">():</span>
<span class="k">global</span> <span class="n">s</span>
<span class="n">s</span> <span class="o">=</span> <span class="s1">'Reset global variable'</span>
<span class="n">foo2</span><span class="p">()</span> <span class="c1"># меняем глобальную переменную</span>
<span class="nb">print</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
</pre></div>
<p>Важно! Питон анализирует код функции не строчка за строчкой, а целиком. Так что следующий код свалится с ошибкой UnboundLocalError:</p>
<div class="highlight"><pre><span></span><span class="n">s</span> <span class="o">=</span> <span class="s2">"I'm global variable"</span>
<span class="k">def</span> <span class="nf">foo</span><span class="p">():</span>
<span class="nb">print</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
<span class="n">s</span> <span class="o">=</span> <span class="s1">'Reset global variable'</span>
<span class="n">foo</span><span class="p">()</span>
</pre></div>
<p>Вот как-то так. Какие вопросы по питону на собеседованиях задавали вам?</p>
Type Hinting в Python 3.52015-08-31T10:00:00+05:002015-08-31T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-08-31:/2015/08/31/type-hinting/<p>"Утиная" типизация конечно ускоряет написание программ, но только до определенных пределов. В больших проектах, когда количество кода достаточно большое, такая гибкость начинает давать сбои. С Python 3.0 можно делать типизацию для функций. Но только с 3.5 появится действительно мощный инструмент встроенный в язык.</p>
<p>"Утиная" типизация конечно ускоряет написание программ, но только до определенных пределов. В больших проектах, когда количество кода достаточно большое, такая гибкость начинает давать сбои. С Python 3.0 можно делать типизацию для функций. Но только с 3.5 появится действительно мощный инструмент встроенный в язык.</p>
<p>Конечно, можно делать описание параметров и возвращаемых значений в докстрингах. Вот только кто их пишет, а если пишет, то кто следит за их актуальностью? Программисты ленивы, если можно что-то не делать, они не будут этого делать. А если уж делать описание типов, то в интерфейсе функции. Так можно и для IDE подсказки получить, и инструментами статического анализа пользоваться (на пример, mypy). И следить за изменением интерфейса в разы проще. Большие парни делают это уже давно, в больших проектах без этого просто не обойтись.</p>
<p>Если посмотреть как можно указывать типы в функциях Python 3.0, то становится понятно, что гибкости синтаксиса не хватает для описания даже для достаточно часто встречаемых ситуаций. К примеру, совершенно не понятно как описать принимаемую в качестве параметра функцию. Сколько у нее должно быть параметров? Какое возвращаемое значение?</p>
<p>Конечно, можно пользоваться mypy. В нем синтаксической гибкости хватит, если не на все случаи, то на большую часть точно. Но это не часть языка, так что применимо только для кода, который не выйдет за пределы команды. Публичные библиотеки так описывать не стоит. Хорошо, что появился <a class="reference external" href="https://www.python.org/dev/peps/pep-0484/">PEP 484</a>. И он уже вот-вот станет частью питона.</p>
<p>Жду этих нововведений с нетерпением. Даже поставил релиз-кандидат, что бы опробовать нововведения в деле. Впечатления от предложенных нововведений отличное. Описания делать удобно и быстро. Читаемость кода не страдает, даже местами улучшается. Пора писать commit-hook.</p>
Кастомные коллекции2015-08-24T10:00:00+05:002015-08-24T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-08-24:/2015/08/24/magic-methods-getitem/<p>Хорошо написанный код на Python должен работать с последовательностями однообразно. При этом не важно встроенные это последовательности или нет. Тем более, что свою последовательность написать в питоне очень просто. Нужно всего лишь переопределить __len__, __getitem__ что бы работа с вашей последовательностью в большинстве случаев не отличалась от работы со встроенными последовательностями.</p>
<p>Хорошо написанный код на Python должен работать с последовательностями однообразно. При этом не важно встроенные это последовательности или нет. Тем более, что свою последовательность написать в питоне очень просто. Нужно всего лишь переопределить __len__, __getitem__ что бы работа с вашей последовательностью в большинстве случаев не отличалась от работы со встроенными последовательностями.</p>
<p>В качестве иллюстрации возьмем такой пример. У нас есть класс ShoppingBasket. В нем лежат какие-то товары. Пусть нам нужно пройтись по всем товарам, научиться получать товары по ключу и добавлять товары.</p>
<div class="section" id="section-2">
<h2>Первый вариант</h2>
<p>Положимся на встроенные коллекции. Для хранения элементов используем список.</p>
<div class="highlight"><pre><span></span><span class="c1"># -*- coding: utf-8 -*-</span>
<span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">namedtuple</span>
<span class="n">BasketItem</span> <span class="o">=</span> <span class="n">namedtuple</span><span class="p">(</span><span class="s1">'BasketItem'</span><span class="p">,</span> <span class="s1">'name, price, quantity'</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">ShoppingBasket</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">items</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
<span class="n">basket</span> <span class="o">=</span> <span class="n">ShoppingBasket</span><span class="p">()</span>
<span class="n">basket</span><span class="o">.</span><span class="n">items</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">BasketItem</span><span class="p">(</span><span class="s2">"Apple"</span><span class="p">,</span> <span class="mf">1.12</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span>
<span class="n">BasketItem</span><span class="p">(</span><span class="s2">"Orange"</span><span class="p">,</span> <span class="mf">1.3</span><span class="p">,</span> <span class="mi">4</span><span class="p">),</span>
<span class="p">]</span>
<span class="c1"># печатаем содержимое</span>
<span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">basket</span><span class="o">.</span><span class="n">items</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
<span class="c1"># только яблоки</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Apples:"</span><span class="p">,</span> <span class="n">basket</span><span class="o">.</span><span class="n">items</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="c1"># всего элементов в корзине</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Items:"</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">basket</span><span class="o">.</span><span class="n">items</span><span class="p">))</span>
</pre></div>
<p>Код работает. Но у него большая проблема: класс ShoppingBasket открывает наружу то как он хранит товары в корзине. Этот код нас жестко привязывает к списку. Если по каким-то причинам мы захотим поменять модель хранения товаров в корзине (привязать к БД, на пример), то придется править код во множестве мест, где имеется работа с корзиной. Нам повезет, если код не является частью публичного API.</p>
</div>
<div class="section" id="section-3">
<h2>Второй вариант</h2>
<p>Попробуем спрятать зависимость от дикта внутри корзины.</p>
<div class="highlight"><pre><span></span><span class="c1"># -*- coding: utf-8 -*-</span>
<span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">namedtuple</span>
<span class="n">BasketItem</span> <span class="o">=</span> <span class="n">namedtuple</span><span class="p">(</span><span class="s1">'BasketItem'</span><span class="p">,</span> <span class="s1">'name, price, quantity'</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">ShoppingBasket</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">items</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">def</span> <span class="nf">append</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">items</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="k">def</span> <span class="fm">__getitem__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">items</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
<span class="k">def</span> <span class="fm">__len__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">items</span><span class="p">)</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
<span class="n">basket</span> <span class="o">=</span> <span class="n">ShoppingBasket</span><span class="p">()</span>
<span class="n">basket</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">BasketItem</span><span class="p">(</span><span class="s2">"Apple"</span><span class="p">,</span> <span class="mf">1.12</span><span class="p">,</span> <span class="mi">2</span><span class="p">))</span>
<span class="n">basket</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">BasketItem</span><span class="p">(</span><span class="s2">"Orange"</span><span class="p">,</span> <span class="mf">1.3</span><span class="p">,</span> <span class="mi">4</span><span class="p">))</span>
<span class="n">basket</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">BasketItem</span><span class="p">(</span><span class="s2">"Bananas"</span><span class="p">,</span> <span class="mf">0.8</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>
<span class="c1"># печатаем содержимое</span>
<span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">basket</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
<span class="c1"># только яблоки</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Apples:"</span><span class="p">,</span> <span class="n">basket</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="c1"># слайсы так же работают</span>
<span class="nb">print</span><span class="p">(</span><span class="n">basket</span><span class="p">[:</span><span class="mi">2</span><span class="p">])</span>
<span class="c1"># всего элементов в корзине</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Items:"</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">basket</span><span class="p">))</span>
</pre></div>
<p>Код внутри __main__ стал более опрятным. Зависимость от способа хранения элементов убрана в класс корзины. Переделать этот вариант корзины на хранение элементов в БД с сохранением интерфейса не составит проблем.</p>
<p>Не ленитесь. Если ваш класс по сути коллекция, прячьте то как он хранит свои элементы, но делайте привычные интерфейсы. Так вы облегчите жизнь себе и пользователям вашего кода.</p>
</div>
Raymond Hettinger - Beyond PEP 8. Впечатления от доклада2015-08-17T10:00:00+05:002015-08-17T10:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-08-17:/2015/08/17/beyond-pep8/<p>Один из самых интересных докладов с PyCon 2015. Реймонд рассказывает о том, что соответствие кода PEP8 не обеспечит ни его правильность, ни читаемоть, ни "питонячность". Рекомендую всем, кто хоть раз пытался улучшить свой код приведением его к PEP8, а не пытался его переосмыслить, или заворачивал пулл-реквесты из-за того, что код не по PEP8.</p>
<p>Один из самых интересных докладов с PyCon 2015. Реймонд рассказывает о том, что соответствие кода PEP8 не обеспечит ни его правильность, ни читаемоть, ни "питонячность". Рекомендую всем, кто хоть раз пытался улучшить свой код приведением его к PEP8, а не пытался его переосмыслить, или заворачивал пулл-реквесты из-за того, что код не по PEP8.</p>
<div class="youtube" align="left"><iframe width="560" height="315" src="https://www.youtube.com/embed/wf-BqAjZb8M" frameborder="0"></iframe></div>Celery: Pickle от которого невозможно отказаться2015-08-10T11:00:00+05:002015-08-10T11:00:00+05:00akorablevtag:www.alexkorablev.ru,2015-08-10:/2015/08/10/celery-pickle-ot-kotorogo-nevozmozhno-otkazatsya/<p>Если вы решили поменять сериализатор задач для Celery с Pickle на
какой-нибудь другой, ничего не выйдет. Вернее задачи действительно будут
сериализоваться и десериализоваться с помощью указанного сериализатора.
Но внутри Celery все равно используется Pickle и это никак не изменить.
Вернее не в Celery, а в библиотеке billiard, которая используется
мастер-процессом воркера для раздачи задач из очереди подпроцессам.</p>
<p>Если вы решили поменять сериализатор задач для Celery с Pickle на
какой-нибудь другой, ничего не выйдет. Вернее задачи действительно будут
сериализоваться и десериализоваться с помощью указанного сериализатора.
Но внутри Celery все равно используется Pickle и это никак не изменить.
Вернее не в Celery, а в библиотеке billiard, которая используется
мастер-процессом воркера для раздачи задач из очереди подпроцессам.</p>
<p>В нашем проекте мы столкнулись с тем, что мы иногда передаем в качестве
параметра задач Celery NoneType. Причем не значение None, а именно
NoneType.</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">celery</span> <span class="kn">import</span> <span class="n">Celery</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">Celery</span><span class="p">(</span><span class="s1">'app'</span><span class="p">,</span> <span class="n">broker</span><span class="o">=</span><span class="s1">'mongodb://localhost:27017/celery_dumps'</span><span class="p">)</span>
<span class="nd">@app</span><span class="o">.</span><span class="n">task</span>
<span class="k">def</span> <span class="nf">print_xy</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">x</span><span class="p">))</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
<span class="n">print_xy</span><span class="o">.</span><span class="n">delay</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="kc">None</span><span class="p">))</span>
</pre></div>
<p>Не знаю как в Python 3, но в Python 2.7 за подобные вольности
наказывают:</p>
<div class="highlight"><pre><span></span>><span class="w"> </span>python<span class="w"> </span>app.py
PicklingError:<span class="w"> </span>Can<span class="se">\'</span>t<span class="w"> </span>pickle<span class="w"> </span><<span class="nb">type</span><span class="w"> </span><span class="se">\'</span>NoneType<span class="se">\'</span>>
</pre></div>
<p>Я решил обойти несправедливость с NoneType за счет смены сериализатора
на JSON. Это делается достаточно легко. Научил его
сериализовать/десериализовать нужные нам типы данных (datetime и тому
подобное). Код начал исполняться, вот только задачи пропадали, а воркеры
"залипали". Оказалось, что проблема переехала в мастер процесс воркера.
Все так же:</p>
<div class="highlight"><pre><span></span><span class="n">Task</span> <span class="n">Handler</span> <span class="n">ERROR</span><span class="p">:</span> <span class="n">PicklingError</span><span class="p">(</span><span class="s2">"Can't pickle <type 'NoneType'>: attribute lookup __builtin__.NoneType failed"</span><span class="p">,)</span>
</pre></div>
<p>Подмена стандартного <a class="reference external" href="https://github.com/celery/celery/issues/2526">pickle на dill в
kombu</a>, предложенная
maximilianr, не поможет. Библиотека billiard выбирает между pypickle и
cPickle и ей плевать, что прописано в kombu.serialization.pickle.</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">celery</span> <span class="kn">import</span> <span class="n">Celery</span>
<span class="kn">import</span> <span class="nn">dill</span> <span class="k">as</span> <span class="nn">dill</span>
<span class="kn">from</span> <span class="nn">io</span> <span class="kn">import</span> <span class="n">BytesIO</span>
<span class="kn">import</span> <span class="nn">kombu</span>
<span class="k">def</span> <span class="nf">add_dill</span><span class="p">():</span>
<span class="n">registry</span> <span class="o">=</span> <span class="n">kombu</span><span class="o">.</span><span class="n">serialization</span><span class="o">.</span><span class="n">registry</span>
<span class="n">kombu</span><span class="o">.</span><span class="n">serialization</span><span class="o">.</span><span class="n">pickle</span> <span class="o">=</span> <span class="n">dill</span>
<span class="n">registry</span><span class="o">.</span><span class="n">unregister</span><span class="p">(</span><span class="s1">'pickle'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">pickle_loads</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">load</span><span class="o">=</span><span class="n">dill</span><span class="o">.</span><span class="n">load</span><span class="p">):</span>
<span class="k">return</span> <span class="n">load</span><span class="p">(</span><span class="n">BytesIO</span><span class="p">(</span><span class="n">s</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">pickle_dumps</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">dumper</span><span class="o">=</span><span class="n">dill</span><span class="o">.</span><span class="n">dumps</span><span class="p">):</span>
<span class="k">return</span> <span class="n">dumper</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">protocol</span><span class="o">=</span><span class="n">kombu</span><span class="o">.</span><span class="n">serialization</span><span class="o">.</span><span class="n">pickle_protocol</span><span class="p">)</span>
<span class="n">registry</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="s1">'pickle'</span><span class="p">,</span> <span class="n">pickle_dumps</span><span class="p">,</span> <span class="n">pickle_loads</span><span class="p">,</span>
<span class="n">content_type</span><span class="o">=</span><span class="s1">'application/x-python-serialize'</span><span class="p">,</span>
<span class="n">content_encoding</span><span class="o">=</span><span class="s1">'binary'</span><span class="p">)</span>
<span class="n">add_dill</span><span class="p">()</span> <span class="c1"># Celery плевать на это</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">Celery</span><span class="p">(</span><span class="s1">'app'</span><span class="p">,</span> <span class="n">broker</span><span class="o">=</span><span class="s1">'mongodb://localhost:27017/celery_dumps'</span><span class="p">)</span>
<span class="nd">@app</span><span class="o">.</span><span class="n">task</span>
<span class="k">def</span> <span class="nf">print_xy</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">x</span><span class="p">))</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
<span class="n">print_xy</span><span class="o">.</span><span class="n">delay</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="kc">None</span><span class="p">))</span>
</pre></div>
<p>Остается только метать лучи гнева, переписывать код так, что бы он был
pickle-friendly, либо грязными хаками менять поведение стандартного
pickle.</p>
<p>Если знаете элегантное решение этой проблемы, пишите в комментариях.</p>
Декораторы с параметрами по умолчанию2015-08-03T13:19:00+05:002015-08-03T13:19:00+05:00akorablevtag:www.alexkorablev.ru,2015-08-03:/2015/08/03/dekoratory-s-parametrami-po-umolchaniyu/<p>Декораторы в питоне - удобный инструмент. Если ими пользоваться с умом,
они значительно повышают читаемость кода и его повторное использование.
Но есть один момент, который меня немного напрягает: для декоратора с
параметрами по умолчанию нужно ставить пустые круглые скобки после его
вызова. Время от времени, это приводит к ошибкам. Особенно это
актуально, если декоратор является частью публичного API.</p>
<p>Декораторы в питоне - удобный инструмент. Если ими пользоваться с умом,
они значительно повышают читаемость кода и его повторное использование.
Но есть один момент, который меня немного напрягает: для декоратора с
параметрами по умолчанию нужно ставить пустые круглые скобки после его
вызова. Время от времени, это приводит к ошибкам. Особенно это
актуально, если декоратор является частью публичного API.</p>
<div class="highlight"><pre><span></span><span class="nd">@decorator_wo_params</span>
<span class="k">def</span> <span class="nf">f1</span> <span class="p">():</span>
<span class="k">pass</span>
<span class="nd">@decorator_w_defaults_params</span><span class="p">()</span> <span class="c1"># ну зачем здесь пустые скобки?</span>
<span class="k">def</span> <span class="nf">f2</span> <span class="p">():</span>
<span class="k">pass</span>
</pre></div>
<p>С этим можно смириться. В режиме жестких сроков и большого потока задач,
времени на поиск решения, написание и тестирование дополнительного кода
может просто не быть. Нормальное тестирование выявит проблему
моментально. Но иногда невозможно удержаться и не сделать свой код
немного красивее.</p>
<p>Предположим, что требуется декоратор, который увеличивает значение
функции в заданное количество раз. По умолчанию - в 10 раз. У меня
получился следующий код:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">wraps</span>
<span class="k">def</span> <span class="nf">multiplicator</span><span class="p">(</span><span class="n">method</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">rate</span><span class="o">=</span><span class="mi">10</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="n">method</span><span class="p">):</span>
<span class="nd">@wraps</span><span class="p">(</span><span class="n">method</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="k">return</span> <span class="n">rate</span><span class="o">*</span><span class="n">method</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="k">return</span> <span class="n">wrapper</span>
<span class="k">if</span> <span class="nb">callable</span><span class="p">(</span><span class="n">method</span><span class="p">):</span>
<span class="k">return</span> <span class="n">decorator</span><span class="p">(</span><span class="n">method</span><span class="p">)</span>
<span class="k">return</span> <span class="n">decorator</span>
</pre></div>
<p>В основе лежит идея предложенная Михаэлем Анджелетти на StackOverflow
(<a class="reference external" href="http://stackoverflow.com/questions/3888158/python-making-decorators-with-optional-arguments">Python - making decorators with optional
arguments</a>).
Я немного модифицировал его код под поставленную задачу. Но идея
сохранена. Если декоратор вызван без скобочек, то первым параметрам в
функцию multiplicator прилетит функция. Если же вызвать со скобочками
(не важно с параметрами или нет), то в if мы не попадаем. Возвращается
декоратор.</p>
<p><a class="reference external" href="https://github.com/ls1651/snakecharmer-python/blob/master/decorators/decorator.py">Код
рабочий</a>,
но не без проблем. Параметры в подобный декоратор нужно передавать по
имени. Что не всегда удобно. Да и непонятный method в подсказке IDE
будет глаза мозолить. С другой стороны, код практически не усложнился,
читаемость не снизилась.</p>
<p>Вот еще пара интересных вариантов реализации декораторов с параметрами,
на которые можно посмотреть. Возможно, для ваших проектов они окажутся
более подходящими:</p>
<ul class="simple">
<li><a class="reference external" href="http://stackoverflow.com/questions/17119154/python-decorator-optional-argument">http://stackoverflow.com/questions/17119154/python-decorator-optional-argument</a></li>
<li><a class="reference external" href="http://blogs.it.ox.ac.uk/inapickle/2012/01/05/python-decorators-with-optional-arguments/">http://blogs.it.ox.ac.uk/inapickle/2012/01/05/python-decorators-with-optional-arguments/</a></li>
</ul>