2015-11-26 73 views
1

这是我认为必须经常提出的事情,但我一直无法找到一个好的解决方案。假设我有一个函数可以传递一个开放资源作为参数(如文件或数据库连接对象)或需要自己创建一个。如果函数需要打开自己的文件,最好的做法通常被认为是这样的:Pythonic方式有条件地使用上下文管理器

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

这工作,但除非有一个内置的对象,它这个(我不认为这是)我要么随处复制粘贴该对象,要么必须导入我的自定义实用程序模块。我觉得有一个更简单的方法可以做到这一点,我错过了。

+0

我想不出一个很好的理由来编写代码,将接受* *任意路径*或*打开文件句柄。在这种边缘情况下,我建议编写自己的包装(正如你所做的那样) –

+4

我认为辅助函数可能更优雅。你在'foo'中做的很多工作是将参数放到正确类型的对象中 - 一个打开的文件句柄。将执行核心工作的代码分解为一个假定打开文件句柄的助手,我认为总体结果更清晰。实际上,“助手”本身可能是一个合法的公共函数,“foo_from_path”在打开文件处理程序后只是简单地调用“foo”。 – jme

+1

@AdamSmith对于它的价值,这是一种常见的设计,比如numpy。例如,'np.load'采用[string或者fileobj](http://docs.scipy.org/doc/numpy-1.10.1/reference/generated/numpy.load.html)。我想这只是为了让一些代码更加简洁,而且因为numpy经常被交互使用,所以这不是不明智的做法。 – jme

回答

0

不过,我喜欢,我不知道它是多么Python的,但它是简单

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

问题可能与singledispatch蟒蛇3.4

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 
+0

如果一个打开的句柄是作为输入提供的,并且在'try:'内引发了一个异常,这将关闭句柄。这很好,但我觉得有点奇怪 - 该功能是否真的有权这样做?避免这种情况的一种方法 - 这就是numpy所做的 - 就是通过布尔型“f_own”来跟踪函数是否拥有文件句柄。然后,在'finally:'块中,只有当'f_own'为真时才关闭。 – jme

0

该解决方案避免了需要解决更好一个明确的布尔值,如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... 
相关问题