简体   繁体   English

如何通过“ exec”和“ eval”替换(或扩展)RepoSurgeon的现有Python类方法?

[英]How to replace existing Python class methods of (or otherwise extend) RepoSurgeon by means of 'exec' and 'eval'?

The documentation (at the time of this writing) on the topic is scarce. 关于该主题的文档 (在撰写本文时)很少。 How can I extend reposurgeon functionality if the use of macros ( define ) isn't sufficient for my purposes? 如果使用宏( define )不足以实现我的目的,该如何扩展后座功能?

The only clues it gives is that: 它提供的唯一线索是:

The code has full access to all internal data structures. 该代码具有对所有内部数据结构的完全访问权限。 Functions defined are accessible to later eval calls. 定义的函数可用于以后的eval调用。

But what does that even mean? 但这到底意味着什么?

We also learn that: 我们还了解到:

Typically this will be a call to a function defined by a previous exec. 通常,这将是对先前执行程序定义的函数的调用。 The variables _repository and _selection will have the obvious values. 变量_repository和_selection具有明显的值。 Note that _selection will be a list of integers, not objects. 请注意,_selection将是整数列表,而不是对象。

Preliminary note 初步说明

I will use italic inline code ( like this ) to denote Python code and "normal" inline code ( like this ) to denote RepoSurgeon commands. 我将使用斜体内联代码( like this )表示Python代码,并使用“普通”内联代码( like this )表示RepoSurgeon命令。 For code blocks, an introductory description should provide the context, ie whether it's a RepoSurgeon command or Python code. 对于代码块,应该在上下文中提供介绍性说明,即是RepoSurgeon命令还是Python代码。

This writeup discusses version 3.10 of RepoSurgeon, which is the latest as of this writing. 本文讨论了RepoSurgeon的3.10版本,这是撰写本文时的最新版本。

Intro 介绍

RepoSurgeon is written in Python and explicitly allows to execfile() other Python code within it. RepoSurgeon用Python编写,并明确允许在其中执行execfile()其他Python代码。 The syntax of the RepoSurgeon command for it is: RepoSurgeon命令的语法为:

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

This much we can gather from the documentation. 我们可以从文档中收集到很多。

We can use this from within a lift script or on the RepoSurgeon prompt. 我们可以在提升脚本中或RepoSurgeon提示符下使用它。

Where does your code end up? 您的代码在哪里结束?

As already pointed out in this Q&A you need to observe the rules imposed by the surrounding code when running in the context of RepoSurgeon. 本问答中已指出的那样, RepoSurgeon上下文中运行时,您需要遵守周围代码所施加的规则。 In particular your Python code is going to be executed within the context of the __main__.RepoSurgeon instance , so this is the first thing to keep in mind. 特别是您的Python代码将在__main__.RepoSurgeon实例的上下文中执行 ,因此这是第一件事。

You also will always have to give a selection with eval . 您还必须始终使用eval进行选择。 It doesn't appear to be legit to give no selection and expect an implied "all selected" as for list or other built-in commands, although you can arguably leverage exec to change that behavior as we'll see in a bit. 尽管您可以利用exec改变行为,但是可以争辩,但似乎没有给出任何选择并期望对list或其他内置命令隐含“全部选中”的行为是合法的。

Also make sure to use eval myfunc() and not eval myfunc . 还要确保使用eval myfunc()而不是eval myfunc Obviously myfunc is a valid Python statement, but don't expect it to do anything. 显然, myfunc是有效的Python语句,但是不要期望它会做任何事情。 You'll have to call the function. 您必须调用该函数。 Everything after eval is handed straight to Python's eval() . eval之后的所有内容都直接传递给Python的eval()

While execfile() ( exec as a RepoSurgeon) runs you can abuse the context you're running in and reference self , which is the instance of __main__.RepoSurgeon mentioned above. execfile()exec作为RepoSurgeon)运行时,您可以滥用正在运行的上下文并引用selfself是上述__main__.RepoSurgeon的实例。 More about this later. 稍后再详细介绍。

A first trivial example 第一个平凡的例子

Consider the following Python code that introduces a new unbound function myfunc : 考虑以下引入了新的未绑定函数myfunc Python代码:

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

and the following command issued at the RepoSurgeon prompt: 并在RepoSurgeon提示符下发出以下命令:

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

followed by: 其次是:

=O eval myfunc()

This will yield the expected output: 这将产生预期的输出:

Hello world!

You may want to use a different selection from mine, though. 不过,您可能要使用与我不同的选择。 Whichever suits your needs. 满足您的需求。

Note: In any case even an empty selection will still result in your Python code being called! 注意:在任何情况下,即使是空选择也将导致您的Python代码被调用! For example the selection =I in my loaded repo is empty, but I will still see the output as produced above. 例如,加载的仓库中的=I选项为空,但仍会看到上面产生的输出。 It's simply important to give any selection to have your code invoked. 它给予援引任何选择你的代码简直是非常重要的。

Exploring the context in which our Python code runs 探索运行Python代码的上下文

With the above trivial example we can check whether it works. 通过上面的例子,我们可以检查它是否有效。 Now on to explore what we can access besides _selection and _repository mentioned in the documentation. 现在就来探究一下,我们除了可以访问_selection_repository的文件中提及。

Changing the function myfunc to: 将函数myfunc更改为:

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

should give us a feeling what we're dealing with. 应该给我们一种我们正在处理的感觉。

After the change (and saving it ;)) simply re-run: 更改后(并保存;),只需重新运行即可:

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

followed by: 其次是:

=O eval myfunc()

you should see a dump of the contents of globals() and locals() . 您应该看到globals()locals()内容的转储。

You will notice that even in the context of the eval you can still access self (part of the globals() in this case). 您会注意到,即使在eval的上下文中,您仍然可以访问self (在这种情况下,它是globals()一部分)。 That's pretty useful. 这很有用。

As I mentioned before, you can also modify the instance of __main__.RepoSurgeon within which your code runs (more about this below). 如前所述,您还可以修改在其中运行代码的__main__.RepoSurgeon实例(有关此内容的更多信息,请__main__.RepoSurgeon下文)。

In order to see all methods etc, use dir(self) in your function (or at the top-level when exec -ing the Python code file). 为了查看所有方法等,请在函数中使用dir(self) (或在exec Python代码文件时在顶层使用)。

So simply add this line to myfunc : 因此,只需将此行添加到myfunc

dir(self)

making it: 进行中:

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

after invoking the exec and eval commands again (on Linux recall it as you would in the shell using cursor Up ) you should now see most of the functions listed you'd also be able to find the the RepoSurgeon code. 再次调用execeval命令之后(在Linux上,您可以像使用光标Up一样在shell中调用它),现在应该看到列出的大多数功能,并且还可以找到RepoSurgeon代码。

Note: simply re-running RepoSurgeon's exec command followed by another eval myfunc() will now add the output of the attributes of __main__.RepoSurgeon . 注意:现在只需重新运行RepoSurgeon的exec命令,然后再运行另一个eval myfunc()现在将添加__main__.RepoSurgeon属性的输出。

While all of this is cool so far and should give you a feeling for how to run your own Python code in RepoSurgeon, you can also replace existing __main__.RepoSurgeon methods. 到目前为止,所有这些都很酷,并且应该使您对如何在RepoSurgeon中运行自己的Python代码有一种感觉,但是您也可以替换现有的__main__.RepoSurgeon方法。 Read on. 继续阅读。

Hooking into RepoSurgeon and replacing functionality 接触RepoSurgeon并替换功能

With the access to self comes the power to add functionality and to modify existing functionality. 拥有self的能力就可以添加功能修改现有功能。

RepoSurgeon.precmd looks like a worthy candidate for this. RepoSurgeon.precmd看起来像是一个值得的候选人。 It's the method that gets called prior to running the actual command and performs a syntax check as well as setting the selection set that is so vital in many RepoSurgeon commands. 这是在运行实际命令之前执行的方法,它执行语法检查以及设置选择集,这在许多RepoSurgeon命令中都至关重要。

What we need is the prototype of precmd . 我们需要的是precmd的原型。 Here it is: 这里是:

def precmd(self, line):

What was the trick again in replacing method? 更换方法又有什么窍门? Alex Martelli's answer here leads the way ... Alex Martelli在这里的答案引领了前进的方向...

We can simply use this as our (complete) Python file to exec : 我们可以简单地使用它作为我们的(完整)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__))
  • The if self: is merely to scope our code. if self:仅仅是为了限制我们的代码。
  • The check for 'orig_precmd' ensures we're not overwriting the value of this attribute again upon subsequent calls to exec . 检查“ orig_precmd”可确保我们在随后调用exec时不会再次覆盖此属性的值。
  • myprecmd(self, line): contains our version of __main__.RepoSurgeon.precmd . myprecmd(self, line):包含我们的__main__.RepoSurgeon.precmd版本。 The awesome new functionality it adds is to parrot the command that was entered. 它添加的令人敬畏的新功能是模仿输入的命令。
  • Last but not least the second setattr() simply overrides the __main__.RepoSurgeon.precmd with our version. 最后但并非最不重要的一点是,第二个setattr()仅使用我们的版本覆盖__main__.RepoSurgeon.precmd Any subsequent call RepoSurgeon makes to self.precmd() will go through our "hook" now. RepoSurgeon随后对self.precmd()任何调用都将通过我们的“钩子”。

Remember, we are overriding internal code of RepoSurgeon, so tread carefully and don't do anything silly. 记住,我们覆盖了RepoSurgeon的内部代码,因此请谨慎行事,不要做任何愚蠢的事情。 The code is very readable, albeit a whopping 10k LoC. 该代码可读性很强,尽管LoC高达10k。

Next time you issue any command, you should have it parroted back at you. 下次发出任何命令时,都应该让它重新回到您的手中。 Consider the following (RepoSurgeon prompt plus excerpt from output): 考虑以下内容(RepoSurgeon提示以及输出摘录):

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

=O list is the command I entered and [pre-precmd] '=O list' the output it yields (followed by the actual output, since I am calling the original implementation of __main__.RepoSurgeon.precmd in my version). =O list是我输入的命令,然后[pre-precmd] '=O list'产生的输出(之后是实际输出,因为我在我的版本中调用__main__.RepoSurgeon.precmd的原始实现)。

Conclusion 结论

The RepoSurgeon commands exec and eval provide a powerful means to override existing functionality and add new functionality in RepoSurgeon. RepoSurgeon命令execeval提供了一种强大的手段来覆盖现有功能并在RepoSurgeon中添加新功能。

The hook example is a superset of "simply" extending RepoSurgeon by using eval with a previously exec 'd function. 钩子示例是通过将eval与以前的exec eval一起使用来“简单”扩展RepoSurgeon的超集。 It allows to sneak code into the guts of RepoSurgeon and bend it to our will where ever it has shortcomings (of which I have only found a handful so far). 它允许将代码潜入RepoSurgeon的胆量,并在存在缺点的地方将其屈服于我们的意愿(到目前为止,我仅发现了少数缺点)。

Kudos to ESR for this design decision. ESR对此设计决定表示敬意。 There is no need for a plugin framework this way. 不需要这种方式的插件框架。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM