Проверьте, является ли объект похожим на файл в Python - PullRequest
83 голосов
/ 02 ноября 2009

Файловидные объекты - это объекты в Python, которые ведут себя как настоящий файл, например, иметь read () и метод write (), но иметь другую реализацию. Это и есть реализация концепции Duck Typing .

Считается хорошей практикой разрешать файловый объект везде, где ожидается файл, например, StringIO или объект Socket можно использовать вместо реального файла. Так что выполнять такую ​​проверку плохо:

if not isinstance(fp, file):
   raise something

Как лучше всего проверить, является ли объект (например, параметр метода) «файловым»?

Ответы [ 8 ]

48 голосов
/ 02 ноября 2009

Как правило, в вашем коде вообще не должно быть подобных проверок, если только у вас нет особых требований.

В Python типизация динамическая, почему вы чувствуете необходимость проверять, похож ли объект на файл, а не просто использовать его, как если бы он был файлом, и обрабатывать возникающую ошибку?

Любая проверка, которую вы можете сделать, произойдет во время выполнения, так что выполнение чего-то вроде if not hasattr(fp, 'read') и вызов некоторого исключения обеспечивают немного больше полезности, чем просто вызов fp.read() и обработка полученной ошибки атрибута, если метод не существует.

48 голосов
/ 17 июля 2014

Для версии 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, и ни один из них не является большинство сторонних файловых объектов, с которыми вы можете столкнуться. Не было официального определения того, что означает «подобный файлу объект»; это просто «что-то вроде встроенного файлового объекта », а разные функции означают разные вещи под «лайком». Такие функции должны документировать, что они означают; если они этого не делают, вы должны посмотреть на код.

Однако наиболее распространенными значениями являются «имеет read(size)», «имеет read()» или «является итерируемым из строк», но некоторые старые библиотеки могут ожидать readline вместо одной из них, некоторые библиотеки например, к close() файлам, которые вы им предоставляете, некоторые ожидают, что если присутствует fileno, то будут доступны другие функции и т. д. И аналогично для write(buf) (хотя вариантов в этом направлении намного меньше).

45 голосов
/ 02 ноября 2009

Как уже говорили другие, вам следует избегать таких проверок. Единственным исключением является случай, когда объект может на законных основаниях иметь разные типы, и вам нужно различное поведение в зависимости от типа. Метод 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 может вызвать всевозможные тонкие проблемы, поскольку каждый путь инициализации частично запускается перед выдачей исключения. По сути, эта конструкция имитирует перегрузку функций и поэтому не очень Pythonic, но она может быть полезна, если использовать ее с осторожностью.

В качестве примечания, вы не можете выполнить проверку файлов таким же образом в Python 3. Вместо этого вам потребуется что-то вроде isinstance(f, io.IOBase).

27 голосов
/ 02 ноября 2009

Доминирующей парадигмой здесь является EAFP: проще просить прощения, чем разрешения. Продолжайте и используйте файловый интерфейс, затем обработайте полученное исключение или позвольте им распространяться на вызывающую сторону.

10 голосов
/ 29 июля 2011

Часто полезно выдавать ошибку, проверяя условие, когда эта ошибка обычно не возникает намного позже. Это особенно верно для границы между кодом «user-land» и «api».

Вы бы не поставили металлоискатель в полицейском участке на выходной двери, вы бы поставили его у входа! Если не проверка условия означает, что может произойти ошибка, которая могла быть перехвачена на 100 строк раньше или в суперклассе, вместо того, чтобы вызываться в подклассе, тогда я говорю, что нет ничего плохого в проверке.

Проверка правильности типов также имеет смысл, когда вы принимаете более одного типа. Лучше вызвать исключение, которое говорит: «Мне нужен подкласс базовой строки, ИЛИ файл», чем просто вызвать исключение, потому что у некоторой переменной нет метода 'seek' ...

Это не значит, что вы сходите с ума и делаете это повсюду, по большей части я согласен с концепцией, вызывающей возникновение исключений, но если вы можете сделать свой API-интерфейс совершенно ясным или избежать ненужного выполнения кода, поскольку простое условие не встретил, сделай так!

6 голосов
/ 02 ноября 2009

Вы можете попробовать вызвать метод, а затем перехватить исключение:

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

Если вам нужны только методы чтения и записи, вы можете сделать это:

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

Если бы я был тобой, я бы использовал метод try / исключением.

2 голосов
/ 03 ноября 2009

В большинстве случаев лучший способ справиться с этим - это не делать. Если метод принимает объект, подобный файлу, и оказывается, что переданный им объект не является, исключение, которое возникает при попытке метода использовать объект, не менее информативно, чем любое исключение, которое вы могли бы явно вызвать. 1001 *

Есть, по крайней мере, один случай, когда вы можете захотеть сделать такую ​​проверку, и именно тогда объект не сразу используется тем, чему вы его передали, например, если он установлен в конструкторе класса. В этом случае, я думаю, что принцип EAFP превосходит принцип «быстро провалиться». Я бы проверил объект, чтобы убедиться, что он реализовал методы, которые нужны моему классу (и что они являются методами), например ::

class C():
    def __init__(self, file):
        if type(getattr(file, 'read')) != type(self.__init__):
            raise AttributeError
        self.file = file
1 голос
/ 28 апреля 2017

Я столкнулся с вашим вопросом, когда писал написанную open -подобную функцию, которая могла бы принимать имя файла, дескриптор файла или предварительно открытый объект типа файла.

Вместо того, чтобы тестировать метод read, как предлагают другие ответы, я закончил проверкой возможности открытия объекта. Если это возможно, это строка или дескриптор, и у меня есть правильный файлоподобный объект под рукой из результата. Если open поднимает TypeError, то объект уже является файлом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...