检查对象在Python中是否像文件一样
像文件一样的对象是Python中的对象,像一个真实的文件,例如有一个read()和一个write方法(),但是具有不同的实现。 这是鸭打字概念的实现。
在所有需要文件的地方都允许类似文件的对象被认为是好的做法,例如可以使用一个StringIO或一个Socket对象来代替一个真实的文件。 所以执行这样的检查是不好的:
if not isinstance(fp, file): raise something
检查一个对象(例如一个方法的参数)是否“像文件一样”的最好方法是什么?
除非你有特殊的要求,否则在你的代码中进行这样的检查通常是不好的做法。
在Python中,键入是dynamic的,为什么你觉得需要检查对象是否像文件一样,而不是像使用文件一样使用它并处理结果错误?
任何你可以做的检查都会在运行时发生,所以if not hasattr(fp, 'read')
并引发一些exception,只会调用fp.read()
并处理结果属性错误方法不存在。
正如其他人所说,你通常应该避免这种检查。 一个例外是,当对象可能是合法的不同types,你需要不同的行为取决于types。 EAFP方法并不总是在这里工作,因为一个物体可能看起来不止一种鸭子!
例如,一个初始化程序可以采用一个文件,string或其自己的类的实例。 你可能会有这样的代码:
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可能会导致各种微妙的问题,因为每个初始化path在抛出exception之前都会部分运行。 本质上这种结构模仿function重载,所以不是非常的Pythonic,但如果小心使用它可能是有用的。
作为一个方面说明,你不能在Python 3中以相同的方式进行文件检查。你将需要类似isinstance(f, io.IOBase)
东西。
这里的主要范式是EAFP:比许可更容易要求宽恕。 继续使用文件接口,然后处理结果exception,或让它们传播给调用者。
对于3.1+,以下之一:
isinstance(something, io.TextIOBase) isinstance(something, io.BufferedIOBase) isinstance(something, io.RawIOBase) isinstance(something, io.IOBase)
对于2.x来说,“类文件对象”是一个太模糊的东西来检查,但是你处理的任何函数的文档都会告诉你他们实际需要什么; 如果没有,请阅读代码。
正如其他答案指出的,首先要问的是你究竟在检查什么。 通常情况下,EAFP是足够的,而且更习惯。
术语表中说“类文件对象”是“文件对象”的同义词,最终意味着它是io
模块中定义的三个抽象基类之一的一个实例,它们本身都是IOBase
子类。 所以,检查的方式与上面所示完全相同。
(但是,检查IOBase
并不是非常有用,你能想象一种情况,你需要区分一个实际的文件types的read(size)
和一个名为read
的非一个文件types的单参数函数,而不需要区分文本文件和原始二进制文件?所以,实际上,你几乎总是要检查,例如,“是文本文件对象”,而不是“文件类对象”。)
对于2.x, io
模块自2.6+以来已经存在,而内置的文件对象不是io
类的实例,stdlib中也没有任何文件类对象,也不是大多数第三方文件类你可能遇到的对象。 关于“文件类对象”的含义没有正式的定义; 它只是“像内build文件对象 ”,不同的function是指“喜欢”不同的东西。 这些职能应该logging他们的意思; 如果他们不这样做,你必须看代码。
然而,最常见的含义是“已read(size)
”,“已read()
”或“是一个string的迭代”,但一些老图书馆可能期望readline
而不是其中的一个,一些图书馆喜欢close()
文件给你,有些人会认为,如果fileno
存在,那么其他function是可用的,等等。同样write(buf)
(虽然在这方面有很多的select)。
通过检查一个条件来提出一个错误通常是很有用的,当这个错误通常不会被提出,直到很久以后。 这对于“user-land”和“api”代码之间的边界尤其如此。
你不会把金属探测器放在出口的警察局,你可以把它放在入口处! 如果不检查一个条件意味着可能会发生错误,可能已经被捕获了100行,或在一个超级类,而不是在子类中提出,那么我说没有错检查。
当您接受多种types时,检查正确的types也是有意义的。 最好是抛出一个exception,说“我需要basetring或OR文件的子类”,而不是仅仅抛出exception,因为某些variables没有“seek”方法。
这并不意味着你疯了,到处都是,大多数情况下我赞同exception提升的概念,但是如果你能够彻底清楚你的API,或者避免不必要的代码执行,因为一个简单的条件没有被满足这样做!
您可以尝试调用该方法,然后捕获exception:
try: fp.read() except AttributeError: raise something
如果你只需要一个读取和写入方法,你可以这样做:
if not (hasattr(fp, 'read') and hasattr(fp, 'write')): raise something
如果我是你,我会用try / except方法去。
在大多数情况下,处理这个问题的最好办法是不行。 如果一个方法需要一个类似文件的对象,并且事实certificate它传递的对象不是,那么当方法尝试使用该对象时引发的exception不会比任何明确提出的exception都less。
至less有一种情况你可能想要做这种检查,那就是当对象没有被你传递给它的东西立即被使用,比如它是在一个类的构造函数中设置的。 在这种情况下,我认为EAFP的原则被“快速失败”的原则所束缚。 我会检查对象,确保它实现了我的类需要的方法(以及它们是方法),例如:
class C(): def __init__(self, file): if type(getattr(file, 'read')) != type(self.__init__): raise AttributeError self.file = file
当我写一个可以接受文件名,文件描述符或预先打开的文件类对象的类似open
的函数时,我最终遇到了你的问题。
正如其他答案所build议的那样,我没有testingread
方法,而是检查了是否可以打开对象。 如果可以的话,它是一个string或描述符,并且从结果中有一个有效的文件类对象。 如果open
引发TypeError
,那么该对象已经是一个文件。