2010-12-28 2 views
3

Мне нужно зарегистрировать функцию atexit для использования с классом (см. Ниже пример Foo), что, к сожалению, у меня нет прямого способа очистки с помощью вызова метода: другой код, который я не имеют контроля, звонки Foo.start() и Foo.end(), но иногда не вызывает Foo.end(), если он сталкивается с ошибкой, поэтому мне нужно очистить себя.python: замыкания и классы

я мог бы использовать некоторые советы по закрытий в этом контексте:

class Foo: 
    def cleanup(self): 
    # do something here 
    def start(self): 
    def do_cleanup(): 
     self.cleanup() 
    atexit.register(do_cleanup) 
    def end(self): 
    # cleanup is no longer necessary... how do we unregister? 
  • Будет ли закрытие работы должным образом, например, в do_cleanup, является ли значение правильной привязки?

  • Как я могу отменить регистрацию процедуры atexit()?

  • Есть ли лучший способ сделать это?

редактировать: это Python 2.6.5

+0

Я надеюсь, что это Python 3, иначе не наследующий от 'object' дает вам один из тех страшных классов старого стиля ... – delnan

+0

old-style? [царапины головы], пожалуйста, уточните, чтобы я мог избежать ошибок. –

+0

классы старого стиля являются классами до того, как классы времени и типы были унифицированы, если вы не наследуете от 'object', вы получаете старый класс стиля. –

ответ

4

Сделать регистрирующий орган глобальный реестр и функцию, которая вызывает в ней функцию, и удалять их там, когда это необходимо.

cleaners = set() 

def _call_cleaners(): 
    for cleaner in list(cleaners): 
     cleaner() 

atexit.register(_call_cleaners) 

class Foo(object): 
    def cleanup(self): 
    if self.cleaned: 
     raise RuntimeError("ALREADY CLEANED") 
    self.cleaned = True 
    def start(self): 
    self.cleaned = False 
    cleaners.add(self.cleanup) 
    def end(self): 
    self.cleanup() 
    cleaners.remove(self.cleanup) 
+0

Это не работает: вы попробуете «Установить измененный размер во время итерации». Вам просто нужно изменить цикл for на 'for clean in list (cleaners)', и тогда это должно быть хорошо. – Duncan

1

Я думаю, что код хорошо. Там нет никакого способа, чтобы отменить, но вы можете установить булево флаг, который будет отключить очистку:

class Foo: 
     def __init__(self): 
     self.need_cleanup = True 
     def cleanup(self): 
     # do something here 
     print 'clean up' 
     def start(self): 
     def do_cleanup(): 
      if self.need_cleanup: 
       self.cleanup() 
     atexit.register(do_cleanup) 
     def end(self): 
     # cleanup is no longer necessary... how do we unregister? 
     self.need_cleanup = False 

Наконец, имейте в виду, что atexit обработчики не вызываются, если «the program is killed by a signal not handled by Python, when a Python fatal internal error is detected, or when os._exit() is called.»

+0

+1 Закрытие само по себе довольно бесполезно, поскольку вы можете просто выполнить 'atexit.register (self.cleanup)' непосредственно. –

+2

-1. Это приведет к утечке записей в таблицу atexit; если миллион этих объектов будет создан, миллион элементов будет оставлен в таблице atexit постоянно. Это не нормально. –

+2

Это также приведет к утечке ссылок на сам объект, что также определенно не очень хорошо. –

2

self правильно связаны внутри обратного вызова для do_cleanup, но на самом деле, если все, что вы делаете вызов метода вы можете также использовать связанный метод непосредственно.

Вы используете atexit.unregister() для удаления обратного вызова, но здесь есть уловка, поскольку вы должны отменить регистрацию той же функции, которую вы зарегистрировали, и поскольку вы использовали вложенную функцию, что означает, что вам нужно сохранить ссылку на эту функцию. Если вы будете следовать моему предложению использовать связанный метод, то вам все равно придется сохранить ссылку на него:

class Foo: 
    def cleanup(self): 
    # do something here 
    def start(self): 
    self._cleanup = self.cleanup # Need to save the bound method for unregister 
    atexit.register(self._cleanup) 
    def end(self): 
    atexit.unregister(self._cleanup) 

Обратите внимание, что это все еще возможно для вашего кода для выхода без вызова þér atexit зарегистрированных функций, например, если процесс прерывается ctrl + break на окнах или убит с помощью SIGABRT на linux.

Также как другой ответ предполагает, что вы можете просто использовать __del__, но это может быть проблематично для очистки во время выхода программы, поскольку она не может быть вызвана до тех пор, пока не будут удалены другие глобальные переменные, необходимые для доступа.

Отредактировано примечание, что когда я написал этот ответ, в вопросе не было указано Python 2.x. Хорошо, я оставлю здесь ответ, если это поможет кому-то еще.

+1

Обратите внимание, что atexit.unregister существует только в Python 3. –

1

Почему бы вам не попробовать? Мне потребовалось всего минуту, чтобы проверить.

(Ответ: Да)

Однако, вы можете упростить.Закрытие не требуется.

class Foo: 
    def cleanup(self): 
     pass 
    def start(self): 
     atexit.register(self.cleanup) 

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

2

С shanked удалил его объявление, я буду говорить в пользу __del__ снова:

import atexit, weakref 
class Handler: 
    def __init__(self, obj): 
     self.obj = weakref.ref(obj) 
    def cleanup(self): 
     if self.obj is not None: 
      obj = self.obj() 
      if obj is not None: 
       obj.cleanup() 

class Foo: 
    def __init__(self): 
     self.start() 

    def cleanup(self): 
     print "cleanup" 
     self.cleanup_handler = None 

    def start(self): 
     self.cleanup_handler = Handler(self) 
     atexit.register(self.cleanup_handler.cleanup) 

    def end(self): 
     if self.cleanup_handler is None: 
      return 
     self.cleanup_handler.obj = None 
     self.cleanup() 

    def __del__(self): 
     self.end() 

a1=Foo() 
a1.end() 
a1=Foo() 
a2=Foo() 
del a2 
a3=Foo() 
a3.m=a3 

Это поддерживает следующие случаи:

  • объекты, где .end, называется регулярно; очистка сразу
  • объекты, выпущенные без вызова; очистка при последнем ссылка не работает
  • объекты, живущие в циклах; очистка atexit
  • объекты, которые хранятся в живых; Очистка atexit

Обратите внимание, что это важно, что обработчик очистки имеет слабую ссылку на объект, как это было бы в противном случае сохранить объект живой.

Редактировать: Циклы, связанные с Foo, не будут собраны в мусор, поскольку Foo реализует __del__. Чтобы разрешить удаление цикла во время сбора мусора, очистка должна быть выведена из цикла.

class Cleanup: 
    cleaned = False 
    def cleanup(self): 
     if self.cleaned: 
      return 
     print "cleanup" 
     self.cleaned = True 
    def __del__(self): 
     self.cleanup() 

class Foo: 
    def __init__(self):... 
    def start(self): 
     self.cleaner = Cleanup() 
     atexit.register(Handler(self).cleanup) 
    def cleanup(self): 
     self.cleaner.cleanup() 
    def end(self): 
     self.cleanup() 

Важно, что объект Cleanup не имеет ссылок на Foo.

+0

Он также сохранит объекты в живых, если есть циклическая ссылка в любом месте. Если вы собираетесь использовать '__del__' хотя бы для того, чтобы этот объект Handler имел метод' __del__', а не 'Foo', и сохранил слабую ссылку на объект Foo, чтобы объект' Handler' удалялся, когда исходный объект получает удален. –

+0

@ Rosh Oxymoron: нет, с Handler реализовать '__del__' не годится.Эти объекты остаются до выхода, поскольку это обработчик atexit. Вы правы, что объект не будет собираться мусором в цикле. Чтобы поддержать это, вам нужен другой объект, который выполняет очистку, см. Мое редактирование. –

 Смежные вопросы

  • Нет связанных вопросов^_^