Python AST to dictionary structure
<p>I am using the Python <code>ast</code> module to parse expressions like <code>X &gt;= 13 and Y == W</code>. What I need is to transform this expression in a pre-order dictionary found below:</p>

<pre><code>{
  "function": "and",
  "args": [
    {
      "function": "&gt;=",
      "args": [
        {
          "variable": "X"
        },
        {
          "value": 13
        }
      ]
    },
    {
      "function": "==",
      "args": [
        {
          "variable": "Y"
        },
        {
          "variable": "W"
        }
      ]
    }
  ]
}
</code></pre>

<p>Python AST gives me the benefit of validating syntax and differencing variables from values. The following code does a basic parsing but notice the output is not pre-order and I'm not entirely sure how to procede:</p>

<pre><code>import ast
class ExclusionParser(ast.NodeVisitor):
    tree = ''

    def append(self, expr):
        self.tree += expr

    def visit_And(self, node):
        self.append("and ")

    def visit_Lt(self, node):
        self.append("&lt; ")

    def visit_Gt(self, node):
        self.append("&gt; ")

    def visit_Eq(self, node):
        self.append("== ")

    def visit_Num(self, node):
        self.append("value: %s " % node.n)

    def visit_Name(self, node):
        self.append("variable: %s " % node.id)

    def generic_visit(self, node):
        """Called if no explicit visitor function exists for a node."""
        self.append("(")
        for field, value in ast.iter_fields(node):
            if isinstance(value, list):
                for item in value:
                    if isinstance(item, ast.AST):
                        self.visit(item)
            elif isinstance(value, ast.AST):
                self.visit(value)
        self.append(")")

if __name__ == '__main__':
    v = ExclusionParser()
    p = ast.parse("X &gt; 13 and Y == W")
    v.visit(p)
    print(v.tree)
</code></pre>

<p><strong>output</strong></p>

<pre><code>(((and (variable: X &gt; value: 13 )(variable: Y == variable: W ))))
</code></pre>

<p>Consider these small changes - I've added <code>'</code>s and commas:</p>

<pre><code>import ast
class ExclusionParser(ast.NodeVisitor):
    tree = ''

    def append(self, expr):
        self.tree += expr

    def visit_And(self, node):
        self.append("'func:and', ")

    def visit_Lt(self, node):
        self.append("'func:lt', ")

    def visit_Gt(self, node):
        self.append("'func:gt', ")

    def visit_Eq(self, node):
        self.append("'func:eq', ")

    def visit_Num(self, node):
        self.append("'value:%s', " % node.n)

    def visit_Name(self, node):
        self.append("'variable:%s', " % node.id)

    def generic_visit(self, node):
        """Called if no explicit visitor function exists for a node."""
        self.append("[")
        for field, value in ast.iter_fields(node):
            if isinstance(value, list):
                for item in value:
                    if isinstance(item, ast.AST):
                        self.visit(item)
            elif isinstance(value, ast.AST):
                self.visit(value)
        self.append("], ")


if __name__ == '__main__':
    v = ExclusionParser()
    p = ast.parse("X &gt; 13 and Y == W")
    v.visit(p)
    tree = ast.literal_eval(v.tree[:-2])
    print(tree)
</code></pre>

<p>At this point <code>tree</code> is a valid list:</p>

<pre><code>[[['func:and', ['variable:X', 'func:gt', 'value:13'], ['variable:Y', 'func:eq', 'variable:W']]]]
</code></pre>

<p>If you've written <code>generic_visit</code> you shouldn't face any difficulties in writing another recursive function that will convert this list of lists into a dictionary. The solution is a bit hacky. (And by "a bit" I mean it's really hacky.) The proper solution would be to modify <code>generic_visit</code>, I suppose.</p>

<p>And on a separate note: dictionaries don't preserve the order of theirs elements. Use <code>OrderedDict</code> to ensure <code>function</code> key goes first.</p>

python json abstract-syntax-tree