如何用Python编写抽象语法树的访问者模式?
我的同事建议我编写一个访问者模式来导航 AST。谁能告诉我更多如何开始写它?
据我了解,AST 中的每个节点都会有 visit()
方法(?),该方法会以某种方式被调用(从哪里?)。我的理解就这样结束了。
为了简化一切,假设我有节点 Root
、Expression
、Number
、Op
并且树如下所示:
Root
|
Op(+)
/ \
/ \
Number(5) \
Op(*)
/ \
/ \
/ \
Number(2) Number(444)
谁能想到访问者模式将如何访问这棵树来产生输出:
5 + 2 * 444
谢谢,Boda Cydo。
My collegue suggested me to write a visitor pattern to navigate the AST. Can anyone tell me more how would I start writing it?
As far as I understand, each Node in AST would have visit()
method (?) that would somehow get called (from where?). That about concludes my understanding.
To simplify everything, suppose I have nodes Root
, Expression
, Number
, Op
and the tree looks like this:
Root
|
Op(+)
/ \
/ \
Number(5) \
Op(*)
/ \
/ \
/ \
Number(2) Number(444)
Can anyone think of how the visitor pattern would visit this tree to produce output:
5 + 2 * 444
Thanks, Boda Cydo.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
维基百科对访客模式如何工作有一个很好的概述,尽管他们使用的示例实现是在Java中。不过,您可以轻松地将其移植到 Python,不是吗?
基本上,您想要实现双重调度的机制。 AST 中的每个节点都需要实现一个
accept()
方法(而不是visit()
方法)。该方法采用访问者对象作为参数。在此accept()
方法的实现中,您调用访问者对象的visit()
方法(每种 AST 节点类型都会有一个方法;在 Java 中,您可以将使用参数重载,在 Python 中我想你可以使用不同的visit_*()
方法)。然后,将使用正确的节点类型作为参数来分派正确的访问者。Wikipedia has a great overview of how the Visitor pattern works, although the sample implementation that they use is in Java. You can easily port that to Python, though, no?
Basically, you want to implement a mechanism for double dispatch. Each node in your AST would need to implement an
accept()
method (NOT avisit()
method). The method takes, as an argument, a visitor object. In the implementation of thisaccept()
method, you call avisit()
method of the visitor object (there will be one for each AST node type; in Java, you'll use parameter overloading, in Python I suppose you can use differentvisit_*()
methods). The correct visitor will then be dispatched with the correct Node type as argument.请参阅 文档 了解
ast.NodeVisitor< /code>,例如,一种粗略的可能性可能是:
当然,即使在需要的地方,这也不会发出括号等,所以实际上还有更多的工作要做,但是,这是一个开始;-)。
See the docs for
ast.NodeVisitor
, e.g. a crude possibility might be:of course this doesn't emit parentheses even where needed, etc, so there's actually more work done, but, it's a start;-).
我在互联网上最常遇到的用 Python 实现访问者模式的两种变体:
设计模式书籍中的翻译示例
此变体使用数据结构类中的
accept()
方法以及访问者中相应的visit_Type()
方法。数据结构
Infix Print Visitor
Prefix Print Visitor
测试
输出
使用附加模块
这变体使用
@functools.singledispatch()
装饰器(自 Python v3.4 起在 Python 标准库中可用)。数据结构
Infix Print Visitor
Prefix Print Visitor
测试
输出
我喜欢的原因这个变体是它消除了accept()方法并将数据结构与访问者中实现的操作完全分离。使用新元素扩展数据结构不需要更改访问者。默认情况下,访问者会忽略未知的元素类型(请参阅带有
pass
关键字的定义)。此方法的缺点是singledispatch
装饰器不能直接与实例方法一起使用,尽管 有多种方法可以使其发挥作用。 注意:从Python 3.8开始functools .singledispatchmethod()
提供与 functools.singledispatch() 相同的功能,但用于实例方法。对于 v3.4 之前的 Python multimethods 模块可以像 singledispatch 装饰器一样使用。多方法模块的一个缺点是,应用于给定数据结构元素的访问者方法的选择不仅基于元素的类型,而且还基于声明方法的顺序。对于具有复杂继承层次结构的数据结构来说,以正确的顺序保持方法定义可能很麻烦并且容易出错。
The two variants to implement the Visitor pattern in Python that I encountered on the Internet most often:
Translated Example from the Design Patterns Book
This variant uses
accept()
methods in the data structure classes and correspondingvisit_Type()
methods in the visitors.The data structure
Infix Print Visitor
Prefix Print Visitor
Test
Output
Using additional modules
This variant uses
@functools.singledispatch()
decorator (available in the Python Standard Library since Python v3.4).The data structure
Infix Print Visitor
Prefix Print Visitor
Test
Output
The reason I prefer this variant is that it eliminates the
accept()
methods and completely separates the data structure from the operations implemented in the visitors. Extending the data structure with new elements does not require changing the visitors. The visitors ignore the unknown element types by default (see the definitions with thepass
keyword). A drawback of this method is thatsingledispatch
decorator cannot be used with instance methods directly, although, there are ways to make it work. Note: Starting with Python 3.8functools.singledispatchmethod()
provides the same functionality asfunctools.singledispatch()
but for instance methods.For Python before v3.4 multimethods module can be used similar to the singledispatch decorator. One drawback of the multimethods module is that the visitor method that is applied to a given data-structure element is selected not only based on the element's type but also on the order in which the methods are declared. Keeping the method definitions in the right order can be cumbersome and error prone for data structures with a complex inheritance hierarchy.