Gestionnaire de contexte qui ne s'exécute pas

Ah oui en effet, c’est moi qui n’ai pas assez spécifié le problème.
L’idée c’est qu’un bloc soit réutilisable (donc appelable zéro ou plusieurs fois), de façon à avoir quelque chose comme :

>>> def range_each(start, stop, block):
...     for i in range(start, stop):
...         block(i)
...
... with Block.call(range_each, 0, 10) as (i,):
...     print(i, '!')
...
... with Block.call(range_each, 0, 0) as (i,):
...     print(i, '?')
...
0 !
1 !
2 !
3 !
4 !
5 !
6 !
7 !
8 !
9 !

Actuellement j’arrive donc à m’en sortir avec le code bien sale suivant (sur lequel il faut encore que je travaille, notamment pour être compatible avec l’interpréteur interactif) :

À vos risques et périls
import ast
import inspect
import itertools
import textwrap
from functools import partial


class _Stop(Exception):
    pass


class _BlockArguments:
    def __iter__(self):
        raise _Stop


class _Block:
    def __init__(self, exit_callable=None):
        self.exit_callable = exit_callable

    def __enter__(self):
        caller_frame_info = inspect.stack()[1]

        for module_frame_info in inspect.stack()[1:]:
            if module_frame_info.function == '<module>':
                break

        source = inspect.getsource(module_frame_info.frame)
        source_lines = source.splitlines()

        first_line = caller_frame_info.lineno
        block_lines = []

        (block_def,) = ast.parse(source_lines[first_line - 1].strip() + ' pass').body
        assert isinstance(block_def, ast.With)
        (block_item,) = block_def.items
        block_args = block_item.optional_vars
        assert isinstance(block_args, ast.Tuple)
        self.block_arg_names = [name.id for name in block_args.elts]

        for i in itertools.count(first_line):
            try:
                line = source_lines[i]
            except IndexError:
                break
            code = textwrap.dedent('\n'.join(block_lines + [line]))
            if code.startswith(' '):
                break
            block_lines.append(line)

        block_source = textwrap.dedent('\n'.join(block_lines))

        self._code = compile(block_source, '<block>', 'exec')

        return _BlockArguments()

    def __exit__(self, exc_type, exc_val, exc_tb):
        ret = exc_type and issubclass(exc_type, _Stop)
        if ret and self.exit_callable is not None:
            self.exit_callable(self)
        return ret

    def __call__(self, *args, **kwargs):
        loc = locals() | kwargs | dict(zip(self.block_arg_names, args))
        exec(self._code, locals=loc)


class Block(_Block):
    def __init__(self):
        super().__init__()

    @staticmethod
    def call(func, /, *args, **kwargs):
        return _Block(partial(func, *args, **kwargs))


def range_each(start, stop, block):
    for i in range(start, stop):
        block(i)


with Block.call(range_each, 0, 10) as (i,):
    print(i, '!')


with Block.call(range_each, 0, 0) as (i,):
    print(i, '?')


with (block1 := Block()) as ():
    print('block1 - foo')
    print('block1 - bar')


def get_block():
    with (block := Block()) as ():
        print('block2 - foo')
        print('block2 - bar')
    return block


block2 = get_block()

print('tests')
block2()
block1()
block2()