Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Как прокачать линтер
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Maxim Mazaev
March 23, 2019
Programming
0
34
Как прокачать линтер
Python Meetup Chelyabinsk #5
Maxim Mazaev
March 23, 2019
Tweet
Share
More Decks by Maxim Mazaev
See All by Maxim Mazaev
Проверка типов в большом проекте
mmazaev
0
24
Pylint изнутри. Как он это делает
mmazaev
0
91
Как поддерживать согласованность в микросервисной архитектуре
mmazaev
0
59
Other Decks in Programming
See All in Programming
コントリビューターによるDenoのすゝめ / Deno Recommendations by a Contributor
petamoriken
0
200
開発者から情シスまで - 多様なユーザー層に届けるAPI提供戦略 / Postman API Night Okinawa 2026 Winter
tasshi
0
210
Smart Handoff/Pickup ガイド - Claude Code セッション管理
yukiigarashi
0
140
今こそ知るべき耐量子計算機暗号(PQC)入門 / PQC: What You Need to Know Now
mackey0225
3
380
AIエージェント、”どう作るか”で差は出るか? / AI Agents: Does the "How" Make a Difference?
rkaga
4
2k
[KNOTS 2026登壇資料]AIで拡張‧交差する プロダクト開発のプロセス および携わるメンバーの役割
hisatake
0
290
Package Management Learnings from Homebrew
mikemcquaid
0
230
CSC307 Lecture 02
javiergs
PRO
1
780
CSC307 Lecture 01
javiergs
PRO
0
690
ノイジーネイバー問題を解決する 公平なキューイング
occhi
0
110
16年目のピクシブ百科事典を支える最新の技術基盤 / The Modern Tech Stack Powering Pixiv Encyclopedia in its 16th Year
ahuglajbclajep
5
1k
AI Agent の開発と運用を支える Durable Execution #AgentsInProd
izumin5210
7
2.3k
Featured
See All Featured
How to train your dragon (web standard)
notwaldorf
97
6.5k
Practical Orchestrator
shlominoach
191
11k
Fireside Chat
paigeccino
41
3.8k
Faster Mobile Websites
deanohume
310
31k
Leading Effective Engineering Teams in the AI Era
addyosmani
9
1.6k
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
61
52k
The Art of Programming - Codeland 2020
erikaheidi
57
14k
What’s in a name? Adding method to the madness
productmarketing
PRO
24
3.9k
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
60
42k
Paper Plane (Part 1)
katiecoart
PRO
0
4.3k
Lightning Talk: Beautiful Slides for Beginners
inesmontani
PRO
1
440
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
130
Transcript
Как прокачать линтер Максим Мазаев
• Программист в ЦИАН • Пишу на Python • Спикер
PyCon, Moscow Python Conf++ О себе
Как прокачать линтер
• 1 млн уникальных пользователей в сутки • 10x разработка
за 2014-2019 • 100+ разработчиков • 30 питонистов • Сотни строк нового кода каждый день О ЦИАН
Код-ревью в Циане 1. Одобрение от робота (тесты, линтер etc.)
2. Одобрение от 2 разработчиков
Проблемы с код-ревью • Есть внутренние договоренности по стилю кода.
Линтер не покрывает
Проблемы с код-ревью • Есть внутренние договоренности по стилю кода.
Линтер не покрывает • Линтер не покрывает наши договоренности по стилю.
Проблемы с код-ревью Программист работает вместо линтера
Решение • Инструмент который знает про наши соглашения • И
проверяет их
Решение
Решение ?
Решение А не написать ли нам свой линтер?
Решение А не написать ли нам свой линтер? Нет, у
нас уже есть pylint • он удобный • он встроен в наши процессы
Решение Написать плагин для Pylint
Пример №1
2015 Sergey # TODO: выпилить (не выпилили) 2015 Misha #
TODO: Убрать после успешного релиза (не убрали) 2017 Vadim # TODO: после эксперимента удалим (не удалили) ¯\_(ツ)_/¯
Пример №1 Проблема: В коде много комментариев «TODO» • Висят
годами • Не выполняются • Копится техдолг
class TodoIssueChecker(BaseChecker): __implements__ = IRawChecker Будем итерироваться по строкам модуля
(«сырой» чекер) Решение. Пишем свой чекер для Pylint
msgs = { 'С9999': ('Комментарий с TODO без ссылки на
задачу', 'issue-code-in-todo', 'Длинное описание')} Код и мнемоничеcкое название чекера Пишем свой чекер
С1234 [C]onvention [W]arning Произвольные 4 цифры [E]rror (уникальные в рамках
pylint) [F]atal [R]efactoring Пишем свой чекер. Код сообщения
msgs = { 'С9999': ('Комментарий с TODO без ссылки на
задачу', 'issue-code-in-todo', 'Длинное описание')} # pylint: disable=C9999 # pylint: disable=issue-code-in-todo Пишем свой чекер
def process_module(self, node): with node.stream() as stream: for (lineno, line)
in enumerate(stream): if has_todo_without_issue_code(line): self.add_message( 'issue-code-in-todo', line=lineno ) Пишем свой чекер Выводим предупреждение Ходим по строкам
def register(linter: PyLinter) -> None: linter.register_checker( TodoIssueChecker(linter) ) * Модуль
чекера должен быть в PYTHONPATH Регистрируем чекер *
$ cat work.py # TODO: Удалю через неделю, честно-честно! $
pylint work.py --load-plugins todo_checker ... И запускаем!
Тем временем внутри pylint...
1. Инициализация плагинов 1. Импортируем все модули, где есть плагины
2. module.register(self) <- Регистрируем + Проверяем, что параметры валидны + Регистрируем опции, сообщения и отчеты
2. Разбираем пул чекеров Чекер Чекер Чекер Чекер Чекер Чекер
Raw checker AST checker Token checker
3. Запуск чекера Запускаем метод process_module, определенный в чекере checker.process_module(module)
$ cat work.py # TODO: Удалю через неделю, честно-честно! $
pylint work.py --load-plugins todo_checker C: 0, 0: Комментарий с TODO без ссылки на задачу (issue-code-in-todo) Результат
get_offer_by_params( “sale”, True, 859483, ) Пример №2. Keyword-arguments Не всегда
очевидно что это за аргументы
get_offer_by_params( “sale”, True, 859483, ) Пример №2. Keyword-arguments get_offer_by_params( deal_type=”sale”,
truncate=True, cian_id=859483, ) Плохо Хорошо
Пример №2. Keyword-arguments Проблема: Сложно написать «сырой» checker
Пример №2. Keyword-arguments Проблема: Сложно написать «сырой» checker Решение: Чекер
на основе AST
Лирическое отступление про AST
Преобразование кода в AST. Вызов функции get_offer( 112, deal_type=’sale’, offer_type=’flat’,
) Call( func=Name(name='get_offer'), args=[Const(value=112)], keywords=[ Keyword( arg='deal_type', value=Const(value='sale') Keyword( arg='offer_type', value=Const(value='flat')) ]))]
Задача. • Найти все ноды Call (вызов функции) • Посчитать
количество аргументов • Проверить отсутствие позиционных аргументов Call( func=Name(name='get_offer'), args=[Const(value=1298880)], keywords=[ ... ]))]
class NonKeywordArgsChecker(BaseChecker): __implements__ =IAstroidChecker Чекер на основе AST Решение. Пишем
чекер на основе AST
msgs = { 'С9191': ('Краткое описание', 'keyword-only-args', 'Длинное описание')} Код
и мнемоничеcкое название чекера Пишем чекер на основе AST
def visit_call(self, node: Call) … visit_<Имя_ноды> вызывается при заходе на
ноду leave_<Имя_ноды> при уходе с ноды Пишем чекер на основе AST
def visit_call(self, n: Node): if n.args and len(n.args + n.keywords)
> 2: self.add_message( 'keyword-only-args', node=node ) Пишем чекер на основе AST
def register(linter: PyLinter): linter.register_checker( TodoIssueChecker(linter) ) Регистрируем чекер
$ cat work.py get_offers(1, True, deal_type=”sale”) $ pylint work.py --load-plugins
non_kwargs_checker ... И запускаем!
Тем временем внутри pylint...
1. Инициализация плагинов 1. Импортируем все модули, где есть плагины
2. module.register(self) <- Регистрируем + Проверяем, что параметры валидны + Регистрируем опции, сообщения и отчеты
2. Разбор модуля на AST Разбор выполняет библиотека Astroid (обертка
над AST-парсером) github.com/PyCQA/astroid/
Почему Astroid, а не AST (stdlib) 1. Разбирает дерево с
помощью typed_ast (c поддержкой тайпхинтов из PEP 484)
Почему Astroid, а не AST (stdlib) from module import Entity
def foo(bar): # type: (Entity) -> None return
Почему Astroid, а не AST (stdlib) 2. Собирает обратно с
дополнительными плюшками: from foo import * Импортирует и добавит в locals содержимое foo
Почему Astroid, а не AST (stdlib) 3. Transform plugins •
Поможем Astroid разобраться в динамической природе питона. • Дополняем AST полезной информацией см. pylint-django
3. Разбираем пул чекеров Чекер Чекер Чекер Чекер Чекер Чекер
AST checker Raw checker Token checker
4. Разобрать чекеры по типам нод 1. У каждого чекера
находим методы visit_<Имя ноды> leave_<Имя ноды>
4. Разобрать чекеры по типам нод 2. И сохраняем: _visit_methods
= dict( <Имя ноды>: [checker1, checker2,… ] ) _leave_methods = dict( <Имя ноды>: [checker1, checker2,… ] )
$ cat work.py get_offers(1, True, deal_type=”sale”) $ pylint work.py --load-plugins
non_kwargs_checker C: 0, 0: Функция с >2 аргументами вызывается с позиционными аргументами (keyword-only-args) Запускаем чекер!
class TestNonKwArgsChecker(CheckerTestCase): CHECKER_CLASS = NonKeywordArgsChecker Чекер, который тестируем A тесты
написать?
1. Создаем тестовую AST-ноду node = astroid.extract_node( "get_offers(3, 'magic', 'args')"
) А тесты написать?
2. Проверяем что Pylint бросает предупреждение with self.assertAddsMessages(message): self.checker.visit_call(node) А
тесты написать?
Еще один тип чекера - TokenChecker Под капотом - tokenize,
лексический сканер
Лексический анализ кода NAME NAME OP NAME OP NAME OP
OP NEWLINE def sum(a, b): INDENT NAME NAME OP NAME NEWLINE return a + b DEDENT ENDMARKER
Как Pylint работает с TokenChecker 1. Токенизирует модуль 2. Для
всех чекеров с интерфейсом ITokenChecker вызывает: process_tokens(tokens)
Применение TokenChecker 1. Проверка правописания 2. Проверка отступов 3. Работа
со строками. ...
Хочу все знать github.com/PyCQA/Pylint
Выводы Чего добились: • Переложили рутинные проверки на линтер •
Уменьшили количество отклоненных пулл-реквестов • Повысили качество ревью
Максим Мазаев
[email protected]
facebook.com/mazaev.maksim