Vérifier si l'objet ressemble à un fichier en Python

93

Les objets de type fichier sont des objets en Python qui se comportent comme un vrai fichier, par exemple ont une méthode read () et une méthode write (), mais ont une implémentation différente. C'est et réalisation du concept Duck Typing .

Il est considéré comme une bonne pratique d'autoriser un objet de type fichier partout où un fichier est attendu afin que, par exemple, un objet StringIO ou un objet Socket puisse être utilisé à la place d'un fichier réel. Il est donc mauvais d'effectuer un contrôle comme celui-ci:

if not isinstance(fp, file):
   raise something

Quelle est la meilleure façon de vérifier si un objet (par exemple un paramètre d'une méthode) est "semblable à un fichier"?

démister
la source

Réponses:

45

Ce n'est généralement pas une bonne pratique d'avoir des vérifications comme celle-ci dans votre code, sauf si vous avez des exigences particulières.

En Python, le typage est dynamique, pourquoi ressentez-vous le besoin de vérifier si l'objet ressemble à un fichier, plutôt que de simplement l'utiliser comme s'il s'agissait d'un fichier et de gérer l'erreur qui en résulte?

Toute vérification que vous pouvez faire se produira de toute façon au moment de l'exécution, alors faire quelque chose comme if not hasattr(fp, 'read')et lever une exception ne fournit guère plus d'utilité que d'appeler fp.read()et de gérer l'erreur d'attribut résultante si la méthode n'existe pas.

Tendayi Mawushe
la source
whyqu'en est-il des opérateurs comme __add__, __lshift__ou __or__dans des classes personnalisées? (objet de fichier et API: docs.python.org/glossary.html#term-file-object )
n611x007
@naxa: Alors qu'en est-il exactement de ces opérateurs?
martineau
32
Souvent, simplement essayer cela fonctionne, mais je n'achète pas la maxime pythonique selon laquelle si c'est difficile à faire en Python, c'est faux. Imaginez que vous recevez un objet et que vous pouvez faire 10 choses différentes avec cet objet en fonction de son type. Vous n'allez pas essayer chaque possibilité et gérer l'erreur jusqu'à ce que vous ayez enfin réussi. Ce serait totalement inefficace. Vous n'avez pas nécessairement besoin de demander de quel type s'agit-il, mais vous devez être en mesure de demander si cet objet implémente l'interface X.
jcoffland
31
Le fait que la bibliothèque de collections python fournisse ce que l'on pourrait appeler des «types d'interface» (par exemple, une séquence) témoigne du fait que cela est souvent utile, même en python. En général, quand quelqu'un demande "comment faire toto", "ne pas toto" n'est pas une réponse très satisfaisante.
AdamC
1
AttributeError peut être déclenché pour toutes sortes de raisons qui n'ont rien à voir avec le fait que l'objet prend en charge l'interface dont vous avez besoin. hasattr est nécessaire pour les filelikes qui ne dérivent pas d'IOBase
Erik Aronesty
74

Pour 3.1+, l'un des éléments suivants:

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

Pour 2.x, "objet semblable à un fichier" est une chose trop vague à vérifier, mais la documentation de la ou des fonctions auxquelles vous avez affaire vous dira avec un peu de chance ce dont elles ont réellement besoin; sinon, lisez le code.


Comme le soulignent d'autres réponses, la première chose à demander est ce que vous recherchez exactement. Habituellement, l'EAFP est suffisant et plus idiomatique.

Le glossaire dit que "objet de type fichier" est un synonyme de "objet fichier", ce qui signifie finalement qu'il s'agit d'une instance de l'une des trois classes de base abstraites définies dans le iomodule , qui sont elles-mêmes toutes des sous-classes de IOBase. Donc, la façon de vérifier est exactement comme indiqué ci-dessus.

(Cependant, la vérification IOBasen'est pas très utile. Pouvez-vous imaginer un cas où vous devez distinguer une fonction semblable à un fichier read(size)d'une fonction à un argument nommée readqui n'est pas semblable à un fichier, sans avoir également besoin de faire la distinction entre les fichiers texte et raw Donc, vraiment, vous voulez presque toujours vérifier, par exemple, "est un objet de fichier texte", et non "est un objet de type fichier".)


Pour 2.x, alors que le iomodule existe depuis 2.6+, les objets de fichier intégrés ne sont pas des instances de ioclasses, aucun des objets de type fichier dans la stdlib non plus, ni la plupart des objets de type fichier tiers que vous sont susceptibles de rencontrer. Il n'y avait pas de définition officielle de ce que signifie «objet semblable à un fichier»; c'est juste "quelque chose comme un objet de fichier intégré ", et différentes fonctions signifient des choses différentes par "comme". Ces fonctions devraient documenter ce qu’elles signifient; s'ils ne le font pas, vous devez regarder le code.

Cependant, les significations les plus courantes sont «a read(size)», «a read()» ou «est une itérable de chaînes», mais certaines anciennes bibliothèques peuvent s'attendre à la readlineplace de l'une de celles-ci, certaines bibliothèques aiment les close()fichiers que vous leur donnez, d'autres s'attendent à ce que si filenoest présente alors d'autres fonctionnalités sont disponibles, etc. Et de même pour write(buf)(bien qu'il y ait beaucoup moins d'options dans cette direction).

Abarnert
la source
1
Enfin, quelqu'un reste réel.
Anthony Rutledge
16
La seule réponse utile. Pourquoi StackOverflowers continue de voter "Arrêtez de faire ce que vous essayez de faire, parce que je sais mieux ... et PEP 8, EAFP, et tout!" messages est au-delà de ma fragile santé mentale. ( Peut - être que Cthulhu le sait? )
Cecil Curry
1
Parce que nous avons rencontré beaucoup trop de code écrit par des personnes qui ne pensaient pas à l'avance, et cela se brise lorsque vous lui transmettez quelque chose qui est presque, mais pas tout à fait un fichier, car ils vérifient explicitement. L'ensemble de l'EAFP, le typage du canard, n'est pas un test de pureté de connerie. C'est une véritable décision d'ingénierie,
drxzcl
1
Cela peut être considéré comme une meilleure ingénierie et je le préférerais personnellement, mais peut ne pas fonctionner. Il n'est généralement pas nécessaire que les objets de type fichier héritent de IOBase. Par exemple, les appareils pytest vous donnent ce _pytest.capture.EncodedFilequi n'hérite de rien.
Tomáš Gavenčiak le
46

Comme d'autres l'ont dit, vous devez généralement éviter de tels contrôles. Une exception est lorsque l'objet peut légitimement être de types différents et que vous souhaitez un comportement différent en fonction du type. La méthode EAFP ne fonctionne pas toujours ici car un objet peut ressembler à plus d'un type de canard!

Par exemple, un initialiseur peut prendre un fichier, une chaîne ou une instance de sa propre classe. Vous pourriez alors avoir du code comme:

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

L'utilisation d'EAFP ici pourrait causer toutes sortes de problèmes subtils car chaque chemin d'initialisation est partiellement exécuté avant de lancer une exception. Essentiellement, cette construction imite la surcharge de fonctions et n'est donc pas très pythonique, mais elle peut être utile si elle est utilisée avec précaution.

En remarque, vous ne pouvez pas effectuer la vérification des fichiers de la même manière en Python 3. Vous aurez besoin de quelque chose comme à la isinstance(f, io.IOBase)place.

Scott Griffiths
la source
28

Le paradigme dominant ici est l'EAFP: plus facile de demander pardon que la permission. Allez-y et utilisez l'interface de fichier, puis gérez l'exception qui en résulte ou laissez-les se propager à l'appelant.

drxzcl
la source
9
+1: Si xn'est pas semblable à un fichier, alors x.read()lèvera sa propre exception. Pourquoi écrire une instruction if supplémentaire? Utilisez simplement l'objet. Cela fonctionnera ou se cassera.
S.Lott
3
Ne gérez même pas l'exception. Si quelqu'un a transmis quelque chose qui ne correspond pas à l'API que vous attendez, ce n'est pas votre problème.
habnabit
1
@Aaron Gallagher: Je ne suis pas sûr. Votre déclaration est-elle vraie même s'il m'est difficile de préserver un état cohérent?
dmeister
1
Pour conserver un état cohérent, vous pouvez utiliser "try / finally" (mais aucun sauf!) Ou la nouvelle instruction "with".
drxzcl
Ceci est également cohérent avec le paradigme «Échouer rapidement et échouer bruyamment». À moins que vous ne soyez méticuleux, les vérifications de hasattr (...) explicites peuvent occasionnellement provoquer le retour normal d'une fonction / méthode sans effectuer l'action prévue.
Ben Burns
11

Il est souvent utile de déclencher une erreur en vérifiant une condition, alors que cette erreur ne serait normalement déclenchée que beaucoup plus tard. Cela est particulièrement vrai pour la frontière entre le code «user-land» et «api».

Vous ne placeriez pas un détecteur de métal dans un poste de police sur la porte de sortie, vous le placeriez à l'entrée! Si le fait de ne pas vérifier une condition signifie qu'une erreur pourrait se produire qui aurait pu être interceptée 100 lignes plus tôt, ou dans une super-classe au lieu d'être levée dans la sous-classe, alors je dis qu'il n'y a rien de mal à vérifier.

La vérification des types appropriés est également judicieuse lorsque vous acceptez plusieurs types. Il est préférable de lever une exception qui dit "J'ai besoin d'une sous-classe de basestring, OR file" plutôt que de simplement lever une exception car une variable n'a pas de méthode 'seek' ...

Cela ne veut pas dire que vous devenez fou et que vous faites cela partout, pour la plupart, je suis d'accord avec le concept d'exceptions se levant d'elles-mêmes, mais si vous pouvez rendre votre API radicalement claire, ou éviter l'exécution de code inutile car une condition simple n'a pas été remplie faites-le!

Ben DeMott
la source
1
Je suis d'accord, mais dans le but de ne pas devenir fou avec ça partout - beaucoup de ces préoccupations devraient se dissiper pendant les tests, et certaines des questions «où attraper ceci / comment afficher à l'utilisateur» seront répondues par des exigences d'utilisabilité.
Ben Burns
7

Vous pouvez essayer d'appeler la méthode puis attraper l'exception:

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

Si vous ne voulez qu'une méthode de lecture et une méthode d'écriture, vous pouvez le faire:

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

Si j'étais vous, j'irais avec la méthode try / except.

Nadia Alramli
la source
Je suggère de changer l'ordre des exemples. tryest toujours le premier choix. Les hasattrchèques sont uniquement - pour une raison vraiment obscure - que vous ne pouvez pas simplement utiliser try.
S.Lott
1
Je suggère d'utiliser fp.read(0)au lieu de fp.read()afin d'éviter de mettre tout le code dans le trybloc si vous souhaitez traiter les données par la fpsuite.
Meow
3
Notez fp.read()qu'avec de gros fichiers augmentera immédiatement l'utilisation de la mémoire.
Kyrylo Perevozchikov
Je comprends que c'est pythonique, mais nous devons ensuite lire le fichier deux fois parmi d'autres problèmes. Par exemple, Flaskj'ai fait cela et j'ai réalisé que l' FileStorageobjet sous-jacent avait besoin de la réinitialisation du pointeur après avoir été lu.
Adam Hughes le
2

Dans la plupart des cas, la meilleure façon de gérer cela est de ne pas le faire. Si une méthode prend un objet de type fichier et qu'il s'avère que l'objet qu'elle a passé ne l'est pas, l'exception qui est déclenchée lorsque la méthode essaie d'utiliser l'objet n'est pas moins informative que toute exception que vous auriez pu déclencher explicitement.

Il y a au moins un cas où vous voudrez peut-être faire ce genre de vérification, cependant, et c'est lorsque l'objet n'est pas immédiatement utilisé par ce à quoi vous l'avez passé, par exemple s'il est défini dans le constructeur d'une classe. Dans ce cas, je pense que le principe de l'EAFP est supplanté par le principe de «l'échec rapide». Je vérifierais l'objet pour m'assurer qu'il implémentait les méthodes dont ma classe a besoin (et qu'elles sont des méthodes), par exemple:

class C():
    def __init__(self, file):
        if type(getattr(file, 'read')) != type(self.__init__):
            raise AttributeError
        self.file = file
Robert Rossney
la source
1
Pourquoi getattr(file, 'read')au lieu de juste file.read? Cela fait exactement la même chose.
abarnert le
1
Plus important encore, cette vérification est erronée. Il se lèvera lorsqu'il sera donné, disons, une fileinstance réelle . (Les méthodes des instances des types intégrés / C-extension sont de type builtin_function_or_method, tandis que celles des classes à l'ancienne le sont instancemethod). Le fait qu'il s'agisse d'une classe à l'ancienne, et qu'elle utilise ==sur les types au lieu de ininstanceou issubclass, sont d'autres problèmes, mais si l'idée de base ne fonctionne pas, cela n'a guère d'importance.
abarnert le
2

J'ai fini par tomber sur ta question alors que j'écrivais un open fonction semblable à celle qui pouvait accepter un nom de fichier, un descripteur de fichier ou semblable à un fichier pré-ouvert.

Plutôt que de tester une readméthode, comme le suggèrent les autres réponses, j'ai fini par vérifier si l'objet peut être ouvert. Si c'est possible, c'est une chaîne ou un descripteur, et j'ai en main un objet de type fichier valide à partir du résultat. Si openlève a TypeError, alors l'objet est déjà un fichier.

Physicien fou
la source