2014-10-29 10 views
10

Użyłem go pod numerem calling __enter__ manually, ale bez powodzenia. Wyobraźmy sobie, że mam klasę złączy MySQL , która używa funkcji __enter__ i __exit__ (pierwotnie używana z instrukcją with) do łączenia/rozłączania z bazą danych.Wywołanie __enter__ i __exit__ ręcznie

I mamy klasę, która wykorzystuje 2 z tych połączeń (na przykład do synchronizacji danych). Uwaga: to nie jest mój prawdziwy scenariusz, ale wydaje się być najprostszym przykładem:.

Najprostszym sposobem, aby to wszystko działa razem jest klasa tak:

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: Czy to w porządku (jest coś nie w porządku), aby wywołać __enter__/__exit__ ręcznie jak to?

Pylint 1.1.0 nie wydał żadnych ostrzeżeń na ten temat, ani nie znalazłem żadnego artykułu na ten temat (link google na początku).

A co dzwoni:

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

Czy to dobry/zły praktyki? Czemu?

+4

Powiedziałbym, że to dobrze, widzisz jako [wszyscy tutaj zgadzamy się na dorosłych] (https://mail.python.org/pipermail/tutor/2003-October/025932.html), lub możesz użyć czegoś podobnego [ExitStack] (https://docs.python.org/3/library/contextlib.html#contextlib.ExitStack), który wykona zaproszenie dla ciebie. – matsjoyce

+0

Metoda \ _ \ _ exit \ _ \ _ będzie wywoływana w instrukcji with w każdym razie, nie w przypadku ręcznego wywoływania tych metod, afaik. – XORcist

+0

@XORcist Dodałem przykładowy przykład użycia ... W dostarczonym przypadku (uważam), musisz wywołać to ręcznie. – Vyktor

Odpowiedz

8

Nie, nie ma w tym nic złego. Istnieją nawet miejsca w standardowej bibliotece, które to robią. Podobnie jak 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) 

Lub 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 

Przykłady standardowej biblioteki nie wzywają __enter__/__exit__ dla dwóch obiektów, ale jeśli masz obiekt, który jest odpowiedzialny za tworzenie/niszczenie kontekstu dla wielu obiektów zamiast jednego, wywołanie dla nich wszystkich jest w porządku.

Jedynym potencjalnym zagrożeniem jest poprawne przetwarzanie zwracanych wartości wywołań obiektów zarządzanych przez użytkownika. Z numerem __enter__ musisz upewnić się, że wszystko, co potrzebne dla użytkownika obiektu opakowującego, jest wymagane, aby uzyskać połączenie with ... as <state>:. Z __exit__ musisz zdecydować, czy chcesz propagować wyjątek, który wystąpił wewnątrz kontekstu (zwracając False), czy też go wyłączyć (zwracając True). Twoje zarządzane obiekty mogą spróbować zrobić to w dowolny sposób, musisz zdecydować, co ma sens dla obiektu opakowania.

7

Twój pierwszy przykład nie jest dobrym pomysłem:

  1. Co się stanie, jeśli slave_connection.__enter__ zgłasza wyjątek:

    • master_connection nabiera zasób
    • slave_connection zawiedzie
    • DataSync.__enter__ propogates wyjątek
    • DataSync.__exit__ nie działa
    • master_connection nigdy nie jest czyszczony!
    • Potencjał dla złych rzeczy
  2. Co się stanie, jeśli master_connection.__exit__ zgłasza wyjątek?

    • DataSync.__exit__ zakończeniu wcześnie
    • slave_connection nigdy nie jest wyczyszczone!
    • Potencjał Bad Things

contextlib.ExitStack może pomóc tutaj:

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) 

wyjściowa te same pytania:

  1. Co się stanie, jeśli slave_connection.__enter__ zgłasza wyjątek:

    • WITH bloku zostanie zamknięty, a stack sprząta master_connection
    • Wszystko jest w porządku!
  2. Co się stanie, jeśli master_connection.__exit__ zgłasza wyjątek?

    • Nieważne, slave_connection zostanie oczyszczone, zanim to się nazywa
    • Wszystko jest w porządku!
  3. OK, co się stanie, jeśli slave_connection.__exit__ zgłasza wyjątek?

    • ExitStack pilnuje zadzwonić master_connection.__exit__ co dzieje się w związku niewolniczej
    • wszystko jest ok!

Nie ma nic złego z wywołaniem __enter__ bezpośrednio, ale jeśli trzeba to nazwać na więcej niż jeden obiekt, upewnij się, posprzątać prawidłowo!

+0

Nie wspominałem o tym, że uruchomiłem Python 3.2 w tym czasie i zgodnie z ['contextlib.ExitStack' dokumentacji] (https://docs.python.org/3/library/contextlib.html#contextlib.ExitStack) został dodany w "3.3", więc w tym czasie jest najdokładniejsze, ale zgadzam się, że dla wersji 3.3+ jest to właściwy sposób. – Vyktor

+2

@Vyktor: Zawsze jest dostępny backport ['contextlib2'] (https://pypi.python.org/pypi/contextlib2) dla wcześniejszych wersji! Nawet przed ExitStack, możesz uzyskać bezpieczeństwo z ostrożnym spróbuj/finallys – Eric

Powiązane problemy