检查对象是否为文件对象(file-like)在Python中

121 投票
10 回答
71858 浏览
提问于 2025-04-15 15:35

类文件对象是Python中的一种对象,它们的行为像真实的文件,比如有读(read())和写(write())的方法,但它们的实现方式和file不同。这是实现鸭子类型概念的一种方式。

在需要文件的地方,允许使用类文件对象被认为是一种好习惯,这样就可以用StringIO或Socket对象来代替真实的文件。因此,像下面这样进行检查是不好的:

if not isinstance(fp, file):
   raise something

检查一个对象(比如方法的参数)是否是“类文件对象”的最佳方法是什么?

10 个回答

41

一般来说,代码里加这种检查并不是个好习惯,除非你有特别的需求。

在Python中,类型是动态的,为什么你觉得有必要检查一个对象是否像文件一样,而不是直接把它当作文件来用,并处理可能出现的错误呢?

你能做的任何检查最终都是在运行时进行的,所以像这样写 if not hasattr(fp, 'read') 并抛出异常,其实比直接调用 fp.read() 并处理可能出现的属性错误要没什么用。

46

正如其他人所说,通常情况下你应该避免这种检查。有一个例外,就是当对象可能确实是不同类型时,你希望根据类型有不同的行为。在这种情况下,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中并不是很“Pythonic”,但如果小心使用,它还是可以派上用场的。

顺便提一下,在Python 3中,你不能用同样的方式进行文件检查。你需要使用像 isinstance(f, io.IOBase) 这样的方式。

132

对于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类的实例,标准库中的任何类文件对象也不是,大多数你可能遇到的第三方类文件对象也不是。没有官方定义“类文件对象”是什么意思;它只是“像内置的文件对象那样的东西”,而不同的函数对“像”有不同的理解。这些函数应该在文档中说明它们的意思;如果没有,你就得看看代码。

不过,最常见的意思是“有read(size)方法”、“有read()方法”或者“是字符串的可迭代对象”,但一些旧库可能会期待readline而不是其中之一,有些库喜欢在你给它们文件时调用close(),有些则会期待如果fileno存在,那么其他功能也可用,等等。对于write(buf)也是类似的(尽管在这方面的选择少得多)。

撰写回答