web-dev-qa-db-ja.com

Pythonで抽象構文木のビジターパターンを書く方法は?

私の同僚は、ASTをナビゲートするためのビジターパターンを書くことを提案しました。誰かが私にそれを書き始める方法をもっと教えてもらえますか?

私が理解している限り、各Node in ASTにはvisit()メソッド(?)があり、それはどういうわけか(どこから? )これで私の理解は終わりです。

すべてを単純化するために、ノードRootExpressionNumberOpがあり、ツリーは次のようになっているとします。

       Root
        |
       Op(+)
      /   \
     /     \
 Number(5)  \
             Op(*)
             /   \
            /     \
           /       \
       Number(2)   Number(444)

誰もがビジターパターンがこのツリーを訪問して出力を生成する方法を考えることができますか?

 5 + 2 * 444

ありがとう、BodaCydo。

27
bodacydo

ウィキペディアには ビジターパターンのしくみ の概要がありますが、使用するサンプル実装はJavaです。それをPythonに簡単に移植できますが、違いますか?

基本的には、 ダブルディスパッチ のメカニズムを実装する必要があります。 ASTの各ノードは、accept()メソッドを実装する必要があります(visit()メソッドではありません)。このメソッドは、引数としてビジターオブジェクトを取ります。このaccept()メソッドの実装では、ビジターオブジェクトのvisit()メソッドを呼び出します(ASTノードタイプごとに1つあります。Javaでは、 Pythonではパラメータのオーバーロードを使用します。さまざまなvisit_*()メソッドを使用できると思います)。正しい訪問者は、引数として正しいNode型を使用してディスパッチされます。

10
Santa

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,

もちろん、これは必要な場合などでも括弧を出力しないので、実際にはさらに多くの作業が行われますが、それは始まりです;-)。

11
Alex Martelli

インターネットで最も頻繁に遭遇したPython)でVisitorパターンを実装するための2つのバリアント:

  • GammaらによるDesighPatternsの本からの例の1対1の翻訳。
  • ダブルディスパッチに追加モジュールを使用する

Desigh PatternsBookからの翻訳例

このバリアントは、データ構造クラスで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要素は、要素のタイプだけでなく、メソッドが宣言される順序にも基づいて選択されます。メソッド定義を正しい順序に保つことは、複雑な継承階層を持つデータ構造では面倒でエラーが発生しやすい場合があります。

4