питон: замыкания и классы - PullRequest
3 голосов
/ 28 декабря 2010

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

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

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, правильно ли значение self связано?

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

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

edit : это Python 2.6.5

Ответы [ 5 ]

4 голосов
/ 28 декабря 2010

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

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)
2 голосов
/ 28 декабря 2010

Поскольку 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
  • объектов, которые остаются живыми;cleanup 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()

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

2 голосов
/ 28 декабря 2010

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)

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

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

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

1 голос
/ 28 декабря 2010

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

(Ответ: Да)

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

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

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

1 голос
/ 28 декабря 2010

Я думаю, что код в порядке. Отменить регистрацию невозможно, но вы можете установить логический флаг, который отключит очистку:

    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 не вызываются, если " программа завершена сигналом, не обработанным Python, при обнаружении фатальной внутренней ошибки Python или при os._exit ( ) называется."

...