Check if object is file-like in Python

File-like objects are objects in Python that behave like a real file, e.g. have a read() and a write method(), but have a different implementation from file. It is realization of the Duck Typing concept.

It is considered a good practice to allow a file-like object everywhere where a file is expected so that e.g. a StringIO or a Socket object can be used instead a real file. So it is bad to perform a check like this:

if not isinstance(fp, file):
raise something

What is the best way to check if an object (e.g. a parameter of a method) is "file-like"?

64854 次浏览

您可以尝试调用该方法,然后捕获异常:

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

如果你只想要一个读写方法,你可以这样做:

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

如果我是你,我会采用 try/other 方法。

这里的主要范例是 EAFP: 请求原谅比请求许可更容易。继续并使用文件接口,然后处理结果异常,或者让它们传播到调用方。

除非您有特殊的需求,否则在代码中使用这样的检查通常是不好的做法。

在 Python 中,类型是动态的,为什么您觉得需要检查对象是否类似于 file,而不是仅仅将它当作一个文件来使用并处理结果错误?

您可以做的任何检查都会在运行时发生,所以执行类似于 if not hasattr(fp, 'read')的操作并引发一些异常不会比仅仅调用 fp.read()和处理方法不存在时的结果属性错误提供更多的实用工具。

正如其他人所说,你应该避免这种检查。一个例外是,当对象可能合法地是不同的类型时,您希望根据类型有不同的行为。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 可能会导致各种各样的微妙问题,因为每个初始化路径在抛出异常之前都会部分运行。 从本质上来说,这个结构模仿了函数重载,所以不是很 Python,但是如果小心使用的话会很有用。

顺便说一句,在 Python3中不能用同样的方式执行文件检查。你需要像 isinstance(f, io.IOBase)这样的东西来代替。

在大多数情况下,处理这个问题的最好方法是。如果一个方法接受一个类似文件的对象,结果发现它传递的对象不是,那么当方法试图使用该对象时引发的异常的信息量并不比您可能显式引发的任何异常少。

但是,至少有一种情况可能需要进行这种检查,即对象没有被传递给的对象立即使用,例如,如果它在类的构造函数中被设置。在这种情况下,我认为 EAFP 原则被“快速失败”原则所压倒我会检查对象以确保它实现了我的类所需要的方法(并且它们是方法) ,例如:

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

通过检查条件来引发错误通常很有用,因为该错误通常要到很久以后才会引发。对于‘ user-land’和‘ api’代码之间的边界来说尤其如此。

你不会把一个金属探测器放在警察局的出口门上,你会把它放在入口处!如果不检查条件意味着可能会发生错误,这个错误可能在100行之前被捕获,或者在超类中而不是在子类中被引发,那么我认为检查没有什么错。

当您接受多个类型时,检查正确的类型也是有意义的。 比起仅仅因为某个变量没有‘ find’方法而引发一个异常,引发一个表示“ I need a subclass of basestring,OR file”的异常要好得多。

这并不意味着你在任何地方都要这样做,大多数情况下,我同意异常自身产生的概念,但是如果你能让你的 API 彻底清晰,或者避免不必要的代码执行,因为一个简单的条件没有得到满足,这样做!

对于3.1 + ,以下内容之一:

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

对于2.x,“文件类对象”太模糊,无法检查,但是您正在处理的函数的文档可能会告诉您它们实际需要什么; 如果不需要,请阅读代码。


正如其他答案所指出的,首先要问的是你到底在检查什么。通常情况下,EAFP 就足够了,而且更加地道。

术语表 说“ file-like object”是“ file object”的同义词,这最终意味着它是在 the io module中定义的三个 抽象基类中的一个的实例,这三个 抽象基类本身都是 IOBase的子类。因此,检查的方法正如上面所示。

(然而,检查 IOBase并不是很有用。您能想象这样一种情况吗? 在这种情况下,您需要区分一个实际的类似于 read(size)的文件和一个名为 read的不类似于文件的单参数函数,而不需要区分文本文件和原始二进制文件?因此,实际上,您几乎总是想要检查,例如,“ is a text file object”,而不是“ is a file-like object”。)


对于2.x,虽然 io模块从2.6 + 开始就存在,但是内置的文件对象并不是 io类的实例,stdlib 中也不存在任何类似文件的对象,而且您可能遇到的大多数第三方类似文件的对象也不存在。对于“类文件对象”的定义没有正式的定义; 它只是“类似于内置 文件对象的东西”,不同的函数通过“ like”表示不同的东西。这些函数应该记录它们的含义; 如果没有,则必须查看代码。

然而,最常见的含义是“ has read(size)”,“ has read()”,或者“ is an iterable of string”,但是一些旧的库可能希望使用 readline而不是其中之一,一些库喜欢使用你给它们的 close()文件,一些库希望如果 fileno存在,那么其他功能就可以使用,等等。同样,对于 write(buf)也是如此(尽管这方面的选择要少得多)。

当我在编写一个类似 open的函数,它可以接受文件名、文件描述符或预先打开的类似文件的对象时,我碰到了你的问题。

我没有像其他答案所建议的那样测试 read方法,而是最终检查对象是否可以打开。如果可以,它是一个字符串或描述符,并且我手头有一个有效的类似文件的对象从结果。如果 open引发 TypeError,则该对象已经是一个文件。

试试这个:

import os


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