如何通过' exec'替换RepoSurgeon的现有Python类方法(或以其他方式扩展)。和' eval'?

时间:2014-08-06 00:46:48

标签: python reposurgeon

关于这个话题的

The documentation(在撰写本文时)很少。如果使用宏(define)不足以达到我的目的,我该如何扩展reposurgeon功能呢?

它给出的唯一线索是:

  

代码可以完全访问所有内部数据结构。定义的函数可供以后的 eval 调用访问。

但这甚至意味着什么?

我们也了解到:

  

通常,这将是对前一个exec定义的函数的调用。变量_repository和_selection将具有明显的值。请注意,_selection将是整数列表,而不是对象。

1 个答案:

答案 0 :(得分:2)

初步说明

我将使用斜体内联代码( like this )来表示Python代码和" normal"内联代码(like this)表示RepoSurgeon命令。对于代码块,介绍性描述应该提供上下文,即它是RepoSurgeon命令还是Python代码。

本文将讨论RepoSurgeon的3.10版本,这是本文撰写时的最新版本。

简介

RepoSurgeon是用Python编写的,并明确允许 execfile() 中的其他Python代码。 RepoSurgeon命令的语法是:

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

我们可以从文档中收集到这些内容。

我们可以在电梯脚本或RepoSurgeon提示中使用它。

您的代码最终会在哪里?

正如已经指出in this Q&A,你需要在RepoSurgeon的环境中运行时遵守周围代码强加的规则。特别是你的Python代码将在 __main__.RepoSurgeon 实例的上下文中执行,所以这是首先要记住的。

您还必须使用eval进行选择。不给予任何选择并期望隐含的所有选择&#34;它似乎是合法的。至于list或其他内置命令,尽管您可以利用exec来改变这种行为,我们会稍微看一下。

另请确保使用eval myfunc()而非eval myfunc。显然 myfunc 是一个有效的Python语句,但不要指望它做任何事情。您必须调用该功能。 eval之后的所有内容都直接发送到Python eval()

execfile() exec作为RepoSurgeon)运行时,您可以滥用您正在运行的内容并引用 self < / em>,这是上面提到的 __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处使用Python代码文件时在顶层)。

只需将此行添加到 myfunc

即可
dir(self)

制作:

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

再次调用execeval命令后(在Linux上调用它就像在shell中使用游标 Up 那样),您现在应该看到列出的大多数函数您也可以找到RepoSurgeon代码。

注意:只需重新运行RepoSurgeon的exec命令后跟另一个eval myfunc(),现在将添加 {{1}属性的输出}}

虽然到目前为止所有这些都很酷,并且应该让您对如何在RepoSurgeon中运行自己的Python代码有所了解,但您也可以替换现有 __main__.RepoSurgeon 方法。请继续阅读。

陷入RepoSurgeon并取代功能

通过访问 __main__.RepoSurgeon ,可以添加功能来修改现有功能。

self 看起来像是一个有价值的候选人。它是在运行实际命令之前调用的方法,并执行语法检查以及设置在许多RepoSurgeon命令中非常重要的选择集。

我们需要的是 RepoSurgeon.precmd 的原型。这是:

precmd

替换方法又有什么诀窍? Alex Martelli's answer here引领潮流......

我们可以简单地将其用作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__)) 仅用于我们的代码范围。
  • 检查&quot; orig_precmd&#39;确保我们在后续调用if self:时不会覆盖此属性的值
  • exec 包含我们的 myprecmd(self, line): 版本。它添加的令人敬畏的新功能是parrot输入的命令。
  • 最后但并非最不重要的是,第二个 __main__.RepoSurgeon.precmd 只是用我们的版本覆盖 setattr() 。 RepoSurgeon对 __main__.RepoSurgeon.precmd 所做的任何后续调用都将通过我们的&#34; hook&#34;现在

请记住,我们凌驾于RepoSurgeon的内部代码之上,所以要小心谨慎,不要做任何愚蠢的事情。代码非常易读,尽管是10k LoC。

下次你发出任何命令时,你应该把它鹦鹉学舌。请考虑以下内容(RepoSurgeon提示以及输出摘录):

self.precmd()

reposurgeon% =O list [pre-precmd] '=O list' 是我输入的命令,=O list输出的输出(后跟实际输出,因为我调用了 [pre-precmd] '=O list'的原始实现在我的版本中。)

结论

RepoSurgeon命令__main__.RepoSurgeon.precmdexec提供了一种强大的方法来覆盖现有功能并在RepoSurgeon中添加新功能。

钩子示例是&#34;简单&#34;的超集。使用eval使用之前的eval&d;功能扩展RepoSurgeon。它允许将代码潜入RepoSurgeon的内部,并将其弯曲到我们的遗嘱,因为它有缺点(到目前为止我只发现了一些)。

对此设计决定感谢ESR。这种方式不需要插件框架。