2014-10-29 18 views
10

Ho cercato su google calling __enter__ manually ma senza fortuna. Immaginiamo quindi di avere classe di connessione MySQL che utilizza le funzioni __enter__ e __exit__ (originariamente utilizzate con l'istruzione with) per connettersi/disconnettersi da un database.Chiamare manualmente __enter__ e __exit__

E abbiamo una classe che utilizza 2 di queste connessioni (ad esempio per la sincronizzazione dei dati). Nota: questo non è il mio scenario reale, ma sembra essere l'esempio più semplice.

modo più semplice per far funzionare il tutto insieme è classe come questa:

class DataSync(object): 

    def __init__(self): 
     self.master_connection = MySQLConnection(param_set_1) 
     self.slave_connection = MySQLConnection(param_set_2) 

    def __enter__(self): 
      self.master_connection.__enter__() 
      self.slave_connection.__enter__() 
      return self 

    def __exit__(self, exc_type, exc, traceback): 
      self.master_connection.__exit__(exc_type, exc, traceback) 
      self.slave_connection.__exit__(exc_type, exc, traceback) 

    # Some real operation functions 

# Simple usage example 
with DataSync() as sync: 
    records = sync.master_connection.fetch_records() 
    sync.slave_connection.push_records(records) 

Q: Va bene (è qualcosa che non va) per chiamare __enter__/__exit__ manualmente come questo?

Pylint 1.1.0 non ha pubblicato alcun avviso, né ho trovato alcun articolo a riguardo (link di Google all'inizio).

E che dire di chiamata:

try: 
    # Db query 
except MySQL.ServerDisconnectedException: 
    self.master_connection.__exit__(None, None, None) 
    self.master_connection.__enter__() 
    # Retry 

questa è una buona/cattiva pratica? Perché?

+4

Direi che va bene, vedi come [siamo tutti adulti consenzienti qui] (https://mail.python.org/pipermail/tutor/2003-October/025932.html), oppure potresti usare qualcosa come [ExitStack] (https://docs.python.org/3/library/contextlib.html#contextlib.ExitStack), che eseguirà la chiamata per te. – matsjoyce

+0

Il metodo \ _ \ _ exit \ _ \ _ verrà chiamato comunque con l'istruzione with, non così quando si chiamano questi metodi manualmente, afaik. – XORcist

+0

@XORcist Ho aggiunto un esempio di utilizzo di esempio ... Nel caso previsto (credo) devi chiamarlo manualmente. – Vyktor

risposta

8

No, non c'è niente di sbagliato in questo. Ci sono persino posti nella libreria standard che lo fanno. Come il multiprocessing module:

class SemLock(object): 

    def __init__(self, kind, value, maxvalue, *, ctx): 
      ... 
      try: 
       sl = self._semlock = _multiprocessing.SemLock(
        kind, value, maxvalue, self._make_name(), 
        unlink_now) 
      except FileExistsError: 
       pass 
    ... 

    def __enter__(self): 
     return self._semlock.__enter__() 

    def __exit__(self, *args): 
     return self._semlock.__exit__(*args) 

O il tempfile module:

class _TemporaryFileWrapper: 

    def __init__(self, file, name, delete=True): 
     self.file = file 
     self.name = name 
     self.delete = delete 
     self._closer = _TemporaryFileCloser(file, name, delete) 

    ... 

    # The underlying __enter__ method returns the wrong object 
    # (self.file) so override it to return the wrapper 
    def __enter__(self): 
     self.file.__enter__() 
     return self 

    # Need to trap __exit__ as well to ensure the file gets 
    # deleted when used in a with statement 
    def __exit__(self, exc, value, tb): 
     result = self.file.__exit__(exc, value, tb) 
     self.close() 
     return result 

Gli esempi della libreria standard non chiedono __enter__/__exit__ per due oggetti, ma se hai un oggetto che è responsabile della creazione/distruggere il contesto per più oggetti invece di uno solo, chiamando __enter__/__exit__ per tutti loro va bene.

L'unico potenziale getcha gestisce correttamente i valori di ritorno delle chiamate __enter____exit__ per gli oggetti che si gestiscono. Con __enter__, è necessario assicurarsi di restituire qualsiasi cosa sia richiesta state per consentire all'utente di recuperare l'oggetto wrapper dalla chiamata with ... as <state>:. Con __exit__, è necessario decidere se si desidera propagare qualsiasi eccezione verificatasi nel contesto (restituendo False) o sopprimerla (restituendo True). Gli oggetti gestiti potrebbero provare a farlo in entrambi i modi, è necessario decidere cosa ha senso per l'oggetto wrapper.

7

Il tuo primo esempio non è una buona idea:

  1. Cosa succede se slave_connection.__enter__ genera un'eccezione:

    • master_connection acquisisce la sua risorsa
    • slave_connection fallisce
    • DataSync.__enter__ propaga l'eccezione
    • DataSync.__exit__ non funziona
    • master_connection non viene mai pulito!
    • Potenziale di Bad Things
  2. Cosa succede se master_connection.__exit__ genera un'eccezione?

    • DataSync.__exit__ finito di primi
    • slave_connection viene mai ripulito!
    • Potenziale di Bad Things

contextlib.ExitStack può aiutare qui:

def __enter__(self): 
    with ExitStack() as stack: 
     stack.enter_context(self.master_connection) 
     stack.enter_context(self.slave_connection) 
     self._stack = stack.pop_all() 
    return self 

def __exit__(self, exc_type, exc, traceback): 
    self._stack.__exit__(self, exc_type, exc, traceback) 

le stesse domande:

  1. Cosa succede se slave_connection.__enter__ genera un'eccezione:

    • Il con il blocco si esce, e stack pulisce master_connection
    • Tutto è giusto!
  2. Cosa succede se master_connection.__exit__ genera un'eccezione?

    • non importa, slave_connection viene pulita prima di questo è chiamato
    • Tutto è giusto!
  3. Ok, cosa succede se slave_connection.__exit__ genera un'eccezione?

    • ExitStack fa in modo di chiamare master_connection.__exit__ qualunque cosa accada alla connessione slave
    • Tutto è giusto!

Non c'è niente di male a chiamare __enter__ direttamente, ma se avete bisogno di chiamare su più di un oggetto, assicurarsi di pulire correttamente!

+0

Ho omesso di menzionare che eseguivo Python 3.2 alla volta e in base a ['contextlib.ExitStack' documentation] (https://docs.python.org/3/library/contextlib.html#contextlib.ExitStack) è stato aggiunto in '3.3', quindi al momento dano è più preciso, ma sono d'accordo che per 3.3+ questo è il modo corretto di farlo. – Vyktor

+2

@Vyktor: C'è sempre il backport ['contextlib2'] (https://pypi.python.org/pypi/contextlib2) per le versioni precedenti! Anche prima di ExitStack, avresti potuto raggiungere la sicurezza con alcuni tentativi provati/finallys – Eric

Problemi correlati