Pythonic有条件地使用上下文管理器的方法

时间:2015-11-26 00:07:22

标签: python python-2.7 contextmanager

这是我认为必须经常出现但我无法找到一个好的解决方案。假设我有一个函数可以作为参数传递一个开放资源(如文件或数据库连接对象)或需要自己创建一个。如果函数需要自己打开文件,最佳实践通常被认为是:

with open(myfile) as fh:
    # do stuff with open file handle...

确保在退出with块时始终关闭文件。但是,如果在函数中传递现有文件句柄,则可能不应自行关闭它。

考虑以下函数,它接受一个打开的文件对象一个字符串,给出该文件的路径作为其参数。如果传递文件路径,则应该如上所述。否则,应省略with语句。这导致重复的代码:

def foo(f):
    if isinstance(f, basestring):
        # Path to file, need to open
        with open(f) as fh:
            # do stuff with fh...
    else:
        # Assume open file
        fh = f
        # do the same stuff...

当然可以通过定义辅助函数并在两个地方调用它来避免这种情况,但这似乎不够优雅。我想到的一个更好的方法是定义一个包装对象的上下文管理器类:

class ContextWrapper(object):
    def __init__(self, wrapped):
        self.wrapped = wrapped
    def __enter__(self):
        return self.wrapped
    def __exit__(self, *args):
        pass

def foo(f):
    if isinstance(f, basestring):
        cm = open(f)
    else:
        cm = ContextWrapper(f)

    with cm as fh:
        # do stuff with fh...

这有效,但除非有一个内置对象执行此操作(我认为没有)我要么必须将该对象复制粘贴到任何地方,要么始终必须导入我的自定义实用程序模块。我觉得有一种更简单的方法可以做到这一点,我错过了。

2 个答案:

答案 0 :(得分:0)

然而,我更喜欢,我不知道它是如何pythonic,但它是直截了当的

def foo(f):
    if isinstance(f, basestring):
        f = open(f)
    try:
        # do the stuff
    finally:
        f.close()

使用python 3.4中的singledispatch可以更好地解决问题

from functools import singledispatch

@singledispatch
def foo(fd):
    with fd as f:
        # do stuff
        print('file')

@foo.register(str)
def _(arg):
    print('string')
    f = open(arg)
    foo(f)


foo('/tmp/file1')  # at first calls registered func and then foo
foo(open('/tmp/file2', 'r'))  # calls foo

答案 1 :(得分:0)

此解决方案避免使用类似f的显式布尔值(在@kAlmAcetA的答案的注释中提到),而只是检查输入参数fh的标识到文件句柄def foo(f): fh = open(f) if isinstance(f, basestring) else f try: # do stuff... finally: if fh is not f: fh.close() 。 try / finally子句是在不创建辅助类作为上下文管理器的情况下执行此操作的唯一方法。

class ContextWrapper(object):
    def __init__(self, file):
        self.f = file

    def __enter__(self):
        self.fh = open(self.f) if isinstance(self.f, basestring) else self.f
        return self.fh

    def __exit__(self, *args):
        if self.fh is not self.f:
            self.fh.close()

如果您需要在多个函数中执行此类操作,那么,是的,您可能应该创建一个带有上下文管理器类的实用程序模块来执行此操作,如下所示:

def foo(f):
    with ContextManager(f) as fh:
        # do stuff...

然后你可以无条件地包裹这样:

{{1}}