Как правильно очистить объект Python? - PullRequest
416 голосов
/ 14 мая 2009
class Package:
    def __init__(self):
        self.files = []

    # ...

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

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

Ответы [ 9 ]

549 голосов
/ 14 мая 2009

Я бы рекомендовал использовать оператор 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__ метод будет вызываться автоматически, независимо от того, происходит ли исключение.

Вы могли бы даже сделать этот подход еще дальше. В приведенном выше примере кто-то еще может создать экземпляр Package, используя его конструктор, не используя предложение with. Вы не хотите, чтобы это произошло. Это можно исправить, создав класс PackageResource, который определяет методы __enter__ и __exit__. Затем класс Package будет определен строго внутри метода __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
30 голосов
/ 13 января 2017

Стандартный способ - использовать 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...
27 голосов
/ 20 мая 2015

В качестве приложения к ответу Клинта вы можете упростить 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
16 голосов
/ 14 мая 2009

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

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

11 голосов
/ 29 ноября 2012

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

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

Источник

8 голосов
/ 20 марта 2017
8 голосов
/ 14 мая 2009

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

Редактировать

Попробуйте это:

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

5 голосов
/ 16 апреля 2018

Вот минимальный рабочий скелет:

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

Важно: вернуть себя


Если вы похожи на меня и пропускаете часть return self (из правильного ответа Клинта Миллера ), вы будете смотреть на эту чушь:

Traceback (most recent call last):
  File "tests/simplestpossible.py", line 17, in <module>                                                                                                                                                          
    fixture.method()                                                                                                                                                                                              
AttributeError: 'NoneType' object has no attribute 'method'

Я потратил на это полдня. Надеюсь, это поможет следующему человеку.

5 голосов
/ 14 мая 2009

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

...