Wie aufzuräumen ich richtig ein Python-Objekt?
-
21-08-2019 - |
Frage
class Package:
def __init__(self):
self.files = []
# ...
def __del__(self):
for file in self.files:
os.unlink(file)
__del__(self)
nicht oben mit einer Attribut Ausnahme. Ich verstehe Python ist keine Garantie die Existenz von „globalen Variablen“ (Mitgliederdaten in diesem Zusammenhang?), wenn __del__()
aufgerufen wird. Wenn das der Fall ist, und dies ist der Grund für die Ausnahme, wie kann ich sicher, dass das Objekt selbst zerstört richtig?
Lösung
würde ich empfehlen, für die Verwaltung von Ressourcen Pythons with
Anweisung, die bis zu gereinigt werden muß. Das Problem mit einer expliziten close()
Aussage ist, dass Sie über die Menschen vergessen zu kümmern, nennt es überhaupt oder zu vergessen, es in einem finally
Block zu platzieren, eine Ressource Leck zu verhindern, wenn eine Ausnahme auftritt.
die with
Anweisung verwenden, erstellen Sie eine Klasse mit den folgenden Methoden:
def __enter__(self)
def __exit__(self, exc_type, exc_value, traceback)
In Ihrem Beispiel oben, dann würden Sie verwenden
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)
Wenn dann jemand benutzen, um Ihre Klasse will, sie würden wie folgt vor:
with Package() as package_obj:
# use package_obj
Die Variable package_obj wird eine Instanz vom Typ Paket sein (es ist der Wert durch die __enter__
Methode zurückgegeben). Seine __exit__
Methode wird automatisch aufgerufen werden, unabhängig davon, ob eine Ausnahme auftritt.
Man könnte sogar nimmt diesen Ansatz einen Schritt weiter. In dem obigen Beispiel könnte jemand noch instanziiert Paket seinen Konstruktor ohne die with
-Klausel. Sie wollen nicht, dass das passiert. Sie können dieses Problem beheben, indem Sie eine PackageResource Klasse erstellen, die die __enter__
und __exit__
Methoden definiert. Dann würde die Paketklasse streng innerhalb der __enter__
Methode und zurück definiert werden. Auf diese Weise der Anrufer nie ohne Verwendung einer with
Anweisung, um die Paketklasse instanziiert könnte:
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()
Sie würden diese wie folgt verwenden:
with PackageResource() as package_obj:
# use package_obj
Andere Tipps
Die Standardmethode ist die Verwendung 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)
Aber Sie sollten bedenken, dass dies alle erstellten Instanzen von Package
bestehen bleiben, bis Python beendet wird.
Demo mit dem Code oben gespeichert als 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...
Als Anhang zum Clint Antwort , können Sie PackageResource
mit contextlib.contextmanager
:
@contextlib.contextmanager
def packageResource():
class Package:
...
package = Package()
yield package
package.cleanup()
Alternativ, wenn auch wahrscheinlich nicht so Pythonic Sie können Package.__new__
außer Kraft setzen:
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):
...
und einfach verwenden with Package(...) as package
.
Um die Dinge kürzer, nennen Sie Ihre Bereinigungsfunktion close
und verwenden contextlib.closing
, wobei in diesem Fall können Sie entweder die unmodifizierte Package
Klasse über with contextlib.closing(Package(...))
verwenden oder seine __new__
der einfacheren
class Package(object):
def __new__(cls, *args, **kwargs):
package = super(Package, cls).__new__(cls)
package.__init__(*args, **kwargs)
return contextlib.closing(package)
Und dieser Konstruktor vererbt wird, so können Sie einfach erben, z.
class SubPackage(Package):
def close(self):
pass
Ich glaube nicht, dass es möglich ist, zum Beispiel Mitglieder entfernt werden, bevor __del__
genannt wird. Meine Vermutung wäre, dass der Grund für Ihre Attribute woanders ist (vielleicht entfernen Sie versehentlich self.file an anderer Stelle).
Da jedoch die anderen darauf hingewiesen wird, sollten Sie die Verwendung __del__
vermeiden. Der Hauptgrund dafür ist, dass Fälle mit __del__
nicht Müll gesammelt werden (sie werden nur freigegeben werden, wenn ihre refcount erreicht 0) gewonnen. Wenn also die Instanzen in zirkulären Referenzen beteiligt sind, werden sie so lange, wie die Anwendung läuft in Erinnerung bleiben. (Ich kann über all diese allerdings irrt, würde ich wieder die gc-Dokumente lesen müssen, aber ich bin ziemlich sicher, dass es so funktioniert).
Ich denke, das Problem in __init__
sein könnte, wenn es mehr Code als gezeigt?
__del__
aufgerufen wird, selbst wenn __init__
wurde nicht richtig ausgeführt oder hat eine Ausnahme.
Eine bessere Alternative ist die Verwendung weakref.finalize . Beispiele finden Sie unter Finalizer Objekte und Vergleich Finalizers mit __del __ () Methoden
Just wickeln Sie Ihren destructor mit einem try / except-Anweisung, und es wird keine Ausnahme, wenn Ihr Globals wirft bereits entsorgt werden.
Bearbeiten
Versuchen Sie folgendes:
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
Es wird die Dateiliste in der del Funktion stopfen, die zum Zeitpunkt des Anrufs existieren garantiert. Der WeakRef Proxy ist Python zu verhindern, oder um sich vor dem Löschen des self.files Variable irgendwie (wenn es gelöscht wird, dann wird es nicht die Originaldatei Liste beeinflussen). Wenn es nicht der Fall ist, dass diese auch gelöscht werden, obwohl es mehr Hinweise auf die variabel sind, dann können Sie die Proxy-Verkapselung entfernen.
Hier ist ein minimales Arbeits Skelett:
class SkeletonFixture:
def __init__(self):
pass
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
pass
def method(self):
pass
with SkeletonFixture() as fixture:
fixture.method()
Wichtig: Rückkehr Selbst
Wenn Sie wie ich sind, und den return self
Teil übersehen (von Clint Miller richtigen Antwort ), werden Sie starre auf diesem Unsinn:
Traceback (most recent call last):
File "tests/simplestpossible.py", line 17, in <module>
fixture.method()
AttributeError: 'NoneType' object has no attribute 'method'
Ich verbrachte einen halben Tag zu diesem Thema. Hoffe, dass es die nächste Person hilft.
Es scheint, dass der idiomatische Weg, dies zu tun, ist eine close()
Methode (oder ähnliches) zur Verfügung zu stellen, und nennen Sie es explizit.