2013-06-04 10 views
21

Ich bin auf der Suche nach Logik für Datenbanktransaktionen in einen with Block zu verkapseln; Wrapping des Codes in einer Transaktion und Behandlung verschiedener Ausnahmen (Locking-Probleme). Das ist einfach genug, aber ich möchte auch, dass der Block das Wiederholen des Codeblocks nach bestimmten Ausnahmen kapselt. Ich kann keinen Weg sehen, dies ordentlich in den Kontextmanager zu verpacken.Encapsulating Wiederholungen in `mit` Block

Ist es möglich, den Code innerhalb einer with Anweisung zu wiederholen?

Ich möchte es so einfach verwenden, das ist wirklich ordentlich.

def do_work(): 
    ... 
    # This is ideal! 
    with transaction(retries=3): 
     # Atomic DB statements 
     ... 
    ... 

ich derzeit Umgang dies mit einem Dekorateur, aber ich würde es vorziehen, den Kontext-Manager zu bieten (oder beides in der Tat), so kann ich wählen stattdessen im with Block ein paar Zeilen Code wickeln einer Inline-Funktion in einem Dekorateur eingewickelt, das ist das, was ich im Moment tun:

def do_work(): 
    ... 
    # This is not ideal! 
    @transaction(retries=3) 
    def _perform_in_transaction(): 
     # Atomic DB statements 
     ... 
    _perform_in_transaction() 
    ... 
+0

http://docs.python.org/release/2.5/whatsnew/pep-343.html sieht aus wie es Beispiele auf hat, wie man einen Kontext-Manager zu implementieren. – Vlad

Antwort

4

Als Dekorateure sind Funktionen selbst nur, könnten Sie folgendes tun:

with transaction(_perform_in_transaction, retries=3) as _perf: 
    _perf() 

Für die Details, die Sie transaction() als Factory-Methode implementieren müssen, die ein Objekt mit __callable__() zurückgibt, um die ursprüngliche Methode aufzurufen, und wiederholen Sie es bis zu retries Anzahl Male bei Fehler; __enter__() und __exit__() würden für Datenbanktransaktionskontextmanager als normal definiert werden.

Sie könnten alternativ transaction() so einrichten, dass es selbst die übergebene Methode bis zu retries Anzahl von Zeiten ausführt, die wahrscheinlich etwa die gleiche Menge an Arbeit wie die Implementierung des Kontextmanagers erfordern würde, würde aber bedeuten, die tatsächliche Nutzung wäre nur reduziert transaction(_perform_in_transaction, retries=3) (was tatsächlich dem Dekorationsbeispiel delnan entspricht).

12

Ist es möglich, den Code innerhalb einer with Anweisung zu wiederholen?

No.

Wie hat bereits darauf hingewiesen, dass Mailing-Liste Thread, können Sie ein wenig Überschneidungen reduzieren, indem sie die Dekorateur die übergebene Funktion aufrufen:

def do_work(): 
    ... 
    # This is not ideal! 
    @transaction(retries=3) 
    def _perform_in_transaction(): 
     # Atomic DB statements 
     ... 
    # called implicitly 
    ... 
+0

Ah, schade, dass es nicht unterstützt wird. Danke für den Link zum Thread. Ich mag die Idee, den Anruf implizit zu haben, um ihn sauberer zu machen. Wenn ich vars in '_perform_in_transaction' setzen/ändern will, muss ich es wahrscheinlich manuell aufrufen und zurückgeben, was ich brauche, um den Rest der 'do_work'-Funktion fortzusetzen. –

4

Die Art und Weise, die mir einfällt, Um dies zu tun, ist nur eine Standard-Datenbanktransaktion context manager zu implementieren, aber erlauben Sie es, ein retries Argument im Konstruktor zu nehmen. Dann würde ich das einfach in Ihre Methodenimplementierungen einbinden. Etwas wie folgt aus:

class transaction(object): 
    def __init__(self, retries=0): 
     self.retries = retries 
    def __enter__(self): 
     return self 
    def __exit__(self, exc_type, exc_val, traceback): 
     pass 

    # Implementation... 
    def execute(self, query): 
     err = None 
     for _ in range(self.retries): 
      try: 
       return self._cursor.execute(query) 
      except Exception as e: 
       err = e # probably ought to save all errors, but hey 
     raise err 

with transaction(retries=3) as cursor: 
    cursor.execute('BLAH') 
+1

Können Sie herausfinden, woher '_cursor' in' self._cursor' kommt? –

+0

@ MikeMüller Ich versuche, auf einige gemeinsame Datenbank-API-Gewohnheiten zu zeichnen, ohne in Implementierungsdetails stecken zu bleiben.'_cursor' soll ein [' Cursor'] (http://www.python.org/dev/peps/pep-0249/#cursor-objects) -Objekt sein, das für die jeweilige betroffene Datenbankverbindung geeignet ist. Eine vollständige Implementierung müsste ein "Connection" -Objekt irgendeiner Art erstellen und enthalten, um die Datenbanktransaktionen tatsächlich auszuführen. –

+0

@HenryKeller Ich hätte etwas wie 'def __init __ (self, cursor, retries = 0): 'und innerhalb des' __init__' dieses 'self._cursor = cursor' gemacht. Verwendung: 'mit Transaktion (Cursor, Wiederholungen = 3) als Cursor:'. Macht das Sinn? –

Verwandte Themen