检查Python中的对象是否像文件一样

dme*_*ter 86 python file

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

在文件预期的任何地方允许类似文件的对象被认为是一种好的做法,例如可以使用StringIO或Socket对象来代替真实文件.所以执行这样的检查是不好的:

if not isinstance(fp, file):
   raise something
Run Code Online (Sandbox Code Playgroud)

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

aba*_*ert 56

对于3.1+,以下之一:

isinstance(something, io.TextIOBase)
isinstance(something, io.BufferedIOBase)
isinstance(something, io.RawIOBase)
isinstance(something, io.IOBase)
Run Code Online (Sandbox Code Playgroud)

对于2.x,"类似文件的对象"太过模糊,无法检查,但是您正在处理的任何函数的文档都希望能够告诉您实际需要的内容; 如果没有,请阅读代码.


正如其他答案所指出的那样,首先要问的是你究竟要检查的是什么.通常,EAFP足够,而且更具惯用性.

词汇说"类文件对象"是"文件对象",同义词这最终意味着它是三者之一的一个实例抽象基类中所定义io模块,其本身的所有子类IOBase.因此,检查的方式完全如上所示.

(但是,检查IOBase不是很有用.你能想象一种情况,你需要区分一个实际的文件类似于read(size)一个名为read不像文件的单参数函数,而不需要区分文本文件和原始文件二进制文件?所以,实际上,你几乎总是要检查,例如,"是一个文本文件对象",而不是"是一个类似文件的对象".)


对于2.x,虽然io模块自2.6+以来就存在,但内置文件对象不是io类的实例,stdlib中的任何类文件对象都不是,并且大多数类似于第三方文件的对象都没有很可能会遇到."文件类对象"的含义没有官方定义; 它只是"类似于内置文件对象的东西",不同的功能意味着"喜欢"不同的东西.这些功能应记录它们的含义; 如果他们不这样做,你必须看看代码.

但是,最常见的含义是"has read(size)","has read()"或"是一个可迭代的字符串",但是一些旧的库可能期望readline而不是其中一个,一些库喜欢close()你给它们的文件,有些人会期望如果fileno存在然后其他功能可用,等等.write(buf)(尽管在该方向上有很多选项).

  • **唯一有用的答案.**为什么StackOverflowers继续推进"停止做你想要做的事情,因为我知道更好......以及PEP 8,EAFP和其他东西!" 帖子超出了我脆弱的理智.(*也许克苏鲁知道?*) (9认同)
  • 因为我们遇到了太多由没有提前思考的人编写的代码,当您传递一些几乎但不完全是文件的东西时,它就会中断,因为它们会显式检查。整个 EAFP、鸭子类型的事情并不是一些废话纯度测试。这是一个实际的工程决定 (3认同)
  • 这可能被视为更好的工程,我个人更喜欢它,但可能行不通。通常“不”要求类文件对象从“IOBase”继承。例如 pytest 固定装置为您提供“_pytest.capture.EncodedFile”,它不继承任何内容。 (3认同)
  • 最后,有人保持真实。 (2认同)
  • 对于像我这样不知道的人来说,“BufferedIOBase”是基于字节的文件对象(如“BytesIO”)的“正常”基类,而“RawIOBase”是通常不使用的无缓冲变体。[在此处查看更多信息](https://docs.python.org/3/library/io.html#binary-io)。 (2认同)

Ten*_*she 47

除非您有特殊要求,否则在您的代码中进行这样的检查通常不是一种好的做法.

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

你可以做的任何检查都会在运行时发生,所以做一些类似的事情if not hasattr(fp, 'read')并提出一些异常只比调用fp.read()和处理结果属性错误(如果该方法不存在)提供更多的实用工具.

  • 通常只是尝试它的工作,但我不买Pythonic格言,如果在Python中很难做到那就错了.想象一下,您传递了一个对象,根据其类型,您可以对该对象执行10种不同的操作.你不会尝试每种可能性并处理错误,直到你最终做对了.那将是完全无效的.你不一定要问,这是什么类型,但你需要能够问这个对象实现接口X. (24认同)
  • python集合库提供了可能被称为"接口类型"(例如,序列)的事实说明了这通常很有用,即使在python中也是如此.一般来说,当有人问"如何foo"时,"不要foo"不是一个非常令人满意的答案. (22认同)
  • 可能会因各种原因引发 AttributeError,而这些原因与对象是否支持您需要的接口无关。对于不是从 IOBase 派生的文件,需要 hasattr (2认同)
  • 我发现“hasattr(fp, 'seek')”更好,花了几个小时。在某些系统中(例如`fastai`),`path = pathlib.Path("filename")`同时具有`read`和`write`属性,但不是类文件对象。正如下面的一些评论所建议的那样,最好使用“insinstance(fp, io.IOBase)”。 (2认同)

Sco*_*ths 45

正如其他人所说,你通常应该避免这种检查.一个例外是当对象可能合法地是不同的类型,并且您希望根据类型而有不同的行为.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
Run Code Online (Sandbox Code Playgroud)

在这里使用EAFP可能会导致各种细微问题,因为每个初始化路径在抛出异常之前都会部分运行.基本上这种结构模仿函数重载,因此不是非常Pythonic,但如果小心使用它可能是有用的.

作为旁注,你不能在Python 3中以相同的方式进行文件检查.你需要这样的东西isinstance(f, io.IOBase).


drx*_*zcl 26

这里的主导范式是EAFP:比获得更容易请求宽恕.继续使用文件接口,然后处理生成的异常,或让它们传播给调用者.

  • +1:如果`x`不是类文件,那么`x.read()`会引发它自己的异常.为什么要写一个额外的if语句?只需使用该对象.它会工作还是休息. (9认同)
  • 甚至不处理异常.如果有人传入了与您期望的API不匹配的内容,那不是您的问题. (3认同)

Ben*_*ott 11

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

你不会在出口门的警察局放置金属探测器,你会把它放在入口处!如果不检查条件意味着可能发生错误,可能已经被捕获了100行,或者在超类中而不是在子类中被引发,那么我说检查没有任何问题.

当您接受多种类型时,检查正确的类型也是有意义的.最好提出一个异常,说"我需要一个basetring,OR文件的子类",而不仅仅是因为某个变量没有'seek'方法而引发异常...

这并不意味着你疯了,到处都这样做,因为大多数情况下我同意异常提升自己的概念,但是如果你可以使你的API大大清楚,或者避免不必要的代码执行因为一个简单的条件没有得到满足这样做!


Nad*_*mli 6

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

try:
    fp.read()
except AttributeError:
    raise something
Run Code Online (Sandbox Code Playgroud)

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

if not (hasattr(fp, 'read') and hasattr(fp, 'write')):
   raise something
Run Code Online (Sandbox Code Playgroud)

如果我是你,我会选择try/except方法.

  • 请注意,带有大文件的`fp.read()`会立即增加内存使用量. (2认同)