簡體   English   中英

在 Python 中檢查對象是否類似於文件

[英]Check if object is file-like in Python

類文件對象是 Python 中的對象,其行為類似於真實文件,例如具有 read() 和 write method(),但與file具有不同的實現。 它是鴨子打字概念的實現。

在需要文件的任何地方都允許類似文件的對象被認為是一種很好的做法,這樣例如可以使用StringIO或 Socket 對象代替真實文件。 所以執行這樣的檢查是不好的:

if not isinstance(fp, file):
   raise something

檢查對象(例如方法的參數)是否“類文件”的最佳方法是什么?

對於 3.1+,以下選項之一:

isinstance(something, io.TextIOBase)
isinstance(something, io.BufferedIOBase)
isinstance(something, io.RawIOBase)
isinstance(something, io.IOBase)

對於 2.x,“類文件對象”太模糊,無法檢查,但是您正在處理的任何函數的文檔都有望告訴您它們實際需要什么; 如果沒有,請閱讀代碼。


正如其他答案所指出的那樣,首先要問的是您正在檢查什么。 通常,EAFP 就足夠了,而且更加地道。

術語表說“類文件對象”是“文件對象”的同義詞,這最終意味着它是io模塊中定義的三個抽象基類之一的實例,它們本身都是IOBase子類。 所以,檢查的方法完全如上圖所示。

(但是,檢查IOBase不是很有用。你能想象這樣一種情況,你需要區分一個實際的類似文件的read(size)和一些名為read單參數函數,而不是像文件一樣的,而不需要區分文本文件和原始二進制文件?所以,實際上,您幾乎總是想檢查,例如,“是一個文本文件對象”,而不是“是一個類似文件的對象”。)


對於 2.x,雖然io模塊自 2.6+ 以來就存在,但內置文件對象不是io類的實例,也不是 stdlib 中的任何類文件對象,也不是大多數第三方類文件對象您可能會遇到的對象。 “類文件對象”的含義沒有官方定義; 它只是“類似於內置文件對象的東西”,不同的功能通過“like”意味着不同的東西。 這些功能應該記錄它們的含義; 如果他們不這樣做,您必須查看代碼。

然而,最常見的含義是“已read(size) ”、“已read() ”或“是一個可迭代的字符串”,但一些舊庫可能期望readline而不是其中之一,有些庫喜歡close()文件,有些人會期望如果fileno存在,則其他功能可用,等等。類似的write(buf) (盡管在這個方向上的選項要少得多)。

正如其他人所說,您通常應該避免此類檢查。 一個例外是當對象可能合法地是不同的類型並且您希望根據類型有不同的行為時。 EAFP 方法在這里並不總是有效,因為一個對象可能看起來不止一種類型的鴨子!

例如,初始化程序可以采用文件、字符串或它自己的類的實例。 然后你可能有這樣的代碼:

class A(object):
    def __init__(self, f):
        if isinstance(f, A):
            # Just make a copy.
        elif isinstance(f, file):
            # initialise from the file
        else:
            # treat f as a string

在這里使用 EAFP 可能會導致各種微妙的問題,因為每個初始化路徑在拋出異常之前都會部分運行。 本質上,這種構造模仿了函數重載,因此不是很 Pythonic,但如果小心使用它會很有用。

作為旁注,您不能在 Python 3 中以相同的方式進行文件檢查。您需要使用isinstance(f, io.IOBase)類的東西。

除非您有特殊要求,否則在您的代碼中進行這樣的檢查通常不是一個好習慣。

在 Python 中,類型是動態的,為什么您覺得需要檢查對象是否與文件類似,而不是像使用文件一樣使用它並處理由此產生的錯誤?

無論如何,您可以做的任何檢查都將在運行時發生,因此執行if not hasattr(fp, 'read')並引發一些異常提供的實用程序比僅調用fp.read()並處理產生的屬性錯誤(如果方法不存在。

這里的主導范式是 EAFP:請求寬恕比許可更容易。 繼續使用文件接口,然后處理產生的異常,或者讓它們傳播給調用者。

通過檢查條件來引發錯誤通常很有用,因為該錯誤通常要到很久以后才會引發。 對於“user-land”和“api”代碼之間的邊界尤其如此。

你不會在警察局門口放一個金屬探測器,你會把它放在入口處! 如果不檢查條件意味着可能會發生一個錯誤,該錯誤可能會在 100 行之前被捕獲,或者在超類中而不是在子類中引發,那么我說檢查沒有任何問題。

當您接受不止一種類型時,檢查正確的類型也很有意義。 最好引發一個異常,說“我需要一個基字符串的子類,或文件”,而不是僅僅引發一個異常,因為某些變量沒有“尋求”方法......

這並不意味着你會發瘋並到處這樣做,在大多數情況下,我同意異常引發的概念,但是如果你能讓你的 API 非常清晰,或者避免不必要的代碼執行,因為一個簡單的條件沒有得到滿足這樣做!

您可以嘗試調用該方法然后捕獲異常:

try:
    fp.read()
except AttributeError:
    raise something

如果你只想要一個讀和寫方法,你可以這樣做:

if not (hasattr(fp, 'read') and hasattr(fp, 'write')):
   raise something

如果我是你,我會使用 try/except 方法。

當我編寫一個類似open的函數可以接受文件名、文件描述符或預先打開的類文件對象時,我最終遇到了你的問題。

正如其他答案所暗示的那樣,我沒有測試read方法,而是檢查了對象是否可以打開。 如果可以,它是一個字符串或描述符,並且我手頭有一個有效的類似文件的對象。 如果open引發TypeError ,則該對象已經是一個文件。

在大多數情況下,處理這個問題的最好方法是不要。 如果一個方法接受一個類似文件的對象,而事實證明它傳遞的對象不是,則該方法嘗試使用該對象時引發的異常並不比您可能明確引發的任何異常信息少。

但是,至少在一種情況下,您可能想要進行這種檢查,那就是當對象沒有立即被您傳遞給它的對象使用時,例如,如果它是在類的構造函數中設置的。 在那種情況下,我會認為 EAFP 的原則被“快速失敗”的原則壓倒了。 我會檢查對象以確保它實現了我的類需要的方法(並且它們是方法),例如:

class C():
    def __init__(self, file):
        if type(getattr(file, 'read')) != type(self.__init__):
            raise AttributeError
        self.file = file

試試這個:

import os 

if os.path.isfile(path):
   'do something'

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM