2013-04-18 17 views
18

假設我想通過在openclose時間的額外操作來擴展內置文件抽象。在Python 2.7這個作品:在python中繼承文件對象(擴展打開和關閉操作)3

class ExtFile(file): 
    def __init__(self, *args): 
     file.__init__(self, *args) 
     # extra stuff here 

    def close(self): 
     file.close(self) 
     # extra stuff here 

現在我期待在更新程序到Python 3,其中open是一個工廠函數可能從取決於io模塊返回的任何幾種不同的類的實例它是如何被調用的。我原則上可以將所有這些分類,但這很乏味,我不得不重新實現open所做的調度。 (在Python 3中,二進制和文本文件之間的區別比2.x中的更重要,而且我需要兩者)。這些對象將被傳遞給庫代碼,可能會對它們做任何事情,所以成語製作一個類似「文件」的鴨式類,它包裝返回值爲open,並且轉發必要的方法將變得非常冗長。

任何人都可以提出一種3.x方法,它涉及到儘可能少的額外的樣板,超出所示的2.x代碼?

+0

也許不是擴展文件對象,而是使用'with'語句來使用自定義對象? –

+1

爲什麼封裝'open'返回值的類太糟糕了?您可以重寫'__getattr__'來批量轉發方法。 –

+0

@BenjaminHodgson它已經足夠長,我不記得我到底在想什麼,但它可能是沿着這樣的路線:「做90%的工作很容易,而且每個角落案例都很痛苦明確了。」我沒有在Python中使用對象自省做過很多事情,當我嘗試過時,它會以令人困惑的方式絆倒我。 – zwol

回答

14

您可以改爲使用上下文管理器。例如,這一個:

class SpecialFileOpener: 
    def __init__ (self, fileName, someOtherParameter): 
     self.f = open(fileName) 
     # do more stuff 
     print(someOtherParameter) 
    def __enter__ (self): 
     return self.f 
    def __exit__ (self, exc_type, exc_value, traceback): 
     self.f.close() 
     # do more stuff 
     print('Everything is over.') 

然後可以使用這樣的:

>>> with SpecialFileOpener('C:\\test.txt', 'Hello world!') as f: 
     print(f.read()) 

Hello world! 
foo bar 
Everything is over. 

使用上下文塊與with是優選的文件對象(和其他資源)反正。

+0

爲了迴應賞金要求:你真的不應該子類型的文件對象;不會直接創建不同的文件類型,而是由一些開啓者創建(例如'open','urlopen',...)。如果你想擴展功能,你應該提供一個自定義的* opener *來包裝類文件對象並提供額外的功能。 – poke

6

我有類似的問題,同時支持Python 2.x和3.x.我所做的是類似於以下(current full version):

class _file_obj(object): 
    """Check if `f` is a file name and open the file in `mode`. 
    A context manager.""" 
    def __init__(self, f, mode): 
     if isinstance(f, str): 
      self.file = open(f, mode) 
     else: 
      self.file = f 
     self.close_file = (self.file is not f) 
    def __enter__(self): 
     return self 
    def __exit__(self, *args, **kwargs): 
     if (not self.close_file): 
      return # do nothing 
     # clean up 
     exit = getattr(self.file, '__exit__', None) 
     if exit is not None: 
      return exit(*args, **kwargs) 
     else: 
      exit = getattr(self.file, 'close', None) 
      if exit is not None: 
       exit() 
    def __getattr__(self, attr): 
     return getattr(self.file, attr) 
    def __iter__(self): 
     return iter(self.file) 

它傳遞給底層的文件對象的所有調用,並且可以從打開的文件或文件名進行初始化。也可用作上下文管理器。靈感來自this answer

+0

這並沒有解決所問的問題;提供一個處理python2和python3的包裝器會分散注意力,因爲擴展python2文件對象已經相當微不足道了,這個問題被問到,因爲在python3中做同樣的事情顯然沒有太長的意義。 – ThorSummoner

+4

@ThorSummoner我認爲「做得比要求更多」與「不解決問題」不一樣。鑑於已經有答案,我認爲添加擴展版本不會有什麼壞處。答案解釋了代碼的其他功能。感謝您的反饋。 –

+0

我發現這個建議很有用。 –

10

tl; dr使用上下文管理器。請參閱此答案的底部以瞭解關於它們的重要注意事項。


雖然有可能在普通用戶類使用一些方法,這些方法不會隨內置類的工作文件已經在Python 3更復雜。一種方法是instanciating之前混合,在所需的類,但這需要知道什麼是混合類應該是第一:

class MyFileType(???): 
    def __init__(...) 
     # stuff here 
    def close(self): 
     # more stuff here 

因爲有這麼多種類,多的可能可能在被加入未來(不太可能,但可能),並且我們不確定哪些將被退回到之後呼叫open,此方法不起作用。

另一種方法是改變了我們的自定義類型有返回的文件的___bases__和修改返回實例的__class__屬性爲我們自定義類型:

class MyFileType: 
    def close(self): 
     # stuff here 

some_file = open(path_to_file, '...') # ... = desired options 
MyFileType.__bases__ = (some_file.__class__,) + MyFile.__bases__ 

,但是這會產生

Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: __bases__ assignment: '_io.TextIOWrapper' deallocator differs from 'object' 

然而,另一種可以使用純用戶類的方法是直接從返回的實例類中創建自定義文件類型,然後更新返回的實例類:

some_file = open(path_to_file, '...') # ... = desired options 

class MyFile(some_file.__class__): 
    def close(self): 
     super().close() 
     print("that's all, folks!") 

some_file.__class__ = MyFile 

卻又:

Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: __class__ assignment: only for heap types 

因此,它看起來好像是會在所有的工作在Python 3,幸運的是也將在Python 2工作的最佳方法(有用的,如果你想在同一代碼基地上兩個版本的工作)是有一個自定義的上下文管理器:

class Open(object): 
    def __init__(self, *args, **kwds): 
     # do custom stuff here 
     self.args = args 
     self.kwds = kwds 
    def __enter__(self): 
     # or do custom stuff here :) 
     self.file_obj = open(*self.args, **self.kwds) 
     # return actual file object so we don't have to worry 
     # about proxying 
     return self.file_obj 
    def __exit__(self, *args): 
     # and still more custom stuff here 
     self.file_obj.close() 
     # or here 

,並使用它:

with Open('some_file') as data: 
    # custom stuff just happened 
    for line in data: 
     print(line) 
# data is now closed, and more custom stuff 
# just happened 

重要的一點要記住:在__init____enter__任何未處理的異常將防止__exit__運行,所以在這兩個位置,你仍然需要使用try/except和/或try/finally成語,以確保你不不會泄漏資源。

+0

感謝您與我澄清這一點。在我的特殊情況下,我知道這些文件將始終是'io.TextIOWrapper'。擴展TextIOWrapper類是否相當可觀?如果我可以擴展並使用該擴展名,那麼我在哪裏處理不接收文本文件的情況? – ThorSummoner

+0

您是否在控制這些文件的打開位置,還是您還想跟蹤其他模塊打開的文件? –

+0

我控制整個文件的打開操作。 我想要實現的一箇中間步驟是添加一個方法來執行文件的hashsum。 – ThorSummoner