我正在使用Ruslan Pavik's Guide在C#中构建自己的解释器。我在第7部分,我们正在创建一个抽象语法树。我能够通过第一部分获得并能够将指南中的Python代码翻译成C#。但是在第7部分中,对于没有Python经验的人来说变得非常困难。
我很难弄清楚数据类型和返回类型是什么,Python是一种动态编程语言。
我对这部分感到困惑:
def visit(self, node):
method_name = 'visit_' + type(node).__name__
visitor = getattr(self, method_name, self.generic_visit)
return visitor(node)
visitor
的数据类型,但我认为它是一个字符串。visitor
被分配getattr
,但我不知道它试图从哪个对象获取属性。node
的数据类型是什么。后来在指南中,他宣布了这堂课:
class AST(object):
pass
但是我知道在C#中它只是一个在大括号内没有任何内容的类:public class AST { }
。
AST
之后的下一课是继承BinOp
的{{1}}:
AST
基本上我现在真的很困惑。但我的主要困惑是class BinOp(AST):
def __init__(self, left, op, right):
self.left = left
self.token = self.op = op
self.right = right
,因为我不知道它有什么数据类型。由于解析器和解释器中的很多类都使用node
,因此在不知道它具有什么数据类型的情况下,我真的不能继续使用我的解释器。
答案 0 :(得分:0)
node
是AST
的子类的实例。 visitor
是NodeVisitor
类的子类(您应该自己编写)的方法。它被动态查找 ,因为传入的node
将是AST
的任何一个可能的子类,并且您应该实现特定的visit_
自定义子类上特定节点的方法。后备是使用self.generic_visit
方法。
AST
是节点的基类。 BinOp
是特定的节点类;不同的班级是documented in the Abstract Grammar section;每个camel-cased名称也是AST
子类。
通过特定节点的名称动态查找方法,开发人员避免了为语法定义的每个节点类型创建特定的具体方法。
为了使这更具体一点:说你对特定的BinOp
运算符节点感兴趣;也许是因为你想分析一段代码中+
的使用方式。
然后,您可以实现NodeVisitor
子类,并在该子类上添加visit_BinOp()
方法,并在传入节点的树时自动调用它到NodeVisitorSubclass().visit(toplevel_node)
;对于没有特定visit_*
方法的任何节点,NodeVisitor.generic_visit()
方法将确保访问树的子节点(通过iter_fields()
函数)。
演示:
>>> import ast
>>> class DemoVisitor(ast.NodeVisitor):
... def visit_BinOp(self, binop_node):
... if isinstance(binop_node.op, ast.Add):
... print('Found addition of {} and {}'.format(
... ast.dump(binop_node.left), ast.dump(binop_node.right)))
...
>>> tree = ast.parse('function(foo + bar)', '', 'eval')
>>> ast.dump(tree)
"Expression(body=Call(func=Name(id='function', ctx=Load()), args=[BinOp(left=Name(id='foo', ctx=Load()), op=Add(), right=Name(id='bar', ctx=Load()))], keywords=[]))"
>>> DemoVisitor().visit(tree)
Found addition of Name(id='foo', ctx=Load()) and Name(id='bar', ctx=Load())
在上面的演示中,NodeWalker.visit()
方法找到了visit_BinOp
方法并为我们调用了该方法,但由于没有visit_Expression
或visit_Call
等方法,这些节点被传递给NodeWalker.generic_visit()
方法,它处理每个字段并为任何其他节点调用self.visit()
。
答案 1 :(得分:0)
关于getattr
的问题:这只是一个函数,它返回一个具有给定名称(在第二个参数中)的对象(在第一个参数中)和一个后退(在第三个参数中)的属性参数)如果没有属性。
因此,在您的情况method_name
中,它会尝试从自身获取名称为self.generic_visit
的属性。如果它不存在则使用method_name
。现在,如果"visit_BinOp"
为self.visit_BinOp
,则会尝试获取self.generic_visit
,如果不存在,则转而获取visitor
。
最后,在不运行代码的情况下,无法确定它将返回什么或i = 42
# i is now an int, but you can easily overwrite it with a string
i = "Hello World"
将是什么类型。它可以是理论上的任何东西,但通常它是一种方法。但这与Python和C#不同,Python中变量的类型取决于分配给它的内容。您可以毫无问题地执行以下操作:
node
同样适用于AST
,理论上它可以是任何东西,但如果使用正确,它将成为foo = [1, 1, 1, 5, 5, 5, 10, 10, 10, 10, 10, 10, 8, 8, 8, 8, 8, 8]
print foo[::3]
的子类。