检查对象在Python中是否类似于文件


93

类文件对象是Python中的对象,其行为类似于真实文件,例如具有read()和write method(),但实现方式不同。鸭打字的实现概念的。

允许在需要文件的任何地方放置类似文件的对象是一种好习惯,例如 可以使用StringIO或Socket对象代替实际文件。因此执行这样的检查很不好:

if not isinstance(fp, file):
   raise something

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

Answers:


45

除非您有特殊要求,否则通常在代码中完全没有这样的检查通常不是一个好习惯。

在Python中,键入是动态的,为什么您觉得需要检查对象是否像文件一样,而不是像使用文件一样仅使用它并处理产生的错误?

无论如何,您都可以在运行时进行任何检查,因此,如果方法不存在,则执行类似的操作if not hasattr(fp, 'read')和引发某些异常将比仅调用fp.read()和处理结果属性错误提供更多的实用程序。


why怎么样操作者喜欢__add____lshift____or__在自定义类?(文件对象和API:docs.python.org/glossary.html#term-file-object
n611x007 2012年

@naxa:那这些运算符到底是什么?
martineau 2015年

32
通常只是尝试它会起作用,但我不购买Pythonic的箴言,即如果很难在Python中做到这一点,那就错了。假设您被传递了一个对象,并且根据对象的类型,可以对它进行10种不同的操作。在最终解决问题之前,您不会尝试每种可能性并处理错误。那将是完全无效的。你不一定要问,是什么类型,但你确实需要能够问这是否对象实现接口X.
jcoffland

31
python集合库提供了可能称为“接口类型”(例如,序列)的事实,这表明即使在python中,这通常也是有用的。通常,当有人问“如何foo”时,“不要foo”并不是一个令人满意的答案。
AdamC

1
可以出于各种原因引发AttributeError,而这些原因与对象是否支持所需的接口无关。对于不是源自IOBase的文件,需要hasattr
Erik Aronesty

74

对于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)(尽管在该方向上的选项较少)。


1
最后,有人是真实的。
安东尼·鲁特里奇

16
唯一有用的答案。为什么StackOverflowers会继续反对“停止做您想做的事情,因为我更了解……以及PEP 8,EAFP和其他东西!” 职位超出了我脆弱的理智范围。(也许克苏鲁知道吗?
塞西尔·库里

1
因为我们遇到了很多没有想到的人编写的代码,当您传递给它的东西几乎是一个但不是一个文件,因为他们明确地检查了它时,它就会中断。整个EAFP,鸭子打字的东西并不是一些胡说八道的纯度测试。这是一个实际的挑战性决定,
drxzcl

1
这可能被认为是更好的工程设计,我个人更喜欢它,但可能不起作用。它一般要求类文件对象继承IOBase。例如,pytest固定装置会为您_pytest.capture.EncodedFile提供不从任何东西继承的东西。
托马什Gavenčiak

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化,但如果谨慎使用,它可能会很有用。

附带说明一下,您无法在Python 3中以相同的方式进行文件检查。您将需要类似的东西isinstance(f, io.IOBase)


28

此处的主要范例是EAFP:要求宽恕比允许容易。继续并使用文件接口,然后处理所产生的异常,或者让它们传播到调用方。


9
+1:如果x不是类似文件的文件,x.read()则将引发它自己的异常。为什么还要写一个额外的if语句?只需使用对象。它会工作或崩溃。
S.Lott

3
甚至不处理异常。如果有人传入了与您期望的API不匹配的内容,那不是您的问题。
哈比人2009年

1
@Aaron Gallagher:我不确定。即使我很难保持一致的状态,您的说法是否正确?
dmeister

1
为了保持一致的状态,您可以使用“ try / finally”(但没有例外!)或新的“ with”语句。
drxzcl

这也与“快速失败并大声失败”范例一致。除非您一丝不苟,否则显式的hasattr(...)检查有时会导致函数/方法正常返回而不执行其预期的操作。
本·伯恩斯

11

通过检查条件来引发错误通常很有用,而通常要等到更晚的时候才会提出该错误。对于“用户区域”和“ api”代码之间的边界尤其如此。

您不会在出口处的警察局放置金属探测器,而会将其放置在入口处!如果不检查条件意味着可能会发生错误,而该错误可能早于100行,或者是在超类中而不是在子类中引发的,那么我说检查没有任何问题。

当您接受多个类型时,检查适当的类型也很有意义。最好提出一个异常,说“我需要一个基本字符串或文件的子类”,而不是仅仅引发一个异常,因为某些变量没有'seek'方法。

这并不意味着您会发疯并在各处执行此操作,在大多数情况下,我都同意异常引发自己的概念,但是,如果您可以使您的API完全清晰,或者避免由于不满足简单条件而导致不必要的代码执行,这样做!


1
我同意,但是沿着所有地方都不会发疯的思路-在测试过程中应该消除很多这些顾虑,并且一些“在哪里捕获此问题/如何向用户显示”问题将由可用性要求回答。
本·伯恩斯

7

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

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

如果您只需要读取和写入方法,则可以执行以下操作:

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

如果我是你,我会尝试使用try / except方法。


我建议切换示例的顺序。 try永远是第一选择。这些hasattr检查只是-由于某些难以理解的原因-您不能简单地使用try
S.Lott

1
如果要随后处理数据,建议fp.read(0)不要使用代替,fp.read()以免将所有代码放入try块中fp
猫叫声

3
请注意,fp.read()使用大文件将立即增加内存使用量。
Kyrylo Perevozchikov 2015年

我知道这是pythonic,但是接下来我们不得不读取文件两次。例如,在执行Flask此操作后,我意识到底层FileStorage对象需要在读取后重置其指针。
亚当·休斯

2

在大多数情况下,处理此问题的最佳方法不是。如果一个方法接受一个类似文件的对象,但事实证明它不是通过的对象,则该方法尝试使用该对象时引发的异常不会比您可能明确提出的任何异常具有更多的信息。

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

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

1
为什么getattr(file, 'read')不只是file.read?这确实做同样的事情。
abarnert 2014年

1
更重要的是,此检查是错误的。如果给出一个实际file实例,它将提高。(内建/ C扩展类型的实例方法的类型为builtin_function_or_method,而旧式类的实例的方法为instancemethod)。这是一个老式的类,并且用==在类型上而不是ininstanceor上issubclass,这是进一步的问题,但是如果基本思想不起作用,那就没关系了。
abarnert 2014年

2

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

read我没有检查其他方法所建议的方法,而是检查了是否可以打开该对象。如果可以,它是一个字符串或描述符,并且结果中有一个有效的类似于文件的对象。如果open引发一个TypeError,则该对象已经是一个文件。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.