55

Jestem ciekaw informacji na temat szczegółów __del__ w python, kiedy i dlaczego należy go używać i do czego nie należy go używać. Nauczyłem się na własnej skórze, że tak naprawdę nie jest to coś, co można by naiwnie oczekiwać od destruktora, ponieważ nie jest przeciwieństwem __new__/__init__.Jak wymusić usunięcie obiektu Pythona?

class Foo(object): 

    def __init__(self): 
     self.bar = None 

    def open(self): 
     if self.bar != 'open': 
      print 'opening the bar' 
      self.bar = 'open' 

    def close(self): 
     if self.bar != 'closed': 
      print 'closing the bar' 
      self.bar = 'close' 

    def __del__(self): 
     self.close() 

if __name__ == '__main__': 
    foo = Foo() 
    foo.open() 
    del foo 
    import gc 
    gc.collect() 

widziałem w dokumentacji, że jest nie gwarantowane __del__() metody są nazywane dla obiektów, które nadal istnieją, kiedy wyjść tłumacza.

  1. W jaki sposób można zagwarantować, że w przypadku wystąpienia jakichkolwiek wystąpień Foo po wyjściu interpretera pasek zostanie zamknięty?
  2. w fragmencie kodu powyżej pasek zostanie zamknięty na del foo lub na gc.collect() ... czy też nie? jeśli chcesz dokładniej kontrolować te szczegóły (np. pasek powinien być zamknięty, gdy obiekt nie jest odwoływany), jaki jest zwykle sposób na jego implementację?
  3. Po wywołaniu __del__ jest zagwarantowane, że już zostało wywołane __init__? a co jeśli podniósł się __init__?

Odpowiedz

64

Sposób, aby zamknąć zasoby są menedżerowie kontekście aka rachunku with:

class Foo(object): 

    def __init__(self): 
    self.bar = None 

    def __enter__(self): 
    if self.bar != 'open': 
     print 'opening the bar' 
     self.bar = 'open' 
    return self # this is bound to the `as` part 

    def close(self): 
    if self.bar != 'closed': 
     print 'closing the bar' 
     self.bar = 'close' 

    def __exit__(self, *err): 
    self.close() 

if __name__ == '__main__': 
    with Foo() as foo: 
    print foo, foo.bar 

wyjściowy:

opening the bar 
<__main__.Foo object at 0x17079d0> open 
closing the bar 

2) obiektów Pythona zostaną usunięte, gdy ich licznika odwołań to 0. W twoim przykładzie del foo usuwa ostatnie odniesienie, więc __del__ jest nazywane natychmiast. GC nie ma w tym żadnego udziału.

class Foo(object): 

    def __del__(self): 
     print "deling", self 

if __name__ == '__main__': 
    import gc 
    gc.disable() # no gc 
    f = Foo() 
    print "before" 
    del f # f gets deleted right away 
    print "after" 

wyjściowa:

before 
deling <__main__.Foo object at 0xc49690> 
after 

gc nie ma nic wspólnego z usunięciem swoje i większość innych obiektów. Jest tam posprzątać, gdy liczenie proste odniesienie nie działa, z powodu własnych odniesień lub okrągły odnośnikach:

class Foo(object): 
    def __init__(self, other=None): 
     # make a circular reference 
     self.link = other 
     if other is not None: 
      other.link = self 

    def __del__(self): 
     print "deling", self 

if __name__ == '__main__': 
    import gc 
    gc.disable() 
    f = Foo(Foo()) 
    print "before" 
    del f # nothing gets deleted here 
    print "after" 
    gc.collect() 
    print gc.garbage # The GC knows the two Foos are garbage, but won't delete 
        # them because they have a __del__ method 
    print "after gc" 
    # break up the cycle and delete the reference from gc.garbage 
    del gc.garbage[0].link, gc.garbage[:] 
    print "done" 

wyjściowa:

before 
after 
[<__main__.Foo object at 0x22ed8d0>, <__main__.Foo object at 0x22ed950>] 
after gc 
deling <__main__.Foo object at 0x22ed950> 
deling <__main__.Foo object at 0x22ed8d0> 
done 

3) Pozwala zobaczyć:

class Foo(object): 
    def __init__(self): 

     raise Exception 

    def __del__(self): 
     print "deling", self 

if __name__ == '__main__': 
    f = Foo() 

podaje:

Traceback (most recent call last): 
    File "asd.py", line 10, in <module> 
    f = Foo() 
    File "asd.py", line 4, in __init__ 
    raise Exception 
Exception 
deling <__main__.Foo object at 0xa3a910> 

Obiekty są tworzone przy użyciu __new__, a następnie przekazywane do __init__ jako self. Po wystąpieniu wyjątku w __init__ obiekt zazwyczaj nie będzie miał nazwy (tzn. Część f = nie zostanie uruchomiona), więc ich liczba ref wynosi 0. Oznacza to, że obiekt jest normalnie usuwany i wywoływana jest __del__.

+0

> Sposobem na zamknięcie zasobów są menedżerowie kontekstu, czyli instrukcja with. Doskonała wskazówka. Nie wiedziałem, że 'with' może być użyte do zawarcia zakresu dla dowolnego obiektu w ten sposób. –

+3

Ta odpowiedź jest bardzo pouczająca. Dzięki za jasne wyjaśnienie! –

2
  1. Dodaj exit handler który zamyka wszystkie bary.
  2. __del__() zostaje wywołany, gdy liczba odniesień do obiektu wynosi 0, gdy maszyna wirtualna nadal działa. Może to być spowodowane przez GC.
  3. Jeśli __init__() zgłosi wyjątek, wówczas obiekt zostanie uznany za niekompletny, a __del__() nie zostanie wywołany.
+2

Na marginesie: os._exit umożliwia całkowite obejście wszystkich funkcji obsługi zamkniętej. – cwallenpoole

8

Generalnie, aby upewnić się, że coś się dzieje, bez względu na to, użyć

from exceptions import NameError 

try: 
    f = open(x) 
except ErrorType as e: 
    pass # handle the error 
finally: 
    try: 
     f.close() 
    except NameError: pass 

finally bloki będą działać, czy nie doszło do błędu w bloku try i czy nie istnieje błąd w jakiejkolwiek obsłudze błędów, która ma miejsce w blokach except. Jeśli nie poradzisz sobie z wyjątkiem, który został podniesiony, będzie on nadal podnoszony po wykonaniu bloku finally.

Ogólnym sposobem upewnienia się, że plik jest zamknięty, jest użycie "menedżera kontekstów".

http://docs.python.org/reference/datamodel.html#context-managers

with open(x) as f: 
    # do stuff 

To spowoduje automatyczne zamknięcie f.

Na twoje pytanie nr 2, bar zostaje zamknięte natychmiast, gdy licznik odniesień osiągnie zero, więc na del foo, jeśli nie ma innych odniesień.

Obiekty NIE są tworzone przez __init__, są one tworzone przez __new__.

http://docs.python.org/reference/datamodel.html#object.new

Kiedy robisz foo = Foo() dwie rzeczy są rzeczywiście dzieje, pierwszy nowy obiekt jest tworzony, __new__, to jest inicjowany, __init__. Więc nie ma możliwości, aby zadzwonić pod numer del foo, zanim oba te kroki miały miejsce. Jeśli jednak wystąpi błąd w postaci __init__, nadal będzie wywoływane __del__, ponieważ obiekt został faktycznie utworzony w __new__.

Edytuj: poprawiono, gdy usuwanie ma miejsce, gdy liczba odwołań zmniejsza się do zera.

+1

Twój przykład "try..except..finally" jest zepsuty: jeśli 'open()' zgłasza wyjątek 'f' zostanie anulowane i ostatecznie nie będzie działać. – Duncan

+0

Dzięki, naprawione. Jednakże nadal zapewniałoby zamknięcie 'f', ponieważ błąd wystąpił tylko w przypadku, gdyby nigdy nie został otwarty. – agf

+2

@afg jesteś w błędzie co do gc i kiedy obiekty są usuwane, zobacz moją odpowiedź. –

5

Być może szukasz context manager?

>>> class Foo(object): 
... def __init__(self): 
...  self.bar = None 
... def __enter__(self): 
...  if self.bar != 'open': 
...  print 'opening the bar' 
...  self.bar = 'open' 
... def __exit__(self, type_, value, traceback): 
...  if self.bar != 'closed': 
...  print 'closing the bar', type_, value, traceback 
...  self.bar = 'close' 
... 
>>> 
>>> with Foo() as f: 
...  # oh no something crashes the program 
...  sys.exit(0) 
... 
opening the bar 
closing the bar <type 'exceptions.SystemExit'> 0 <traceback object at 0xb7720cfc> 
Powiązane problemy