检查对象是否为文件对象(file-like)在Python中
10 个回答
一般来说,代码里加这种检查并不是个好习惯,除非你有特别的需求。
在Python中,类型是动态的,为什么你觉得有必要检查一个对象是否像文件一样,而不是直接把它当作文件来用,并处理可能出现的错误呢?
你能做的任何检查最终都是在运行时进行的,所以像这样写 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中并不是很“Pythonic”,但如果小心使用,它还是可以派上用场的。
顺便提一下,在Python 3中,你不能用同样的方式进行文件检查。你需要使用像 isinstance(f, io.IOBase)
这样的方式。
对于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)
也是类似的(尽管在这方面的选择少得多)。