2014-02-15 2 views
2

У меня есть класс, объекты которого используют системные ресурсы (например, создавая временные файлы), которые необходимо очистить. По крайней мере, это должно произойти, когда программа закончится. До сих пор я использую contextmanager сделать это, напримерОбеспечить выпуск ресурсов в интерактивном сеансе

@contextlib.contextmanager 
def tempdir(prefix='tmp'): 
    tmpdir = tempfile.mkdtemp(prefix=prefix) 
    try: 
     yield tmpdir 
    finally: 
     shutil.rmtree(tmpdir) 

with tempdir() as tmp: 
    do_something(tmp) 

Это гарантирует, что ресурсы будут освобождены, как только я закончу с ними, и это хорошо работает для большинства моих целей. Тем не менее, я нахожу, что эта модель ломается при использовании python в интерактивном режиме, например, в ipython. В этом случае весь сеанс должен находиться внутри блока with, что не очень практично (фактически, ipython будет оценивать только блоки сразу, поэтому вся интерактивность исчезнет).

Есть ли способ обеспечить очистку ресурсов, оставаясь при этом доступным для использования в интерактивных сеансах? В C++ это может быть достигнуто путем помещения очистки в деструктор. Оператор python __del__ похож, но мне сказали, что __del__ является ненадежным и его следует избегать. В частности, это it is not guaranteed that __del__ will be called for objects that still exist when the interpreter exits .. Это один из случаев, когда __del__ по-прежнему является лучшим решением? И если подход contextmanager несовместим с интерактивными сеансами, почему он рекомендуется как самый «питонический» подход?

ответ

2

Я не думаю, что есть серебряная пуля для вашей проблемы, но вы можете как-то решить эту проблему с помощью:

  • метода явно вызывающего распоряжаться ресурсы (ваш метод close). Единственный недостаток, ну, явный.

  • создание тонкой обертки для интерактивного интерпретатора, которая регистрирует метод close экземпляра при выходе с использованием atexit module. Недостатком является то, что все ваши ресурсы будут выпущены обычно позже, чем вы хотели бы.

  • создание вспомогательных функций (их не сложно создать динамически), что обертывает использование ресурсов. Это невозможно, если вам нужно вызвать больше функций с одним ресурсом. Например:

    def do_something() 
        with tempdir() as tmp: 
         return original.do_something(tmp) 
    
  • создание решения, которое скроет управление ресурсами. Например, мне не нужны TCP-сокеты, перенаправление ssl, 301/302, открытие файла сертификата и т. Д., Мне просто нужно отправить запрос GET через https с использованием одного конкретного сертификата. Естественно, это зависит от проблемы, которую вы хотели бы решить.

1

Я в конечном итоге следующие atexit предложения prokopst, и определил этот класс декоратор:

import atexit 

_toclean_ = set() 
def call_exit_for_objects(objs): 
     """Calls __exit__ for all objects in objs, leaving objs empty.""" 
     while len(objs) > 0: 
       obj = objs.pop() 
       obj.__exit__(None,None,None) 
atexit.register(call_exit_for_objects, _toclean_) 

def autoclean(cls): 
     global _toclean_ 
     # Fail on purpose if __init__ and __exit__ don't exist. 
     oldinit = cls.__init__ 
     oldexit = cls.__exit__ 
     def newinit(self, *args, **kwargs): 
       oldinit(self, *args, **kwargs) 
       _toclean_.add(self) 
     def newexit(self, type, value, traceback): 
       try: _toclean_.remove(self) 
       except KeyError: pass 
       oldexit(self, type, value, traceback) 
     cls.__init__ = newinit 
     cls.__exit__ = newexit 
     return cls 

С этим я могу иметь класс, который поддерживает как with синтаксиса и интерактивные.Например, для класса TmpDir выше, я бы переопределить его как:

@autoclean 
class tempdir 
    def __init__(self, prefix='tmp'): 
     self.dir = tempfile.mkdtemp(prefix=prefix) 
    def close(self): shutil.rmtree(self.dir) 
    def __enter__(self): return self 
    def __exit__(self, type, value, traceback): self.close() 
    def __str__(self): return self.dir 

, а затем использовать его либо как:

with tempdir() as tmp: 
    do_something(tmp) 

или

tmp = tempdir() 
do_something(tmp) 
tmp.close() # If this is skipped, @autoclean ensures it 
       still happens when python exits 
Смежные вопросы