Взаимодействие на основе патчей. Исследование кода

Ещё про работу с историей

Статья на Хабре про стратегии git merge

Раздельное добавление ханков

Работа с патчами и наборами патчей

Немного о формате

Патчи и Git:

Патч или набор с точки зрения GIT — это сериализация коммитов, превращение их в пригодный для передачи формат.

BTW: difflib

(если успеем) Организация взаимодействия при совместной разработке

Классическая модель (если не будет хватать времени, рассмотрим только её):

Открытая модель:

Модель общего хостинга:

Централизованная модель:

Ветки и индивидуальные репозитории — ортогональные сущности:

Исследование кода

В прошлом Д/З упоминался inspect (но там он не нужен).

Парсинг и исследование синтаксиса — ast

Пример разбора дерева «на скорую руку»:

   1 def astformat(node):
   2     if isinstance(node, ast.AST):
   3         args = []
   4         for field in node._fields:
   5             value = getattr(node, field)
   6             args.append(astformat(value))
   7         return f"{node.__class__.__name__} {''.join(args)}\n"
   8     elif isinstance(node, list):
   9         return "".join(astformat(x) for x in node)
  10     return str(node)

Сравнение текстов: difflib

(Если успеем) Доступ к байт-коду — dis

Д/З

  1. Написать программу dubfinder.py, которая исследует модули на наличие похожих функций или методов с учётом переименования объектов, наличия произвольных комментариев и изменений в форматировании.

    • Параметры командной строки: dubfinder.py модуль1 необязательный модуль2 …

    • Программа выводит пары функций, исходный текст которых «очень похож» (см. ниже критерии)
    • Вывод: для dubfinder.py spiral (spiral.py):

      $ python3 dubfinder.py spiral        
      spiral.Spiral.__add__ : spiral.Spiral.__sub__
      spiral.Spiral.__mul__ : spiral.Spiral.__rmul__
    • Вывод: для dubfinder.py spiral spiral2 (spiral.py и spiral2.py).

      spiral.Spiral.__add__ spiral2.Spiral.__add__
      spiral.Spiral.__init__ spiral2.Spiral.__init__
      spiral.Spiral.__iter__ spiral2.Spiral.__iter__
      spiral.Spiral.__len__ spiral2.Spiral.__len__
      spiral.Spiral.__mul__ spiral.Spiral.__rmul__
      spiral.Spiral.__rmul__ spiral2.Spiral.__mul__
      spiral.Spiral.__str__ spiral2.Spiral.__str__
      spiral.Spiral.__sub__ spiral2.Spiral.__sub__
      spiral.Spiral._square spiral2.Spiral._show
      spiral.main spiral2.master
      spiral2.Spiral.__add__ spiral2.Spiral.__sub__
      spiral2.Spiral.__mul__ spiral2.Spiral.__rmul__
    • Требования по выводу не вполне чёткие — он должен быть упорядочен лексикографически, но что делать, если «одинаковых» функций более двух, не регламентировано

      • Полное совпадение __mul__ и __rmul__ в одном модуле

      • Частичное совпадение __add__ и __sub__ в одном модуле

      • Полное совпадение соответствующих функций и методов в обоих модулях
    • Допущения относительно проверяемых модулей (для простоты):
      • Не используются docstring-и и аннотации
      • Не используются подмодули
      • Не используются инструкции вида from модуль import что-то

  2. Возможный вариант реализации (я сделал так, но теперь мне кажется, что он более сложный):
    1. Модули разрешено import-тить (с помощью importlib)

    2. Затем рекурсивно просмотрим их с помощью inspect.getmembers() и составим список определённых в них функций

      • Разрешено игнорировать классы, имя которых начинается с "__" (например, не стоит рекурсивно заходить в что_то.__class__)

    3. Для каждой функции с помощью inspect.getsource() получаем исходный текст.

    4. С помощью ast.parse() превращаем этот текст в дерево разбора (для методов надо предварительно применить textwrap.dedent()

    5. Заменить в дереве все идентификаторы на один (например, на "_")

      • Я просто прошёлся по ast.walk() и подменил все атрибуты 'name', 'id', 'arg', 'attr' у тех узлов, у которых они были

    6. Собрать обратно препарированный текст с помощью ast.unparse()

      • Разумеется, комментарии при этом исчезают, а вместо имён везде стоит "_"

    7. Составим словарик вида {найденная_функция: препарат}

    8. Сравним все препараты друг с другом (это n²/2, увы) и для каждой функции подберём наиболее «близкую» с помощью difflib.SequenceMatcher.ratio()

    9. Если это ratio() > 0.95 — пара считается «похожей»

  3. Есть и другой вариант решения: вместо importlib и inspect использовать тот же ast, и вручную обходить дерево. Возможно, он проще.

  4. В отчётном репозитории с Д/З создать подкаталог 05_DiffPatchAST и поместить туда решение

LecturesCMC/PythonDevelopment2022/05_DiffPatchAST (последним исправлял пользователь FrBrGeorge 2022-03-12 21:00:55)