Расширяем кругозор

07 июня 2023
dhjg

В июньском дайджесте расскажем об автоматизации часто используемых действий и приёмах, которые можно почерпнуть из Rust. Также поделимся простой и эффективной библиотекой для интерактивных подсказок и узнаем, какое будущее готовится для Python на мобильных платформах. Приятного чтения!

Лайфхаки с Makefile

При разработке любого проекта на Python есть масса рутинных операций: тесты, запуск приложения в контейнере или прогон кода линтером. И не стоит плодить лишние сущности в виде shell-скриптов под каждую такую задачу. Можно использовать встроенный в IDE task runner или даже поставить какой-то внешний инструмент, но есть решение элегантнее и проще. В состав любого Linux-дистрибутива или macOS с древних времён входит такая штука, как Makefile. Её типичное применение — собирать код на C, но де-факто это крутое средство автоматизации любых задач.

Проще всего объяснить, как это работает, на примере кода. Идеальный пример был найден в блоге Антона Жиянова, разработчика на Python и Golang. Вот как может выглядеть простейший Makefile: 

coverage:  ## Run tests with coverage 
coverage erase 
coverage run --include=dadata/* -m pytest -ra 
coverage report -m 
 
deps:  ## Install dependencies 
pip install black coverage flake8 mypy pylint pytest tox 
 
lint:  ## Lint and static-check 
flake8 dadata 
pylint dadata 
mypy dadata 
 
push:  ## Push code with tags 
git push && git push --tags 
 
test:  ## Run tests 
pytest -ra 

Прогон несколькими линтерами и тестирование? Легко: 

$ make lint coverage 

Запушить с тегами в репу? Ещё проще: 

$ make push 

Из примера видно, что можно создавать цепочки действий, каждое из которых будет выполнено в отдельном подпроцессе (как в coverage или lint). Если надо выполнить связанную между собой цепочку действий, то объединение делается через два амперсанда && (как в секции push).

Внутри этого же Makefile можно легко определить зависимости между задачами. Тут, например, тесты не будут запущены, пока не выполнен линтинг: 

test: lint 
pytest -ra 

Есть даже возможность явного вызова задач прямо внутри Makefile: 

lint: 
flake8 dadata 
pylint dadata 
mypy dadata 
 
test: 
pytest -ra 
 
prepare: 
make lint 
make test 
 

Освоив эти нехитрые приёмы, можно переходить к более сложным трюкам. В этом поможет статья Makefile tricks for Python projects. Там вы найдёте полезные советы не только по составлению Makefile, но и по настройке оболочки операционной системы. Например, для BASH можно флагами отключить встроенные правила и добавлять дополнительную информацию в вывод, если операция завершилась неудачей: 

SHELL := bash 
.SHELLFLAGS := -eu -o pipefail -c 
MAKEFLAGS += --warn-undefined-variables 
MAKEFLAGS += --no-builtin-rules 

Резюмируем: освоить создание Makefile полезно любому Python-разработчику. Этот, странный на первый взгляд, инструмент умеет отлично упрощать жизнь. Ну а главное — скорее всего он уже есть на вашей рабочей машине и готов к работе без дополнительных танцев с бубном.

Пишем в стиле Rust

Всю жизнь писали на Python, а потом решили попробовать Rust? Скорее всего вы испытаете те же чувства, какие испытывает хулиган, оказавшись в английской школе 19го века. Величественно, строго и могут высечь розгами (вспомните, как наказывали Тома Сойера).

Строгость системы типов и другие особенности языка вначале доставляют много боли и страданий. Но если все требования Rust удовлетворены, это гарантирует, что код будет работать очень быстро, и не упадёт по неизвестной причине. Значит ли это, что надо бросить всё и срочно учить другой язык? Вряд ли! А вот привнести немного порядка и строгости в Python вы можете самостоятельно, взяв за основу те же концепции, которые применены в Rust.

Начать можно с малого — подсказок типа. Возьмём вот такую простейшую функцию: 

def find_item(records, check): 

Эта строка несёт в себе очень мало информации. Records — это список, словарь или вообще соединение с базой данных? А что за check? Это булево значение или какая-то другая функция? Если функция, что она вернёт? А если сбой, то получим ли мы исключение или None? Масса вопросов и ноль ответов. Теперь давайте перепишем эту функцию на аналогичную. А на вопросы сразу ответим, используя встроенный механизм подсказок: 

def find_item( 
  records: List[Item], 
  check: Callable[[Item], bool] 
) -> Optional[Item]: 

Стало понятно, что такое records и check. А главное, мы сразу видим, какого они должны быть типа. Написать такой код легко и не слишком долго. Зато потом в нём легко будет разобраться и найти потенциальные ошибки. Теперь, когда у нас есть удобное и внятное описание, пришла пора сделать интерфейсы более точными и защищёнными от неожиданностей. Вернуть несколько значений (или одно сложное значение) из функции можно несколькими способами. Проще всего вернуть кортеж или словарь, но оба этих способа на выходе дадут нечто, что придётся изучать. Вот пример кортежа: 

def find_person(...) -> Tuple[str, str, int]: 

Вернулись три каких-то значения. Придётся внимательно изучать функцию, чтобы выяснить их значение. Словарь тоже не решит ситуацию: 

def find_person(...) -> Dict[str, Any]: 
	... 
	return { 
    	"name": ..., 
    	"city": ..., 
    	"age": ... 
	} 

Тут ещё хуже. Мы не знаем ни количество, ни типы отдельных атрибутов. Если функцию изменить, то ключи в возвращаемом словаре могут быть переименованы или удалены. Выяснить потом тип каждого ключа можно будет только вручную, что долго и сложно.

Решением станет возвращать строго типизированный объект. Каждый параметр такого объекта должен иметь ассоциированный тип. Это автоматически подводит нас к решению о создании класса, а чтобы не усложнять жизнь, его можно создать при помощи dataclasses: 

@dataclass 
class City: 
	name: str 
	zip_code: int 
 
 
@dataclass 
class Person: 
	name: str 
	city: City 
	age: int 
 
 
def find_person(...) -> Person: 

Так гораздо информативнее. Мы сразу же получаем явное описание того, что возвращает функция. Автодополнение IDE начнёт работать эффективнее, показывая имена и типы атрибутов. При рефакторинге и изменении атрибутов средство проверки типов и IDE укажут на все места, которые нужно изменить. Даже запускать программу не потребуется. Хотите узнать больше, тогда рекомендуем заглянуть в статью Writing Python like it's Rust чешского программиста Якуба Беранека (Jakub Beránek).

CLI prompts без ncurses

Мы в Evrone любим удобные и легковесные инструменты. Сегодня кратко расскажем об одной простой и красивой библиотеке survey для создания подсказок. Её автор вдохновлялся одноимённой библиотекой из языка Go. С помощью survey можно создавать интерактивные подсказки с множеством дополнительных возможностей. При этом она самодостаточна и не требует дополнительных зависимостей, таких как ncurses. 

survey

Библиотека имеет хорошо структурированную документацию с кучей гифок, демонстрирующих для чего её можно применять. Используется принцип конструктора, где каждая фича представлена в виде виджета. Комбинируя их, можно создавать действительно удобные пользовательские интерфейсы для консоли. Там учтено множество моментов, основанных на опыте использования разных операционных систем. Так, например, пользователи привыкли, что при введении паролей, они видят количество символов, замаскированных звёздочками. В survey есть соответствующий виджет Conceal

value = survey.routines.conceal('Password: ') 
print(f'Answered {value}.') 

sdfg

Если стоит задача сделать удобную форму, то это легко делается одноимённым виджетом Form

form = { 
	'name': survey.widgets.Input(), 
	'price': survey.widgets.Count(), 
	'type': survey.widgets.Select(options = ('food', 'stationary', 'tobacco', 'literature')) 
} 
data = survey.routines.form('Item Data: ', form = form) 

fghd

Также легко сделать прогресс-бар, список с возможностью выбора одного или нескольких значений и прочие элементы, вроде ввода даты/времени. Так что если вы решили добавить консольному интерфейсу своего приложения удобства, то эта небольшая библиотека точно справится.

Python на мобильных

Проблема экосистем для приложений в том, что друг с другом они несовместимы и каждая из них предлагает собственные средства разработки. Раньше вообще предполагалось, что если вы пишете приложение под десктопные системы, оно не заработает на мобильных устройствах.

Время идёт, а легче не становится. Среди мобильных экосистем всё ещё жёсткая конкуренция, так что если вы пишете под Android на каком-нибудь Kotlin, то на iOS такое приложение простым способом не портировать. Частично ситуацию решает фреймворк Flutter для языка Dart. Он позволяет иметь единую кодовую базу, но при этом созданные приложения заработают и на десктопных системах, и на смартфонах из разных экосистем.

Проект BeeWare служит той же цели и даёт возможность разрабатывать приложения на Python, которые бы работали на разных мобильных устройствах и могли бы нативно задействовать возможности этих устройств. Эта идея возникла очень давно, но до недавнего времени проект выглядел как чьё-то хобби и поддерживался энтузиастами во главе с Расселом Кит-Маги (Russel Keith-Magee). Теперь же компания Anaconda увидела потенциал BeeWare и стала его финансировать, что позволило Расселу заниматься им full-time вместе с коллегой, Малькольмом Смитом (Malcolm Smith).

На Python Language Summit в этом году прозвучало интересное предложение. Рассел предложил включить Android и iOS в список платформ уровня Tier 3 от CPython в Python 3.13. Переводя с «питонячьего бюрократического», это означает, что как минимум один основной разработчик CPython возьмёт на себя обязательство поддерживать работу интерпретатора на этих платформах.

Точно такой же уровень поддержки уже присвоен платформам emscripten, WASI и FreeBSD. Основная проблема и препятствие на этом пути — запуск тестов на мобильных платформах в CI. Именно это препятствие Рассел устранил, создав Briefcase. Будучи одним из инструментов упаковки и разработки BeeWare, Briefcase может закрыть вопрос с тестированием.

Разумеется, впереди будет ещё масса специфических вопросов. Например, iOS полностью аутентична, а вот Android во многом схож с Linux. С первой ОС всё понятно и она может быть легко указана, как sys.platform == “ios”. А вот Android может быть либо указан как sys.platform == “android”, либо как sys.platform == “linux”.

Второй вариант, по словам Рассела, оптимален, поскольку большая часть кода будет работать на Android сразу, даже если возможность запуска на этой платформе не учитывается. Так что если предложение будет принято, возможно мы получим возможность писать приложения на Python, которые будут отлично работать на всех современных смартфонах.

Митапы

Онлайн

Python meetup

28 июня 2023

Рады сообщить, что у нас запланирован летний Python Meetup. Программа мероприятия формируется. Заявки на участие спикера принимаются до 10 июня. Видео с предыдущего митапа в студийном качестве 4K уже выложены на нашем YouTube-канале.

Теперь следить за митапами Evrone стало удобнее. В Telegram-канале Evrone meetups мы выкладываем анонсы с подробными описаниями докладов, а также студийные записи после мероприятий. А ещё, у нас можно выступить, мы поможем оформить вашу экспертизу в яркое выступление. Подписывайтесь и пишите @andrew_aquariuss, чтобы узнать подробности.

Регистрация

Вакансии

Удаленка / Офис

Evrone 

Мы рады новым Python-разработчикам. Удалённая работа с первого дня, помощь в подготовке выступлений на профессиональных конференциях, поощрение и оплата участия в Open-source проектах. Прозрачный способ увеличить грейд через обучение и проверку навыков под контролем ментора. Здесь есть понимание как организовать разработку комфортно и эффективно. Присоединяйтесь!

Подробнее

Подписаться
на Digest →
Важные новости и мероприятия без спама
Технологии которыми вы владеете и которые вам интересны
Ваш адрес электронной почты в безопасности - вот наша политика конфиденциальности.