Подходит ли контекстный менеджер для этой работы? - PullRequest
5 голосов
/ 23 ноября 2011

Код, вставленный ниже, делает следующее:

  • создает ловушку для импорта
  • создает менеджер контекста, который устанавливает meta_path и очищает при выходе.
  • сбрасывает весь импорт, выполненный программой, переданной во входных данных в import.log

Теперь мне стало интересно, является ли использование контекстного менеджера хорошей идеей в этом случае, потому что на самом деле у меня нет стандартного потока try/finally, а просто настройка и очистка.

Другое дело - с этой строкой:

with CollectorContext(cl, sys.argv, 'imports.log') as cc:

cc становится None? Разве это не должен быть CollectorContext объект?

from __future__ import with_statement
import os
import sys

class CollectImports(object):
    """
    Import hook, adds each import request to the loaded set and dumps
    them to file
    """

    def __init__(self):
        self.loaded = set()

    def __str__(self):
        return str(self.loaded)

    def dump_to_file(self, fname):
        """Dump the loaded set to file
        """
        dumped_str = '\n'.join(x for x in self.loaded)
        open(fname, 'w').write(dumped_str)

    def find_module(self, module_name, package=None):
        self.loaded.add(module_name)


class CollectorContext(object):
    """Sets the meta_path hook with the passed import hook when
    entering and clean up when exiting
    """

    def __init__(self, collector, argv, output_file):
        self.collector = collector
        self.argv = argv
        self.output_file = output_file

    def __enter__(self):
        self.argv = self.argv[1:]
        sys.meta_path.append(self.collector)

    def __exit__(self, type, value, traceback):
        # TODO: should assert that the variables are None, otherwise
        # we are quitting with some exceptions
        self.collector.dump_to_file(self.output_file)
        sys.meta_path.remove(self.collector)


def main_context():
    cl = CollectImports()

    with CollectorContext(cl, sys.argv, 'imports.log') as cc:
        progname = sys.argv[0]
        code = compile(open(progname).read(), progname, 'exec')
        exec(code)


if __name__ == '__main__':
    sys.argv = sys.argv[1:]
    main_context()

Ответы [ 3 ]

3 голосов
/ 23 ноября 2011

Я думаю, что эта концепция в порядке. Кроме того, я не вижу каких-либо причин против использования элементов очистки в предложении finally:, поэтому менеджер контекста идеально подходит.

Ваш cc равен None, потому что вы сказали, что это так.

Если вы не хотите этого, измените метод __enter__, чтобы он возвращал что-то еще :

Значение, возвращаемое этим методом, привязывается к идентификатору в предложении as операторов with, использующих этот менеджер контекста.

def __enter__(self):
    self.argv = self.argv[1:]
    sys.meta_path.append(self.collector)
    return self
    # or
    return self.collector
    # or
    return "I don't know what to return here"

, а затем

with CollectorContext(cl, sys.argv, 'imports.log') as cc:
    print cc, repr(cc) # there you see what happens.
    progname = sys.argv[0]
    code = compile(open(progname).read(), progname, 'exec')
    exec(code)
2 голосов
/ 23 ноября 2011

Если вы всегда хотите, чтобы очистка происходила, вы должны использовать менеджер контекста. Я не уверен, где вы используете try..finally, если вы реализуете менеджер контекста, используя низкоуровневые специальные методы. Если вы используете декоратор @ contextmanager , вы кодируете менеджер контекста «естественным» способом, поэтому вы используете try..finally вместо получения исключения в качестве параметра.

Кроме того, cc будет значением, которое вы вернете из __enter__(). В вашем случае None. Насколько я понимаю, дизайн менеджера контекста заключается в том, что возвращаемое значение является «контекстом». Менеджер контекста устанавливает и очищает контексты, в которых происходит что-то еще. Например. соединение с базой данных будет создавать транзакции, и операции с базой данных выполняются в рамках этих транзакций.

Тем не менее, вышесказанное просто для максимальной гибкости. Нет ничего плохого в том, что вы просто создаете контекст (который управляет собой) напрямую и возвращаете self, или даже не возвращаете ничего, если вам не нужно использовать значение контекста внутри with. Поскольку вы нигде не используете cc, вы можете просто сделать это и не беспокоиться о возвращаемом значении:

with CollectorContext(cl, sys.argv, 'imports.log'):
        progname = sys.argv[0]
        code = compile(open(progname).read(), progname, 'exec')
        exec(code)
1 голос
/ 23 ноября 2011

Спасибо всем, теперь все работает гладко, я на самом деле хотел вернуть что-то, потому что я хотел инкапсулировать "запуск" внутри диспетчера контекста, поэтому я получаю что-то, как показано ниже.

Более того, теперь я сохраняюстарый sys.argv и восстановить его при выходе, вероятно, это не принципиально, но все же я думаю, что это хорошая вещь ..

class CollectorContext(object):
    """Sets the meta_path hook with the passed import hook when
    entering and clean up when exiting
    """

    def __init__(self, collector, argv, output_file):
        self.collector = collector
        self.old_argv = argv[:]
        self.output_file = output_file
        self.progname = self.old_argv[1]

    def __enter__(self):
        sys.argv = self.old_argv[1:]
        sys.meta_path.append(self.collector)
        return self

    def __exit__(self, type, value, traceback):
        # TODO: should assert that the variables are None, otherwise
        # we are quitting with some exceptions
        self.collector.dump_to_file(self.output_file)
        sys.meta_path.remove(self.collector)
        sys.argv = self.old_argv[:]

    def run(self):
        code = compile(open(self.progname).read(), self.progname, 'exec')
        exec(code)


def main_context():
    cl = CollectImports()

    with CollectorContext(cl, sys.argv, 'imports.log') as cc:
        cc.run()
...