2009-05-14 9 views
340
class Package: 
    def __init__(self): 
     self.files = [] 

    # ... 

    def __del__(self): 
     for file in self.files: 
      os.unlink(file) 

__del__(self) Powyżej kończy się niepowodzeniem z wyjątkiem AttributeError. Rozumiem, że istnieje Python doesn't guarantee istnienie "zmiennych globalnych" (dane członków w tym kontekście?) Po wywołaniu __del__(). Jeśli tak jest i to jest przyczyna wyjątku, w jaki sposób mogę się upewnić, że obiekt jest zniszczony prawidłowo?Jak poprawnie usunąć obiekt Pythona?

+3

Czytanie tego, co łączyłeś, zmienne globalne odchodzą, nie ma tu zastosowania, chyba że mówisz o tym, kiedy program jest wychodzący, podczas którego myślę, że zgodnie z tym, co łączyłeś, MOŻLIWE jest, że sam moduł os jest już nie ma. W przeciwnym razie nie sądzę, że ma ona zastosowanie do zmiennych członkowskich w metodzie __del __(). –

+3

Wyjątek jest generowany na długo przed wyjściem mojego programu. Wyjątek AttributeError, który otrzymuję, to Python, który twierdzi, że nie rozpoznaje self.files jako atrybutu Package. Być może robię to źle, ale jeśli przez "globały" nie mają znaczenia zmienne globalne względem metod (ale być może lokalne dla klasy), to nie wiem, co powoduje ten wyjątek. Wskazówki Google Python zastrzega sobie prawo do czyszczenia danych członkowskich zanim zostanie wywołany __del __ (self). – wilhelmtell

+1

Opublikowany kod wydaje się działać dla mnie (w Pythonie 2.5). Czy możesz opublikować rzeczywisty kod, który się nie powiódł - lub uproszczony (im prostsza, lepsza wersja, która nadal powoduje błąd?) – Silverfish

Odpowiedz

468

Polecam używanie oświadczenia Pythona with do zarządzania zasobami, które wymagają oczyszczenia. Problem z użyciem wyraźnego oświadczenia close() polega na tym, że trzeba się martwić, że ludzie zapomnieli w ogóle go wywołać lub zapomnieli umieścić go w bloku finally, aby zapobiec wyciekowi zasobów w przypadku wystąpienia wyjątku.

Aby użyć instrukcji with utworzyć klasę z następujących metod:

def __enter__(self) 
    def __exit__(self, exc_type, exc_value, traceback) 

W przykładzie powyżej, można użyć

class Package: 
    def __init__(self): 
     self.files = [] 

    def __enter__(self): 
     return self 

    # ... 

    def __exit__(self, exc_type, exc_value, traceback): 
     for file in self.files: 
      os.unlink(file) 

Potem, gdy ktoś chciał wykorzystać swoją klasę , wykonaj następujące czynności:

with Package() as package_obj: 
    # use package_obj 

Zmienna package_obj będzie wystąpienie typu Package (jest to wartość zwrócona przez metodę __enter__). Jego metoda __exit__ zostanie automatycznie wywołana, niezależnie od tego, czy wystąpi wyjątek.

Można nawet posunąć to podejście o krok dalej. W powyższym przykładzie ktoś mógł nadal utworzyć instancję pakietu przy użyciu swojego konstruktora bez użycia klauzuli with. Nie chcesz, żeby tak się stało. Można to naprawić, tworząc klasę PackageResource, która definiuje metody __enter__ i __exit__. Następnie klasa Package zostanie zdefiniowana ściśle w metodzie __enter__ i zwrócona.W ten sposób rozmówca nie mógł instancję klasy opakowania bez użycia with oświadczenie:

class PackageResource: 
    def __enter__(self): 
     class Package: 
      ... 
     self.package_obj = Package() 
     return self.package_obj 

    def __exit__(self, exc_type, exc_value, traceback): 
     self.package_obj.cleanup() 

byłoby użyć to w następujący sposób:

with PackageResource() as package_obj: 
    # use package_obj 
+25

Technicznie rzecz biorąc, można nazwać PackageResource() .__ wpisać __() jawnie iw ten sposób utworzyć pakiet, który nigdy nie zostanie sfinalizowany ... ale naprawdę musiałby próbować złamać kod. Prawdopodobnie nie ma się czym martwić. –

+3

Nawiasem mówiąc, jeśli używasz Pythona 2.5, musisz wykonać z __future__ import z_statementem , aby móc użyć instrukcji with. –

+2

Znalazłem artykuł, który pomaga pokazać, dlaczego __del __() działa tak, jak robi i daje wiarę w korzystanie z rozwiązania do zarządzania kontekstem: http://www.andy-pearce.com/blog/posts/2013/Apr/python- destructor-minus/ – eikonomega

6

Po prostu zapakuj destruktor za pomocą instrukcji try/except i nie wyrzuci wyjątku, jeśli globale zostały już usunięte.

Edit

Spróbuj tego:

from weakref import proxy 

class MyList(list): pass 

class Package: 
    def __init__(self): 
     self.__del__.im_func.files = MyList([1,2,3,4]) 
     self.files = proxy(self.__del__.im_func.files) 

    def __del__(self): 
     print self.__del__.im_func.files 

Będzie rzeczy listę plików w del funkcji, która jest zagwarantowana istnieć w momencie wywołania. Proxy weakref ma zapobiegać w jakiś sposób Pythonowi lub samemu usuwaniu zmiennej self.files (jeśli zostanie usunięta, nie wpłynie to na oryginalną listę plików). Jeśli nie jest tak, że jest to usuwane, mimo że istnieje więcej odwołań do zmiennej, można usunąć enkapsulację proxy.

+2

Problem polega na tym, że jeśli dane członkowskie znikną, jest już za późno. Potrzebuję tych danych. Zobacz mój kod powyżej: Potrzebuję nazw plików, aby wiedzieć, które pliki usunąć. Uproszczę jednak mój kod, są inne dane, które muszę oczyścić (tj. Tłumacz nie będzie wiedział, jak je wyczyścić). – wilhelmtell

+0

To rozwiązanie zadziałało wyjątkowo dobrze w moim przypadku. – gaborous

4

Wydaje się, że idiomatycznym sposobem na to jest dostarczenie metody close() (lub podobnej) i wywołanie tego explicitely.

+18

To jest podejście, którego użyłem wcześniej, ale napotkałem na inne problemy z tym związane. Z wyjątkami wyrzuconymi wszędzie przez inne biblioteki potrzebuję pomocy Pythona w oczyszczaniu bałaganu w przypadku błędu. W szczególności potrzebuję Pythona, aby wywołać destruktor dla mnie, ponieważ w przeciwnym razie kod stanie się szybko niemożliwy do zarządzania, a na pewno zapomnę punktu wyjścia, w którym powinien być wywołanie funkcji .close(). – wilhelmtell

16

Nie sądzę, że jest to możliwe dla członków instancji do usunięcia przed wywołaniem __del__. Domyślam się, że powodem twojego szczególnego AttributeError jest gdzieś indziej (może przez pomyłkę usuniesz plik self.file w innym miejscu).

Jednak, jak wskazali inni, należy unikać używania __del__. Głównym powodem jest to, że instancje z __del__ nie będą zbierane śmieci (będą one uwolnione dopiero, gdy ich wartość odniesienia osiągnie 0). Dlatego jeśli twoje instancje są zaangażowane w odwołania cykliczne, będą one żyły w pamięci tak długo, jak aplikacja będzie działać. (Mogę się jednak mylić z tym wszystkim, będę musiał ponownie przeczytać dokumentację gc, ale jestem pewien, że to działa tak).

+4

Obiekty z '__del__' mogą być zbędne, jeśli ich licznik odniesień od innych obiektów z' __del__' wynosi zero i są one niedostępne. Oznacza to, że jeśli masz cykl odniesienia między obiektami z '__del__', żadne z nich nie zostaną zebrane. Każdy inny przypadek powinien jednak zostać rozwiązany zgodnie z oczekiwaniami. – Collin

10

Myślę, że problem może być w __init__, jeśli jest więcej kodu niż pokazano?

__del__ zostanie wywołany, nawet jeśli __init__ nie został wykonany poprawnie lub wyrzucił wyjątek.

Source

+2

Brzmi bardzo prawdopodobne. Najlepszym sposobem na uniknięcie tego problemu podczas używania '__del__' jest jawne deklarowanie wszystkich członków na poziomie klasy, upewniając się, że zawsze istnieją, nawet jeśli' __init__' się nie powiedzie. W podanym przykładzie działa 'files =()', ale najczęściej wystarczy przypisać 'None'; w obu przypadkach nadal trzeba przypisać wartość rzeczywistą w '__init__'. –

21

jako dodatek do Clint's answer można uprościć PackageResource użyciu contextlib.contextmanager:

@contextlib.contextmanager 
def packageResource(): 
    class Package: 
     ... 
    package = Package() 
    yield package 
    package.cleanup() 

Alternatywnie, choć zapewne nie tak pythonic można przesłonić Package.__new__:

class Package(object): 
    def __new__(cls, *args, **kwargs): 
     @contextlib.contextmanager 
     def packageResource(): 
      # adapt arguments if superclass takes some! 
      package = super(Package, cls).__new__(cls) 
      package.__init__(*args, **kwargs) 
      yield package 
      package.cleanup() 

    def __init__(self, *args, **kwargs): 
     ... 

i po prostu użyj with Package(...) as package.

dostać rzeczy krótsze, nazwij swoją funkcję oczyszczania close i używać contextlib.closing, w takim przypadku można użyć do niezmodyfikowanej Package klasę poprzez with contextlib.closing(Package(...)) lub zastąpić jej __new__ do prostszej

class Package(object): 
    def __new__(cls, *args, **kwargs): 
     package = super(Package, cls).__new__(cls) 
     package.__init__(*args, **kwargs) 
     return contextlib.closing(package) 

I to konstruktor jest dziedziczona , więc możesz po prostu dziedziczyć, np

class SubPackage(Package): 
    def close(self): 
     pass 
+1

** To jest niesamowite. ** Szczególnie podoba mi się ostatni przykład. Szkoda, że ​​nie możemy jednak ominąć cztero-liniowej tablicy z metody 'Package .__ new __()'. Może lub możemy. Moglibyśmy zdefiniować albo dekoratora klasy, albo metaklasy generalizujące dla nas ten zestaw. Jedzenie dla myśli Pythonic. –

+0

@CecilCurry Dzięki, i dobry punkt. Każda klasa dziedzicząca z 'pakietu' również powinna to zrobić (chociaż jeszcze tego nie przetestowałem), więc żadna metaklama nie powinna być wymagana. Chociaż znalazłem dość ciekawe metody używania metaclasses w przeszłości ... –

+0

@CecilCurry W rzeczywistości konstruktor jest dziedziczony, więc możesz użyć 'Package' (lub lepiej klasy o nazwie' Closing') jako swojego rodzica klasowego zamiast "obiekt". Ale nie pytaj mnie, jak wiele niechcianych spadków z tym ... –

6

Standardowym sposobem jest użycie atexit.register:

# package.py 
import atexit 
import os 

class Package: 
    def __init__(self): 
     self.files = [] 
     atexit.register(self.cleanup) 

    def cleanup(self): 
     print("Running cleanup...") 
     for file in self.files: 
      print("Unlinking file: {}".format(file)) 
      # os.unlink(file) 

Ale należy pamiętać, że ta będzie się utrzymywać wszystkie utworzone instancje Package aż Python jest zakończona.

Demo stosując powyższy kod zapisany jako package.py:

$ python 
>>> from package import * 
>>> p = Package() 
>>> q = Package() 
>>> q.files = ['a', 'b', 'c'] 
>>> quit() 
Running cleanup... 
Unlinking file: a 
Unlinking file: b 
Unlinking file: c 
Running cleanup... 
3

Lepszym rozwiązaniem jest użycie weakref.finalize. Zobacz przykłady na Finalizer Objects i Comparing finalizers with __del__() methods.

+0

Używane dzisiaj i działa bezbłędnie, lepiej niż inne rozwiązania. Mam klasy komunikatora wieloprocesowego, który otwiera port szeregowy, a następnie mam metodę 'stop()' do zamykania portów i 'join()' procesów. Jeśli jednak programy nieoczekiwanie wyjdą, 'stop()' nie zostanie wywołany - rozwiązałem to przy pomocy finalizatora. Ale w każdym razie nazywam '_finalizer.detach()' w metodzie zatrzymania, aby zapobiec wywołaniu go dwukrotnie (ręcznie i później ponownie przez finalizatora). –

Powiązane problemy