Как заменить существующие методы класса Python (или иным образом расширить) RepoSurgeon с помощью "exec" и "eval"?

Документация (на момент написания этой статьи) по теме недостаточна. Как я могу расширить функциональность reposurgeon, если использование макросов (define) не достаточно для моих целей?

Единственное, что он дает, это то, что:

Код имеет полный доступ ко всем внутренним структурам данных. Определенные функции доступны для последующих вызовов eval.

Но что это вообще значит?

Мы также узнаем, что:

Обычно это будет вызов функции, определенной предыдущим exec. Переменные _repository и _selection будут иметь очевидные значения. Обратите внимание, что _selection будет списком целых чисел, а не объектов.

1 ответ

Предварительная записка

Я буду использовать курсивный встроенный код (like this) для обозначения кода Python и "нормального" встроенного кода (like this) для обозначения команд RepoSurgeon. Для блоков кода вводное описание должно предоставлять контекст, т.е. будь то команда RepoSurgeon или код Python.

В этой статье обсуждается версия 3.10 RepoSurgeon, которая является последней на момент написания этой статьи.

вступление

RepoSurgeon написан на Python и явно позволяет execfile() другой код Python внутри. Синтаксис команды RepoSurgeon для нее:

exec </path/to/python-source.py

Это много мы можем собрать из документации.

Мы можем использовать это в скрипте лифта или в приглашении RepoSurgeon.

Где заканчивается ваш код?

Как уже указывалось в этом вопросе и ответах, вам необходимо соблюдать правила, налагаемые окружающим кодом при работе в контексте RepoSurgeon. В частности, ваш код Python будет выполняться в контексте __main__.RepoSurgeon Например, это первое, что нужно иметь в виду.

Вы также всегда должны будете дать выбор с eval, Это не кажется законным, чтобы не делать выбор и ожидать подразумеваемого "все отобранные", как для list или другие встроенные команды, хотя вы можете использовать exec изменить это поведение, как мы увидим чуть позже.

Также обязательно используйте eval myfunc() и не eval myfunc, очевидно myfunc является допустимым оператором Python, но не ожидайте, что он что-то сделает. Вам придется вызвать функцию. Все после eval передается прямо в Python eval(),

В то время как execfile() (exec как RepoSurgeon) вы можете злоупотреблять контекстом, в котором вы работаете, и ссылками self, который является примером __main__.RepoSurgeon упомянутое выше. Подробнее об этом позже.

Первый тривиальный пример

Рассмотрим следующий код Python, который вводит новую несвязанную функцию myfunc:

def myfunc():
    print("Hello world!")

и следующая команда, введенная в приглашении RepoSurgeon:

exec </path/to/your/python-code.py

с последующим:

=O eval myfunc()

Это даст ожидаемый результат:

Hello world!

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

Примечание. В любом случае даже пустой выбор все равно приведет к вызову вашего кода Python! Например, выбор =I в моем загруженном репо пусто, но я все равно увижу вывод, как показано выше. Просто важно сделать выбор, чтобы ваш код вызывался.

Изучение контекста, в котором работает наш код Python

С приведенным выше тривиальным примером мы можем проверить , работает ли он. Теперь, чтобы изучить, что мы можем получить доступ, кроме _selection а также _repository упоминается в документации.

Изменение функции myfunc чтобы:

def myfunc():
    from pprint import pprint
    pprint(globals())
    pprint(locals())

должен дать нам ощущение того, с чем мы имеем дело.

После изменения (и сохранения его;)) просто перезапустите:

exec </path/to/your/python-code.py

с последующим:

=O eval myfunc()

вы должны увидеть дамп содержимого globals() а также locals(),

Вы заметите, что даже в контексте eval вы все еще можете получить доступ self (часть globals() в этом случае). Это довольно полезно.

Как я уже упоминал ранее, вы также можете изменить экземпляр __main__.RepoSurgeon внутри которого работает ваш код (подробнее об этом ниже).

Чтобы увидеть все методы и т. Д., Используйте dir(self) в вашей функции (или на верхнем уровне, когда exec -ing файл кода Python).

Так что просто добавьте эту строку в myfunc:

dir(self)

делая это:

def myfunc():
    from pprint import pprint
    pprint(globals())
    pprint(locals())
    dir(self)

после вызова exec а также eval Снова введите команды (в Linux вызовите их, как в командной оболочке, используя курсор вверх), теперь вы должны увидеть большинство перечисленных функций, которые вы также сможете найти в коде RepoSurgeon.

Примечание: просто перезапустите RepoSurgeon's exec команда, сопровождаемая другим eval myfunc() Теперь добавим вывод атрибутов __main__.RepoSurgeon,

Пока все это круто и должно дать вам представление о том, как запустить собственный код Python в RepoSurgeon, вы также можете заменить существующий __main__.RepoSurgeon методы. Читать дальше.

Подключение к RepoSurgeon и замена функциональности

С доступом к selfприходит возможность добавить функциональность и изменить существующую функциональность.

RepoSurgeon.precmd выглядит достойным кандидатом на это. Это метод, который вызывается до запуска фактической команды и выполняет проверку синтаксиса, а также установку набора выбора, который так важен во многих командах RepoSurgeon.

Нам нужен прототип precmd, Вот:

def precmd(self, line):

Какова была хитрость снова в замене метода? Ответ Алекса Мартелли здесь ведет путь...

Мы можем просто использовать это как наш (полный) файл Python для exec:

if self:
    if not 'orig_precmd' in self.__dict__:
        setattr(self, 'orig_precmd', self.precmd) # save original precmd
    def myprecmd(self, line):
        print("[pre-precmd] '%s'" % line)
        orig_precmd = getattr(self, 'orig_precmd')
        return self.orig_precmd(line)
    setattr(self, 'precmd', myprecmd.__get__(self, self.__class__))
  • if self: просто для охвата нашего кода.
  • Проверка 'orig_precmd' гарантирует, что мы не перезаписываем значение этого атрибута снова при последующих вызовах exec,
  • myprecmd(self, line): содержит нашу версию __main__.RepoSurgeon.precmd, Потрясающая новая функциональность, которую он добавляет, - это попугай введенной команды.
  • Последнее, но не менее важное setattr() просто переопределяет __main__.RepoSurgeon.precmd с нашей версией. Любой последующий вызов RepoSurgeon делает для self.precmd() сейчас пройдем через наш "крюк".

Помните, что мы переопределяем внутренний код RepoSurgeon, поэтому действуйте осторожно и не делайте глупостей. Код очень удобочитаемый, хотя и колоссальный 10 тыс. Лок.

В следующий раз, когда вы введете какую-либо команду, вы должны вернуть ее вам. Рассмотрим следующее (подсказка RepoSurgeon плюс выдержка из вывода):

reposurgeon% =O list
[pre-precmd] '=O list'

=O list это команда, которую я ввел и [pre-precmd] '=O list' вывод, который он дает (сопровождается фактическим выводом, так как я называю оригинальную реализацию __main__.RepoSurgeon.precmd в моей версии).

Заключение

Команды RepoSurgeon exec а также eval предоставить мощные средства для переопределения существующих функций и добавления новых функций в RepoSurgeon.

Пример ловушки - это расширенный набор "простого" расширения RepoSurgeon с помощью eval с ранее exec 'функция Это позволяет внедрить код в кишки RepoSurgeon и подчинить его нашей воле, где бы он ни имел недостатки (из которых я пока нашел лишь несколько).

Престижность ESR для этого дизайнерского решения. Таким образом, нет необходимости в плагине.

Другие вопросы по тегам