Проверьте, является ли объект файловым в Python

94

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

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

if not isinstance(fp, file):
   raise something

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

dmeister
источник

Ответы:

44

Как правило, не рекомендуется включать подобные проверки в код, если у вас нет особых требований.

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

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

Тендаи Мавуше
источник
whyа как насчет операторов вроде __add__, __lshift__или __or__в кастомных классах? (объект файла и API: docs.python.org/glossary.html#term-file-object )
n611x007 08
@naxa: А что именно с этими операторами?
Мартино
34
Часто просто пробовать это срабатывает, но я не верю утверждению Pythonic, что если это сложно сделать на Python, значит, это неправильно. Представьте, что вам передали объект, и вы можете сделать с ним 10 различных действий в зависимости от его типа. Вы не собираетесь пробовать каждую возможность и обрабатывать ошибку, пока, наконец, не поймете все правильно. Это было бы совершенно неэффективно. Вам не обязательно спрашивать, что это за тип, но вы должны иметь возможность спросить, реализует ли этот объект интерфейс X.
jcoffland
33
Тот факт, что библиотека коллекций python предоставляет то, что можно было бы назвать «типами интерфейса» (например, последовательность), говорит о том, что это часто полезно, даже в python. В общем, когда кто-то спрашивает «как сделать foo», «not foo» не является удовлетворительным ответом.
AdamC
1
AttributeError может возникать по разным причинам, которые не имеют ничего общего с тем, поддерживает ли объект нужный вам интерфейс. hasattr необходим для файловых лайков, которые не являются производными от IOBase
Эрик Аронести,
77

Для версии 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)(хотя в этом направлении вариантов намного меньше).

Abarnert
источник
1
Наконец, кто-то должен держать это в секрете.
Энтони Ратледж
18
Единственный полезный ответ. Почему StackOverflowers продолжают голосовать «Прекратите делать то, что вы пытаетесь сделать, потому что я знаю лучше ... и PEP 8, EAFP и тому подобное!» посты выходит за рамки моего хрупкого рассудка. ( Может быть, Ктулху знает? )
Сесил Карри
1
Потому что мы столкнулись со слишком большим количеством кода, написанного людьми, которые не думали о будущем, и он ломается, когда вы передаете ему что-то почти, но не совсем файл, потому что они проверяют явно. Весь EAFP, утиная типизация - это не какой-то тест на чистоту чуши. Это реальное инженерное решение,
drxzcl
1
Это может рассматриваться как лучшая разработка, и я бы предпочел это лично, но может не работать. Обычно не требуется, чтобы файловые объекты наследовали от IOBase. Например, приборы pytest дают вам то, _pytest.capture.EncodedFileчто ни от чего не наследует.
Томаш Гавенчяк,
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 здесь может вызвать всевозможные тонкие проблемы, поскольку каждый путь инициализации частично запускается перед выдачей исключения. По сути, эта конструкция имитирует перегрузку функций и поэтому не очень Pythonic, но может быть полезна при осторожном использовании.

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

Скотт Гриффитс
источник
28

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

drxzcl
источник
9
+1: если xне файловый, то x.read()вызовет собственное исключение. Зачем писать лишнее if-выражение? Просто используйте объект. Это либо сработает, либо сломается.
S.Lott
3
Даже не обрабатывайте исключение. Если кто-то передал что-то, что не соответствует ожидаемому API, это не ваша проблема.
habnabit 02
1
@ Аарон Галлахер: Я не уверен. Верно ли ваше утверждение, даже если мне сложно сохранять последовательное состояние?
dmeister 04
1
Чтобы сохранить непротиворечивое состояние, вы можете использовать «try / finally» (но не за исключением!) Или новый оператор «with».
drxzcl 07
Это также согласуется с парадигмой «терпи неудачу быстро и громко проваливай». Если вы не будете дотошны, явные проверки hasattr (...) могут иногда вызывать нормальный возврат функции / метода без выполнения предполагаемого действия.
Бен Бернс,
11

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

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

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

Это не значит, что вы сходите с ума и делаете это везде, по большей части я согласен с концепцией возникновения исключений, но если вы можете сделать свой 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впоследствии обрабатывать данные .
Meow
3
Обратите внимание, что fp.read()с большими файлами сразу увеличится использование памяти.
Кирилл Перевозчиков
Я понимаю, что это питонический язык, но тогда мы должны дважды прочитать файл среди других проблем. Например, 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
1
Что еще более важно, эта проверка неверна. Он будет подниматься, если дан, скажем, реальный fileэкземпляр. (Методы экземпляров типов встроенных / C-расширений относятся к типу builtin_function_or_method, а методы классов старого стиля - к типу instancemethod). Тот факт, что это класс старого стиля и что он использует ==типы вместо ininstanceили issubclass, является дополнительной проблемой, но если основная идея не работает, это вряд ли имеет значение.
abarnert
2

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

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

Безумный физик
источник