Сегодня немного о том, как обрабатывать Python код: читать и модифицировать.
ast – built-in модуль, позволяющий получать общую информацию о Python коде. Используется в большинстве falke8 пдагинов. Он ничего не знает о структуре файлов, только “суть”. Например, теряется информация о пробелах. Собственно, поэтому pycodestyle, содержащий все проверки по умолчанию во flake8, работает исключительно на регулярных выражениях.
Module(body=[Import(names=[alias(name='lol', asname=None)])])```
[ast.NodeTransformer](https://docs.python.org/3/library/ast.html#ast.NodeTransformer) позволяет изменять какие-то символы налету, но конвертировать это обратно в код не получится. Например, это используется в [pyrthon](https://github.com/tobgu/pyrthon) -- библиотеке, которая через import hook подменяет встроенные Python-литералы на иммутабельные структуры из [pyrsistent](https://github.com/tobgu/pyrsistent).
[tokenize](https://docs.python.org/3/library/tokenize.html) -- позволяет не только читать исходный код и трансформировать его, но и дампать трансформированный результат обратно в код.
```> t = list(itertools.islice(tokenize.tokenize(lambda: b'import lol'), 3))
> t
[TokenInfo(type=59 (ENCODING), string='utf-8', start=(0, 0), end=(0, 0), line=''),
TokenInfo(type=1 (NAME), string='import', start=(1, 0), end=(1, 6), line='import lol'),
TokenInfo(type=1 (NAME), string='lol', start=(1, 10), end=(1, 13), line='import lol')]
> tokenize.untokenize(t)
b'import lol'```
Работает это всё не регулярках и предоставляет, как видно выше, довольно посредственную и косвенную информацию о пробелах и прочем. Используется в [black](https://github.com/python/black) и вполне сочетается с их философией "забей на всё, что там было, и перепиши весь код с нуля".
[rope](https://github.com/python-rope/rope) -- большой и древний проект для рефакторинга Python-кода. Работает поверх AST. Позволяет, кажется, сделать довольно много, но работать с ядром довольно сложно. Есть большая коллекция встроенных преобразований, таких как переименование объектов по всему проекту, изменение относительных импортов на абсолютные, перемещение объектов между модулями с фиксом всех импортов и т.д. Я столкнулся с тем, что все эти операции выполняются с "ресурсами", и ресурс должен находиться внутри проекта. То есть, переименовать импорты внешней библиотеки не получится. А так, возможности там огромные. Rope используется для рефакторинга в PyCharm.
Когда появился Python3, в стандартной библиотеке появился инструмент [2to3](https://docs.python.org/2/library/2to3.html), автоматически меняющий старый синтаксис на новый. Этот инструмент использует внутри [lib2to3](https://github.com/python/cpython/tree/master/Lib/lib2to3) -- мощный набор механизмов для работы с Python грамматиками, сохраняющий абсолютно всю информацию об исходном файле и позволяющий это довольно безболезненно изменять. Потенциальные возможности у этой штуки колоссальные. Если бы её хорошо задокументировали, то, возможно, у нас был бы уже нормальный автоформаттер вместо black.