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
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
それ、本当に安全? ファイルアップロードで見落としがちなセキュリティリスクと対策
penpeen
7
3.9k
[KNOTS 2026登壇資料]AIで拡張‧交差する プロダクト開発のプロセス および携わるメンバーの役割
hisatake
0
290
高速開発のためのコード整理術
sutetotanuki
1
410
LLM Observabilityによる 対話型音声AIアプリケーションの安定運用
gekko0114
2
430
Patterns of Patterns
denyspoltorak
0
1.4k
開発者から情シスまで - 多様なユーザー層に届けるAPI提供戦略 / Postman API Night Okinawa 2026 Winter
tasshi
0
210
そのAIレビュー、レビューしてますか? / Are you reviewing those AI reviews?
rkaga
6
4.6k
Apache Iceberg V3 and migration to V3
tomtanaka
0
170
Basic Architectures
denyspoltorak
0
680
2026年 エンジニアリング自己学習法
yumechi
0
140
HTTPプロトコル正しく理解していますか? 〜かわいい猫と共に学ぼう。ฅ^•ω•^ฅ ニャ〜
hekuchan
2
690
例外処理とどう使い分ける?Result型を使ったエラー設計 #burikaigi
kajitack
16
6.1k
Featured
See All Featured
The SEO Collaboration Effect
kristinabergwall1
0
350
Rebuilding a faster, lazier Slack
samanthasiow
85
9.4k
Reality Check: Gamification 10 Years Later
codingconduct
0
2k
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.3k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
287
14k
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
63
What the history of the web can teach us about the future of AI
inesmontani
PRO
1
430
Tell your own story through comics
letsgokoyo
1
810
brightonSEO & MeasureFest 2025 - Christian Goodrich - Winning strategies for Black Friday CRO & PPC
cargoodrich
3
100
Speed Design
sergeychernyshev
33
1.5k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
122
21k
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