这个工作适合用上下文管理器吗?
下面的代码做了以下几件事:
- 创建了一个导入钩子
- 创建了一个上下文管理器,用来设置
meta_path
,并在退出时进行清理。 - 将输入程序中所有的导入记录到 imports.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 个回答
如果你总是希望在使用完某些资源后能自动清理,那你应该使用上下文管理器。我不太确定如果你用低级的特殊方法来实现上下文管理器,在哪里会用到 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)
我觉得这个概念挺好的。而且,我也看不出有什么理由反对在 finally:
这个部分放入清理的代码,所以使用上下文管理器是非常合适的。
你的 cc
现在是 None
,因为你就是这样设置的。
如果你不想这样,那就需要修改你的 __enter__
方法,让它返回其他的东西:
这个方法返回的值会绑定到使用这个上下文管理器的
with
语句中的as
部分的标识符上。
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)
谢谢大家,现在它运行得很顺利。其实我想让它返回一些东西,因为我想把“运行”这个过程放在上下文管理器里面,这样我就能得到下面这样的结果。
而且,现在我把旧的 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()