Un gestionnaire de contexte libéré, (délivré, …)

@entwanne, @debnet et moi jouons avec les gestionnaires de contextes ces temps-ci, ça m’a permis de faire ça par exemple : mdk/forking - La forge de l'AFPy

Le jeu du jour et d’aller plus loin dans la libération des gestionnaires de contextes, et je commence à toucher quelque chose, probablement encore un peu fragile, à peine testé, mais ça commence à marcher :

import ast
import inspect
import sys
import textwrap


class Stop(Exception):
    """Internal exception to block the execution of the context body."""


class With:
    def tracer_cb(self, frame, event, arg):
        if frame is self.frame and event == "opcode":
            raise Stop

    def __enter__(self):
        current_frame = inspect.currentframe()
        self.frame = current_frame.f_back
        self.frame.f_trace = self.tracer_cb
        self.frame.f_trace_opcodes = True
        sys.settrace(self.tracer_cb)

    def execute(self, body):
        """Do whatever with the body of the with statement."""
        raise NotImplementedError

    def _locate_with_body(self, tree, lineno):
        """Locate the with statement, in tree, at lineno."""
        for node in ast.walk(tree):
            try:
                node_lineno = node.lineno
            except AttributeError:
                continue
            if node_lineno == lineno:
                return node.body

    def __exit__(self, exc_type, exc, tb):
        frame = tb.tb_frame
        src = textwrap.dedent(inspect.getsource(tb.tb_frame))
        tree = ast.parse(src)
        module = ast.Module(body=self._locate_with_body(tree, tb.tb_frame.f_lineno))
        compiled = compile(module, filename="<with>", mode="exec")
        Function = type(Repeat.__enter__)
        self.execute(Function(compiled, frame.f_globals, name="<with>"))
        return True


class Repeat(With):
    def __init__(self, times):
        self.times = times

    def execute(self, body):
        for _ in range(self.times):
            body()



with Repeat(4):
    print(42)
1 « J'aime »

Ah bah @entwanne en parlait justement aujourd’hui sur IRC.

Bonjour,

Ça a l’air intéressant mais pourriez-vous expliquer de quelle libération il s’agit ? Plus globalement, qu’est-ce que votre innovation permet de faire (ou cherche encore à permettre de faire) que l’API Python de base ne permet pas ? Merci

Entre autre, ça permet d’exécuter le corps du with en fonction d’une condition, donc peut-être de ne pas l’exécuter du tout (utile ici : mdk/forking - La forge de l'AFPy), ou de l’exécuter plusieurs fois (utile dans un projet de @entwanne) comme dans mon exemple avec Repeat.

Le corps du with étant encapsulé dans une fonction on peut imaginer d’autres cas d’usages comme un with Retry àla tenacity, qui réexécute le corps du with tant qu’il lève une exception.

On peut imaginer aussi un genre de timeit qui exécute le bloc du with plein de fois pour en mesurer les perfs.

Et probablement pas mal d’autres possibilités, moi je me suis arrêté à l’utiliser pour mdk/forking - La forge de l'AFPy et @entwanne j’ignore s’il l’a finalement utilisé.

1 « J'aime »

Non moi c’était plus pour jouer qu’autre chose, et mon cas de Repeat est effectivement équivalent à un retry de tenacity.
Actuellement je m’en sors avec un

for ctx in repeat_if_needed(sleep_time=0.1):
    with ctx:
        # bloc qui peut lever une exception
        # et qui ne s'arrêtera qu'avec un break ou return explicite
        pass

faute de mieux.

1 « J'aime »