Как заменить существующие методы класса 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 для этого дизайнерского решения. Таким образом, нет необходимости в плагине.