2013-06-04 4 views
21

Sto cercando di incapsulare la logica per le transazioni del database in un blocco with; avvolgere il codice in una transazione e gestire varie eccezioni (problemi di blocco). Questo è abbastanza semplice, tuttavia mi piacerebbe anche che il blocco incapsulasse il nuovo tentativo del blocco di codice dopo alcune eccezioni. Non riesco a vedere un modo per impacchettarlo ordinatamente nel gestore del contesto.Esegui l'incapsulamento dei tentativi nel blocco `with`

È possibile ripetere il codice all'interno di una dichiarazione with?

Mi piacerebbe usarlo semplicemente come questo, che è veramente pulito.

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

Attualmente sto manipolazione di questo con un decoratore, ma preferirei di offrire al gestore di contesto (o di fatto entrambi), in modo da poter scegliere di avvolgere un paio di righe di codice nel blocco with invece di una funzione inline avvolto in un decoratore, che è quello che faccio in questo momento:

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 sembra che ha esempi su come implementare un gestore di contesto. – Vlad

risposta

4

Come decoratori sono solo se stessi funzioni, è possibile effettuare le seguenti operazioni:

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

Per i dettagli, è necessario implementare transaction() come metodo di fabbrica che restituisce un oggetto con __callable__() impostato per chiamare il metodo originale e ripeterlo su retries numero di volte in caso di errore; __enter__() e __exit__() sarebbero definiti come normali per i gestori di contesto delle transazioni del database.

Si potrebbe in alternativa impostare transaction() tale che si esegue il metodo passato fino a retries numero di volte, che richiederebbe probabilmente circa la stessa quantità di lavoro come attuare il gestore contesto ma significherebbe utilizzo effettivo sarebbe ridotto a solo transaction(_perform_in_transaction, retries=3) (che è, in effetti, equivalente al decoratore delnan fornito).

12

È possibile ripetere il codice all'interno di una dichiarazione with?

No.

Come sottolineato in precedenza in quel filo mailing list, è possibile ridurre un po 'di duplicazione facendo il decoratore chiamare la funzione passata:

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

Ah, peccato che non sia supportato. Grazie per il link alla discussione. Mi piace l'idea di avere la chiamata implicita per renderla più pulita. Se voglio impostare/modificare vars all'interno di '_perform_in_transaction', suppongo che dovrò comunque chiamarlo manualmente e restituire ciò di cui ho bisogno per continuare il resto della funzione' do_work'. –

4

Il modo in cui mi viene in mente per fare questo è solo per implementare una transazione di database standard context manager, ma consentire di prendere un argomento retries nel costruttore. Quindi lo completerei nelle implementazioni del tuo metodo. Qualcosa di simile a questo:

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

Puoi elaborare da dove proviene '_cursor' in' self._cursor'? –

+0

@ MikeMüller Sto cercando di attingere ad alcune comuni usanze API del database senza impantanarmi nei dettagli di implementazione.'_cursor' è pensato per essere un oggetto [' Cursore'] (http://www.python.org/dev/peps/pep-0249/#cursor-objects), come appropriato per la particolare connessione al database in questione. Un'implementazione completa dovrebbe creare e contenere un oggetto 'Connection' di qualche tipo, al fine di eseguire effettivamente le transazioni del database. –

+0

@HenryKeller Avrei fatto qualcosa di simile a 'def __init __ (self, cursor, retries = 0):' e all'interno di '__init__' questo' self._cursor = cursor '. Uso: 'con transazione (cursore, tentativi = 3) come cursore:'. Ha senso ciò? –