【问题标题】:Pythonic way to use context manager conditionally有条件地使用上下文管理器的 Pythonic 方式
【发布时间】:2015-11-26 16:28:12
【问题描述】:

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

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...

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

【问题讨论】:

  • 我想不出一个很好的理由来编写可以接受要么 路径 打开文件句柄的代码。在那种极端情况下,我建议您编写自己的包装器(就像您所做的那样)
  • 我认为辅助函数可能更加优雅。您在foo 中所做的很多工作是将参数放入正确类型的对象——打开的文件句柄。将执行核心工作的代码分解为假定打开文件句柄的帮助程序,我认为整体结果更加清晰。实际上,“helper”本身可能是一个合法的公共函数,“foo_from_path”在打开文件处理程序后简单地调用“foo”。
  • @AdamSmith 对于它的价值,这是一个常见的设计,比如说,numpy。例如,np.load 采用 string or a fileobj。我想它只是为了让一些代码更简洁,而且由于 numpy 经常被交互使用,这并不是不明智的。
  • @jme 感谢您的上下文! :)
  • Python 3.x 有更优雅的解决方案,见:stackoverflow.com/questions/41251850

标签: python python-2.7 contextmanager


【解决方案1】:

不过,我更喜欢它,我不知道它有多 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

【讨论】:

  • 如果一个打开的句柄作为输入给出并且在try:中抛出一个异常,这将关闭句柄。这很好,但我觉得有点奇怪——这个函数真的有权这样做吗?避免这种情况的一种方法——这就是 numpy 所做的——是通过布尔值f_own 跟踪函数是否“拥有”文件句柄。然后,在 finally: 块中,仅当 f_own 为 True 时才关闭。
【解决方案2】:

此解决方案避免了像 f_own 这样的显式布尔值(@kAlmAcetA 的答案的评论中提到了这一点),而是只检查输入参数 f 到文件句柄 fh 的身份。 try/finally 子句是在不创建帮助类作为上下文管理器的情况下执行此操作的唯一方法。

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

    try:
        # do stuff...
    finally:
        if fh is not f:
            fh.close()

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

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...

【讨论】:

    猜你喜欢
    • 2018-05-23
    • 2015-09-20
    • 1970-01-01
    • 2019-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-07
    • 1970-01-01
    相关资源
    最近更新 更多