【问题标题】:Check if object is file-like in Python检查对象是否在 Python 中类似于文件
【发布时间】:2009-11-02 13:13:57
【问题描述】:

File-like objects 是 Python 中的对象,其行为类似于真实文件,例如有一个 read() 和一个 write method(),但与file 有不同的实现。它是Duck Typing 概念的实现。

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

if not isinstance(fp, file):
   raise something

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

【问题讨论】:

    标签: python file


    【解决方案1】:

    对于 3.1+,以下之一:

    isinstance(something, io.TextIOBase)
    isinstance(something, io.BufferedIOBase)
    isinstance(something, io.RawIOBase)
    isinstance(something, io.IOBase)
    

    对于 2.x,“类文件对象”太模糊了,无法检查,但您正在处理的任何函数的文档都有望告诉您它们实际需要什么;如果没有,请阅读代码。


    正如其他答案所指出的,首先要问的是您要检查的内容。通常,EAFP 就足够了,而且更惯用。

    The glossary 说“类文件对象”是“文件对象”的同义词,这最终意味着它是the io module 中定义的三个abstract base classes 之一的实例,它们本身都是@987654328 的子类@。所以,检查的方法就是如上所示。

    (但是,检查IOBase 并不是很有用。您能想象这样一种情况,您需要将一个实际的类似文件的read(size) 与某个名为read 的单参数函数区分开来,它不是文件-比如,不需要区分文本文件和原始二进制文件?所以,实际上,您几乎总是想要检查,例如“是文本文件对象”,而不是“是类文件对象”。)


    对于 2.x,虽然 io 模块自 2.6+ 起就存在,但内置文件对象不是 io 类的实例,也不是 stdlib 中的任何类文件对象,也不是您可能会遇到的大多数第三方文件类对象。 “类文件对象”的含义没有官方定义;它只是“类似于内置file object”,不同的功能用“like”表示不同的东西。此类功能应记录其含义;如果没有,您必须查看代码。

    然而,最常见的含义是“有read(size)”、“有read()”或“是字符串的可迭代”,但一些旧库可能期望readline 而不是其中之一,一些库喜欢 close() 你给他们的文件,有些人会期望如果 fileno 存在,那么其他功能是可用的,等等。write(buf) 也是如此(尽管在这个方向上的选择要少得多)。

    【讨论】:

    • 终于有人保持真实。
    • 唯一有用的答案。 为什么 StackOverflowers 继续支持“停止做你想做的事,因为我知道得更好……还有 PEP 8、EAFP 等等!”帖子超出了我脆弱的理智。 (也许克苏鲁知道?
    • 因为我们遇到了太多没有提前考虑的人编写的代码,并且当您传递几乎但不完全是文件的东西时它会中断,因为他们会明确检查。整个 EAFP,鸭子打字不是什么废话纯度测试。这是一个实际的工程决策,
    • 这可能被视为更好的工程,我个人更喜欢它,但可能行不通。通常要求类文件对象从IOBase 继承。例如 pytest 固定装置给你_pytest.capture.EncodedFile 它不继承任何东西。
    • 对于像我这样不知道的人来说,BufferedIOBase 是像 BytesIO 这样的基于字节的文件对象的“普通”基类,而 RawIOBase 是不常用的无缓冲变体。 See more here.
    【解决方案2】:

    正如其他人所说,您通常应该避免此类检查。一个例外是当对象可能合法地是不同的类型并且您想要根据类型不同的行为时。 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) 这样的东西。

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

      • why 自定义类中的__add____lshift____or__ 等运算符呢? (文件对象和API:docs.python.org/glossary.html#term-file-object
      • 通常只是尝试一下,但我不相信 Pythonic 的格言,即如果在 Python 中很难做到,那就错了。想象一下,你收到了一个对象,根据它的类型,你可以对这个对象做 10 种不同的事情。在你最终做对之前,你不会尝试每种可能性并处理错误。那将是完全低效的。你不一定要问,这是什么类型,但你确实需要能够问这个对象是否实现了接口X。
      • python 集合库提供了可能被称为“接口类型”(例如,序列)的事实说明这通常很有用,即使在 python 中也是如此。一般来说,当有人问“如何 foo”时,“不要 foo”并不是一个非常令人满意的答案。
      • AttributeError 可以出于各种原因引发,与对象是否支持您需要的接口无关。不是从 IOBase 派生的类文件需要 hasattr
      • hasattr(fp, 'seek') 我发现更好,花了几个小时。在某些系统中(例如fastai),path = pathlib.Path("filename") 同时具有readwrite 属性,但不是类文件对象。正如下面的一些 cmets 建议的那样,最好使用 insintance(fp, io.IOBase)
      【解决方案4】:

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

      【讨论】:

      • +1:如果x 不是文件类,那么x.read() 将引发它自己的异常。为什么要编写额外的 if 语句?只需使用对象。它要么工作要么坏。
      • 甚至不处理异常。如果有人传入的内容与您期望的 API 不符,那不是您的问题。
      • @Aaron Gallagher:我不确定。即使我很难保持一致的状态,您的陈述是否正确?
      • 为了保持一致的状态,您可以使用“try/finally”(但不能例外!)或新的“with”语句。
      • 这也符合“快速失败并大声失败”的范式。除非你很细致,否则显式的 hasattr(...) 检查偶尔会导致函数/方法正常返回而不执行其预期的操作。
      【解决方案5】:

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

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

      当您接受多个类型时,检查正确的类型也很有意义。 最好引发一个异常,上面写着“我需要一个 basestring 的子类或文件”,而不是仅仅引发一个异常,因为某些变量没有“seek”方法......

      这并不意味着你会发疯并到处这样做,在大多数情况下,我同意引发异常的概念,但如果你可以让你的 API 非常清晰,或者避免不必要的代码执行,因为一个简单的条件已经还没遇到就这样做!

      【讨论】:

      • 我同意,但是不要随处发疯——很多这些问题应该在测试期间消除,以及一些“在哪里捕捉这个/如何向用户显示”问题将根据可用性要求来回答。
      【解决方案6】:

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

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

      如果您只想要一个读取和写入方法,您可以这样做:

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

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

      【讨论】:

      • 我建议切换示例的顺序。 try 永远是首选。 hasattr 检查只是——出于一些非常模糊的原因——你不能简单地使用try
      • 如果你想随后处理来自fp的数据,我建议使用fp.read(0)而不是fp.read()以避免将所有代码放在try块中。
      • 请注意,fp.read() 带有大文件会立即增加内存使用量。
      • 我知道这是pythonic,但是我们必须两次读取文件以及其他问题。例如,在Flask 中,我这样做并意识到底层的FileStorage 对象需要在读取后重置它的指针。
      【解决方案7】:

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

      而不是测试 read 方法,正如其他答案所暗示的那样,我最终检查了对象是否可以打开。如果可以,它是一个字符串或描述符,并且我从结果中获得了一个有效的类似文件的对象。如果open 引发TypeError,则该对象已经是一个文件。

      【讨论】:

        【解决方案8】:

        在大多数情况下,处理此问题的最佳方法是不这样做。如果一个方法接受了一个类似文件的对象,而结果它传递的对象不是,那么当该方法尝试使用该对象时引发的异常不会比您可能显式引发的任何异常提供的信息少。

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

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

        【讨论】:

        • 为什么是getattr(file, 'read') 而不仅仅是file.read?这完全一样。
        • 更重要的是,这个检查是错误的。当给定一个实际的file 实例时,它将引发。 (内置/C 扩展类型的实例方法是 builtin_function_or_method 类型,而旧式类的实例方法是 instancemethod)。这是一个老式的类,并且它在类型上使用== 而不是ininstanceissubclass,这是进一步的问题,但如果基本思想不起作用,那就没关系了。跨度>
        【解决方案9】:

        试试这个:

        import os 
        
        if os.path.isfile(path):
           'do something'
        

        【讨论】:

        • 这个不在io.StringIOio.BytesIO 上。 os.path.isfile() 仅适用于字符串、字节、os.PathLike 或整数。
        猜你喜欢
        • 2013-09-23
        • 1970-01-01
        • 1970-01-01
        • 2022-12-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-04-25
        相关资源
        最近更新 更多