Как правильно очистить объект Python?

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

    # ...

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

__del__(self) выше не выполняется с помощью исключения AttributeError. Я понимаю Python не гарантирует существование "глобальных переменных" (данные члена в этом контексте?) При вызове __del__(). Если это так, и это причина исключения, как я могу убедиться, что объект деструктурируется правильно?

Ответ 1

Я бы рекомендовал использовать оператор Python with для управления ресурсами, которые необходимо очистить. Проблема с использованием явного выражения close() заключается в том, что вам приходится беспокоиться о том, что люди вообще забывают позвонить или забыть поместить его в блок finally, чтобы предотвратить утечку ресурса при возникновении исключения.

Чтобы использовать оператор with, создайте класс со следующими методами:

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

В приведенном выше примере вы должны использовать

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)

Затем, когда кто-то хотел использовать ваш класс, они сделали бы следующее:

with Package() as package_obj:
    # use package_obj

Переменная package_obj будет экземпляром типа Package (это значение, возвращаемое методом __enter__). Его метод __exit__ будет вызываться автоматически, независимо от того, происходит ли исключение.

Вы даже можете сделать такой подход еще дальше. В приведенном выше примере кто-то все еще может создавать пакет с помощью своего конструктора без использования предложения with. Вы не хотите, чтобы это произошло. Вы можете исправить это, создав класс PackageResource, который определяет методы __enter__ и __exit__. Затем класс пакета будет определен строго внутри метода __enter__ и возвращен. Таким образом, вызывающий абонент никогда не мог создать экземпляр класса Package без использования инструкции with:

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()

Вы использовали бы это следующим образом:

with PackageResource() as package_obj:
    # use package_obj

Ответ 2

В качестве приложения к клиенту answer вы можете упростить PackageResource с помощью contextlib.contextmanager:

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

Альтернативно, хотя, вероятно, не как Pythonic, вы можете переопределить 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):
        ...

и просто используйте with Package(...) as package.

Чтобы сократить время, назовите функцию очистки close и используйте contextlib.closing, и в этом случае вы можете либо использовать немодифицированный Package через with contextlib.closing(Package(...)) или переопределить его __new__ на более простой

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

И этот конструктор наследуется, поэтому вы можете просто наследовать, например.

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

Ответ 3

Я не думаю, что возможно, чтобы члены были удалены до вызова __del__. Я предполагаю, что причина для вашего конкретного AttributeError находится где-то в другом месте (возможно, вы ошибочно удаляете self.file в другом месте).

Однако, как указывали другие, вам следует избегать использования __del__. Основной причиной этого является то, что экземпляры с __del__ не будут собирать мусор (они будут освобождены только тогда, когда их refcount достигнет 0). Поэтому, если ваши экземпляры задействованы в циклических ссылках, они будут жить в памяти до тех пор, пока приложение запускается. (Возможно, я ошибаюсь во всем этом, мне придется снова прочитать gc docs, но я уверен, что он работает так).

Ответ 4

Я думаю, проблема может быть в __init__, если есть больше кода, чем показано?

__del__ будет вызываться даже тогда, когда __init__ не был выполнен правильно или выбрал исключение.

Источник

Ответ 5

Стандартный способ - использовать 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)

Но вы должны иметь в виду, что это сохранит все созданные экземпляры Package до тех пор, пока Python не будет завершен.

Демо с использованием приведенного выше кода, сохраненного как 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...

Ответ 6

Просто оберните свой деструктор с помощью инструкции try/except, и он не будет генерировать исключение, если ваши глобальные массивы уже удалены.

Edit

Попробуйте следующее:

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

Он заполнит список файлов в функции del, которая, как гарантируется, будет существовать во время разговора. Прокси-сервер weakref должен помешать Python или самостоятельно удалить переменную self.files(если она удалена, то это не повлияет на исходный список файлов). Если это не так, что это удаляется, хотя есть больше ссылок на переменную, вы можете удалить инкапсуляцию прокси.

Ответ 7

Кажется, что идиоматический способ сделать это - предоставить метод close() (или аналогичный) и называть его явно.