def _hoist_variables_to_piglet_context(astnode, exclude_functions=frozenset()):
    """
    Template functions extract all local variables from the
    :var:`piglet.runtime.data` thread local
    """

    # Names we never hoist.
    # "None" would raise a SyntaxError if we try assigning to it. The others
    # are used internally by piglet and need to be reserved.
    restricted_names = {'None', 'True', 'False',
                        'value_of', 'defined', 'Markup', 'iter',
                        'AttributeError', 'Exception',
                        'print', 'getattr'}

    # Mapping of function -> names used within function
    func_names = {}

    # Mapping of {function name: {<ancestor nodes>, ...}}
    func_ancestors = {}

    # All names discovered together with their ast location
    names = []

    for fn, ancestors in _get_function_defs(astnode):
        func_ancestors.setdefault(fn.name, set()).add(ancestors)
        func_names[fn] = set()

    is_reserved = lambda n: n.id in restricted_names
    is_piglet = lambda n: n.id.startswith('__piglet')
    is_function = lambda n, a: any(
        a[:len(fancestors)] == fancestors
        for fancestors in func_ancestors.get(n.id, set()))

    names = ((ancestors, node)
             for node, ancestors in _get_out_of_scope_names(astnode)
             if not (is_reserved(node) or
                     is_piglet(node) or
                     is_function(node, ancestors)))

    for ancestors, node in names:
        container_func = next((a
                               for a in reversed(ancestors)
                               if isinstance(a, FunctionDef)), None)
        if container_func in exclude_functions:
            continue
        if container_func is None:
            lineno = getattr(node, 'lineno', '(unknown line number)')
            raise AssertionError("Unexpected variable found at {}: {}"
                                 .format(lineno, node.id))
        func_names[container_func].add(node.id)

    for f, names in func_names.items():
        assignments = [
            Assign(targets=[StoreName('__piglet_ctx')],
                   value=Subscript(
                       value=LoadAttribute('__piglet_rtdata.context'),
                       slice=Index(value=Num(n=-1)),
                       ctx=Load()
                   ))
        ]

        for n in sorted(names):
            is_builtin = n in builtin_names
            if is_builtin:
                default = LoadAttribute(
                    LoadAttribute('__piglet_rt', 'builtins'), n)
            else:
                default = Call(func=Attribute(value=Name(id='__piglet_rt',
                                                         ctx=Load()),
                                              attr='Undefined',
                                              ctx=Load()),
                               args=[Str(s=n)],
                               starargs=None,
                               kwargs=None,
                               keywords=[])

            value = Call(func=Attribute(value=Name(id='__piglet_ctx',
                                                    ctx=Load()),
                                        attr='get',
                                        ctx=Load()),
                         args=[Str(s=n), default],
                         starargs=None,
                         kwargs=None,
                         keywords=[])

            a = Assign(targets=[Name(id=n, ctx=Store())], value=value)
            assignments.append(a)

        f.body[0:0] = assignments
    return astnode