2015-11-26 50 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...