Перевірте, чи об’єкт схожий на файл у Python


93

Файлоподібні об'єкти - це об'єкти в Python, які ведуть себе як справжній файл, наприклад, мають метод read () та метод запису (), але мають іншу реалізацію. Це і реалізація концепції набору качки .

Вважається доброю практикою дозволяти файлоподібний об'єкт скрізь, де очікується файл, щоб, наприклад, StringIO або Socket-об'єкт можна було використовувати замість реального файлу. Тож погано виконувати перевірку, як це:

if not isinstance(fp, file):
   raise something

Який найкращий спосіб перевірити, чи є об’єкт (наприклад, параметр методу) «файловим»?

Відповіді:


45

Зазвичай взагалі не є доброю практикою мати такі перевірки у своєму коді, якщо у вас немає спеціальних вимог.

У Python набір тексту динамічний, чому ви відчуваєте необхідність перевіряти, чи об'єкт схожий на файл, а не просто використовувати його, ніби це файл, і обробляти отриману помилку?

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


whyяк щодо операторів подобається __add__, __lshift__або __or__в призначених для користувача класах? (об'єкт файлу та API: docs.python.org/glossary.html#term-file-object )
n611x007,

@naxa: То що саме з цими операторами?
Мартіно

32
Часто просто спробувати це працює, але я не купую максиму Pythonic, що якщо це важко зробити в Python, то це неправильно. Уявіть, що вам передали об’єкт, і ви можете зробити 10 різних речей із цим об’єктом залежно від його типу. Ви не збираєтеся випробовувати кожну можливість і обробляти помилку, поки нарешті не виправите її. Це було б абсолютно неефективно. Не обов’язково запитувати, що це за тип, але потрібно мати можливість запитати, чи реалізує цей об’єкт інтерфейс X.
jcoffland

31
Той факт, що бібліотека колекцій python надає так звані "типи інтерфейсу" (наприклад, послідовність), говорить про те, що це часто корисно навіть у python. Взагалі, коли хтось запитує "як слід", "не фудуй" - це не дуже задовільна відповідь.
AdamC

1
Помилку AttributeError можна викликати з різних причин, які не мають нічого спільного з тим, чи підтримує об'єкт потрібний вам інтерфейс. hasattr потрібен для файлів, які не походять від IOBase
Ерік Аронесті

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, а також більшість сторонніх файлоподібних об'єктів, які ви швидше за все зіткнеться. Не було офіційного визначення того, що означає "файлоподібний об'єкт"; це просто "щось на зразок вбудованого об'єкта файлу ", а різні функції означають різні речі під "подобається". Такі функції повинні документувати, що вони означають; якщо вони цього не роблять, вам слід поглянути на код.

Однак найпоширенішими значеннями є "має read(size)", "має read()" або "це ітерація рядків", але деякі старі бібліотеки можуть очікувати readlineзамість одного з них, деякі бібліотеки люблять close()файли, які ви їм надаєте, деякі очікують, що якщо filenoприсутній, тоді доступна інша функціональність тощо. І так само для write(buf)(хоча варіантів у цьому напрямку набагато менше).


1
Нарешті, хтось тримає це реальним.
Ентоні Ратледж,

16
Єдина корисна відповідь. Чому 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 тут може спричинити всі види тонких проблем, оскільки кожен шлях ініціалізації частково запускається перед створенням винятку. По суті, ця конструкція імітує перевантаження функції, і тому не надто пітонічна, але може бути корисною, якщо використовувати її обережно.

Як додаткову примітку, ви не можете виконати перевірку файлів однаково в Python 3. Натомість вам знадобиться щось на зразок isinstance(f, io.IOBase).


28

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


9
+1: якщо xне схожий на файл, тоді x.read()буде викликано власний виняток. Навіщо писати зайве твердження if-if? Просто використовуйте об’єкт. Це або спрацює, або зламається.
S.Lott

3
Навіть не обробляйте виняток. Якщо хтось передав щось, що не відповідає API, який ви очікуєте, це не ваша проблема.
habnabit 02

1
@ Аарон Галлахер: Я не впевнений. Чи правдивим є ваше твердження, навіть якщо мені важко зберегти стабільний стан?
dmeister

1
Для збереження послідовного стану ви можете використовувати "try / нарешті" (але нічого, крім!) Або нове твердження "with".
drxzcl

Це також узгоджується з парадигмою "Невдало швидко і голосно невдало". Якщо ви не скрупульозні, явні перевірки hasattr (...) можуть іноді спричиняти нормальну функцію / метод, не виконуючи запланованих дій.
Бен Бернс,

11

Часто корисно викликати помилку, перевіряючи умову, коли ця помилка зазвичай не виникає набагато пізніше. Це особливо справедливо для межі між "користувачем-землею" та "api" кодом.

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

Перевірка правильних типів також має сенс, коли ви приймаєте більше одного типу. Краще викликати виняток, який говорить: "Мені потрібен підклас базового рядка, АБО файл", ніж просто виняток, оскільки деяка змінна не має методу "шукати" ...

Це не означає, що ви божеволієте і робите це скрізь, здебільшого я погоджуюся з концепцією винятків, які самі по собі роблять, але якщо ви можете різко зрозуміти свій API або уникнути непотрібного виконання коду, оскільки проста умова не виконується роби так!


1
Я погоджуюсь, але, не зважаючи на це скрізь - багато з цих проблем повинні викинутись під час тестування, і на деякі запитання "де це зловити / як показати користувачеві" відповідатимуть вимоги юзабіліті.
Бен Бернс,

7

Ви можете спробувати викликати метод, а потім зловити виняток:

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

Якщо ви хочете лише метод читання та запису, ви можете зробити це:

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

Якби я був на вашому місці, я пішов би за методом try / Osim.


Я б запропонував змінити порядок прикладів. tryзавжди є першим вибором. Ці hasattrчеки тільки - за якою - то дуже неясної причини - ви не можете просто використовувати try.
S.Lott 02

1
Я пропоную використовувати fp.read(0)замість fp.read()того, щоб уникнути розміщення всього коду в tryблоці, якщо ви хочете обробити дані fpзгодом.
Мяу

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екземпляр. (Методи екземплярів вбудованих типів / типів розширень мають тип builtin_function_or_method, тоді як типи класів старого стилю instancemethod). Той факт, що це клас старого стилю, і який він використовує ==на типах замість ininstanceабо issubclass, є подальшими проблемами, але якщо основна ідея не працює, це навряд чи має значення.
abarnert

2

Нарешті, я натрапив на ваше запитання, коли писав openподібну функцію, яка могла прийняти ім’я файлу, дескриптор файлу або попередньо відкритий схожий на файл об’єкт.

Замість тестування readметоду, як пропонують інші відповіді, я закінчив перевіряти, чи можна відкрити об’єкт. Якщо це можливо, це рядок або дескриптор, і я маю на руках дійсний файлоподібний об’єкт з результату. Якщо openпіднімає a TypeError, то об’єкт - це вже файл.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.