[Python] How to programmatically use the result of an expression as argument to a `Name` node?

Discussão em 'Python' iniciado por Stack, Abril 4, 2025.

  1. Stack

    Stack Membro Participativo

    In the process of developing a custom Jinja2 extension that creates namespaces with dynamically evaluated names, I need to use the result of the evaluation of a template expression as argument to a Name node. All of my attempts so far have failed.

    Follows a sample extension (not the actual, more complex, extension that I'm working on) that captures the essence of what I'm trying to accomplish:

    {% set my_name = 'my_namespace' %}
    {% namespace my_name %}
    {# A namespace named 'my_namespace' should now exists #}


    Of course, I can easily parse the name expression and have the result of its evaluation displayed with an Output node. I can also create namespaces with predefined literal names:

    from jinja2 import nodes
    from jinja2.ext import Extension

    class NamespaceExtension(Extension):

    tags = {"namespace"}

    def __init__(self, environment):
    super().__init__(environment)

    def parse(self, parser):

    lineno = next(parser.stream).lineno
    eval_context = nodes.EvalContext(self.environment)
    name = parser.parse_expression()

    ##
    ## This obviously works as expected and outputs the result
    ## of evaluating `name` as an expression.
    ##

    return nodes.Output([name]).set_lineno(lineno)

    ##
    ## This too works, as a proof of concept, but we need the
    ## namespace name to be evaluated dynamically.
    ##

    # return nodes.Assign(
    # nodes.Name('my_namespace', 'store'),
    # nodes.Call(nodes.Name('namespace', 'load'), [], [], None, None)
    # ).set_lineno(lineno)

    namespace = NamespaceExtension


    The following attempts, however, don't work (I actually only had hope in the first-to-last attempt, so not too surprised regarding most of the others, but here they are, if only for the sake of completeness and demonstration).

    return nodes.Assign(
    name,
    nodes.Call(nodes.Name('namespace', 'load'), [], [], None, None)
    ).set_lineno(lineno)

    # SyntaxError: cannot assign to function call


    Right, we need the expression's value.

    return nodes.Assign(
    name.as_const(),
    nodes.Call(nodes.Name('namespace', 'load'), [], [], None, None)
    ).set_lineno(lineno)

    # RuntimeError: if no eval context is passed, the node must have an attached environment.


    Well, ok, easy enough.

    return nodes.Assign(
    name.as_const(eval_context),
    nodes.Call(nodes.Name('namespace', 'load'), [], [], None, None)
    ).set_lineno(lineno)

    # jinja2.nodes.Impossible


    Might have better luck attaching the environment.

    name.set_environment(self.environment)
    return nodes.Assign(
    name.as_const(),
    nodes.Call(nodes.Name('namespace', 'load'), [], [], None, None)
    ).set_lineno(lineno)

    # jinja2.nodes.Impossible


    Of course, expressions are parsed by default with a 'load' context and we need a Name node with a 'store' context.

    name.set_ctx('store')
    return nodes.Assign(
    name,
    nodes.Call(nodes.Name('namespace', 'load'), [], [], None, None)
    ).set_lineno(lineno)

    # SyntaxError: cannot assign to function call


    How about wrapping the name expression in 'load' context inside of a Name node with a 'store' context?

    return nodes.Assign(
    nodes.Name(name, 'store'),
    nodes.Call(nodes.Name('namespace', 'load'), [], [], None, None)
    ).set_lineno(lineno)

    # TypeError: '<' not supported between instances of 'Getattr' and 'str'


    The Name node's first argument must be a string, so then how about evaluating name as a constant?

    return nodes.Assign(
    nodes.Name(name.as_const(), 'store'),
    nodes.Call(nodes.Name('namespace', 'load'), [], [], None, None)
    ).set_lineno(lineno)

    # jinja2.nodes.Impossible


    Or perhaps this? Out of sheer despair...

    return nodes.Assign(
    nodes.Name(nodes.Const(name.as_const()), 'store'),
    nodes.Call(nodes.Name('namespace', 'load'), [], [], None, None)
    ).set_lineno(lineno)

    #TypeError: '<' not supported between instances of 'str' and 'Getattr'


    The problem is, the AST results from static evaluation, while the expression requires runtime evaluation (because expressions can contain variables whose value is not known at parse time). But the Name node's first parameter only accepts a string argument, not a node.

    Is there a way to bridge that gap?

    Continue reading...

Compartilhe esta Página