私の同僚は、ASTをナビゲートするためのビジターパターンを書くことを提案しました。誰かが私にそれを書き始める方法をもっと教えてもらえますか?
私が理解している限り、各Node in ASTにはvisit()
メソッド(?)があり、それはどういうわけか(どこから? )これで私の理解は終わりです。
すべてを単純化するために、ノードRoot
、Expression
、Number
、Op
があり、ツリーは次のようになっているとします。
Root
|
Op(+)
/ \
/ \
Number(5) \
Op(*)
/ \
/ \
/ \
Number(2) Number(444)
誰もがビジターパターンがこのツリーを訪問して出力を生成する方法を考えることができますか?
5 + 2 * 444
ありがとう、BodaCydo。
ウィキペディアには ビジターパターンのしくみ の概要がありますが、使用するサンプル実装はJavaです。それをPythonに簡単に移植できますが、違いますか?
基本的には、 ダブルディスパッチ のメカニズムを実装する必要があります。 ASTの各ノードは、accept()
メソッドを実装する必要があります(visit()
メソッドではありません)。このメソッドは、引数としてビジターオブジェクトを取ります。このaccept()
メソッドの実装では、ビジターオブジェクトのvisit()
メソッドを呼び出します(ASTノードタイプごとに1つあります。Javaでは、 Pythonではパラメータのオーバーロードを使用します。さまざまなvisit_*()
メソッドを使用できると思います)。正しい訪問者は、引数として正しいNode型を使用してディスパッチされます。
ast.NodeVisitor
については ドキュメント を参照してください。例:大まかな可能性は次のとおりです。
import ast
class MyVisitor(ast.NodeVisitor):
def visit_BinaryOp(self, node):
self.visit(node.left)
print node.op,
self.visit(node.right)
def visit_Num(self, node):
print node.n,
もちろん、これは必要な場合などでも括弧を出力しないので、実際にはさらに多くの作業が行われますが、それは始まりです;-)。
インターネットで最も頻繁に遭遇したPython)でVisitorパターンを実装するための2つのバリアント:
このバリアントは、データ構造クラスでaccept()
メソッドを使用し、訪問者で対応するvisit_Type()
メソッドを使用します。
データ構造
_class Operation(object):
def __init__(self, op, arg1, arg2):
self.op = op
self.arg1 = arg1
self.arg2 = arg2
def accept(self, visitor):
visitor.visitOperation(self)
class Integer(object):
def __init__(self, num):
self.num = num
def accept(self, visitor):
visitor.visitInteger(self)
class Float(object):
def __init__(self, num):
self.num = num
def accept(self, visitor):
visitor.visitFloat(self)
expression = Operation('+', Integer('5'),
Operation('*', Integer('2'), Float('444.1')))
_
中置印刷ビジター
_class InfixPrintVisitor(object):
def __init__(self):
self.expression_string = ''
def visitOperation(self, operation):
operation.arg1.accept(self)
self.expression_string += ' ' + operation.op + ' '
operation.arg2.accept(self)
def visitInteger(self, number):
self.expression_string += number.num
def visitFloat(self, number):
self.expression_string += number.num
_
プレフィックス印刷ビジター
_class PrefixPrintVisitor(object):
def __init__(self):
self.expression_string = ''
def visitOperation(self, operation):
self.expression_string += operation.op + ' '
operation.arg1.accept(self)
self.expression_string += ' '
operation.arg2.accept(self)
def visitInteger(self, number):
self.expression_string += number.num
def visitFloat(self, number):
self.expression_string += number.num
_
テスト
_infixPrintVisitor = InfixPrintVisitor()
expression.accept(infixPrintVisitor)
print(infixPrintVisitor.expression_string)
prefixPrintVisitor = PrefixPrintVisitor()
expression.accept(prefixPrintVisitor)
print(prefixPrintVisitor.expression_string)
_
出力
_5 + 2 * 444.1
+ 5 * 2 444.1
_
このバリアントは @functools.singledispatch()
デコレータを使用します(Python v3.4以降、Python標準ライブラリで利用可能)。
データ構造
_class Operation(object):
def __init__(self, op, arg1, arg2):
self.op = op
self.arg1 = arg1
self.arg2 = arg2
class Integer(object):
def __init__(self, num):
self.num = num
class Float(object):
def __init__(self, num):
self.num = num
expression = Operation('+', Integer('5'),
Operation('*', Integer('2'), Float('444.1')))
_
中置印刷ビジター
_from functools import singledispatch
@singledispatch
def visitor_print_infix(obj):
pass
@visitor_print_infix.register(Operation)
def __(operation):
return visitor_print_infix(operation.arg1) + ' ' \
+ operation.op + ' ' \
+ visitor_print_infix(operation.arg2)
@visitor_print_infix.register(Integer)
@visitor_print_infix.register(Float)
def __(number):
return number.num
_
プレフィックス印刷ビジター
_from functools import singledispatch
@singledispatch
def visitor_print_prefix(obj):
pass
@visitor_print_prefix.register(Operation)
def __(operation):
return operation.op + ' ' \
+ visitor_print_prefix(operation.arg1) + ' ' \
+ visitor_print_prefix(operation.arg2)
@visitor_print_prefix.register(Integer)
@visitor_print_prefix.register(Float)
def __(number):
return number.num
_
テスト
_print(visitor_print_infix(expression))
print(visitor_print_prefix(expression))
_
出力
_5 + 2 * 444.1
+ 5 * 2 444.1
_
私がこのバリアントを好む理由は、accept()
メソッドを排除し、訪問者に実装された操作からデータ構造を完全に分離するためです。新しい要素でデータ構造を拡張する場合、訪問者を変更する必要はありません。訪問者はデフォルトで不明な要素タイプを無視します(pass
キーワードの定義を参照してください)。このメソッドの欠点は、singledispatch
デコレータをインスタンスメソッドで直接使用できないことですが、 それを機能させる方法はあります 。
For Python v3.4より前 multimethods モジュールはシングルディスパッチデコレータと同様に使用できます。multimethodsモジュールの欠点の1つは、特定のビジターメソッドが適用されることです。 data-structure要素は、要素のタイプだけでなく、メソッドが宣言される順序にも基づいて選択されます。メソッド定義を正しい順序に保つことは、複雑な継承階層を持つデータ構造では面倒でエラーが発生しやすい場合があります。