Überprüfen Sie, ob das Objekt in Python dateiähnlich ist

93

Dateiähnliche Objekte sind Objekte in Python, die sich wie eine echte Datei verhalten, z. B. eine read () - und eine write-Methode (), aber eine andere Implementierung haben. Es ist und Umsetzung des Duck Typing- Konzepts.

Es wird als bewährte Methode angesehen , ein dateiähnliches Objekt überall dort zuzulassen, wo eine Datei erwartet wird, sodass beispielsweise ein StringIO- oder ein Socket-Objekt anstelle einer realen Datei verwendet werden kann. Es ist also schlecht, eine Prüfung wie folgt durchzuführen:

if not isinstance(fp, file):
   raise something

Wie kann am besten überprüft werden, ob ein Objekt (z. B. ein Parameter einer Methode) "dateiähnlich" ist?

dmeister
quelle

Antworten:

45

Es ist im Allgemeinen keine gute Praxis, solche Überprüfungen in Ihrem Code durchzuführen, es sei denn, Sie haben spezielle Anforderungen.

In Python ist die Eingabe dynamisch. Warum müssen Sie überprüfen, ob das Objekt dateiähnlich ist, anstatt es nur als Datei zu verwenden und den daraus resultierenden Fehler zu behandeln?

Jede Überprüfung, die Sie durchführen können, wird ohnehin zur Laufzeit durchgeführt. Wenn Sie also etwas tun if not hasattr(fp, 'read')und eine Ausnahme auslösen, ist dies wenig nützlicher als nur das Aufrufen fp.read()und Behandeln des resultierenden Attributfehlers, wenn die Methode nicht vorhanden ist.

Tendayi Mawushe
quelle
whywas Operatoren wie __add__, __lshift__oder __or__in benutzerdefinierten Klassen? ( Dateiobjekt und API: docs.python.org/glossary.html#term-file-object )
n611x007
@naxa: Und was genau ist mit diesen Operatoren?
Martineau
31
Oft funktioniert es einfach, aber ich kaufe nicht die Pythonic-Maxime, dass es falsch ist, wenn es in Python schwierig ist. Stellen Sie sich vor, Sie erhalten ein Objekt und es gibt 10 verschiedene Dinge, die Sie je nach Typ mit diesem Objekt tun können. Sie werden nicht jede Möglichkeit ausprobieren und den Fehler behandeln, bis Sie ihn endlich richtig verstanden haben. Das wäre völlig ineffizient. Sie müssen nicht unbedingt fragen, um welchen Typ es sich handelt, aber Sie müssen fragen können, ob dieses Objekt die Schnittstelle X implementiert.
jcoffland
31
Die Tatsache, dass die Python-Sammlungsbibliothek sogenannte "Schnittstellentypen" (z. B. Sequenz) bereitstellt, spricht dafür, dass dies selbst in Python häufig nützlich ist. Wenn jemand fragt, wie man foo macht, ist "nicht foo" im Allgemeinen keine sehr zufriedenstellende Antwort.
AdamC
1
AttributeError kann aus allen möglichen Gründen ausgelöst werden, die nichts damit zu tun haben, ob das Objekt die von Ihnen benötigte Schnittstelle unterstützt. hasattr wird für filelikes benötigt, die nicht von IOBase abgeleitet sind
Erik Aronesty
74

Für 3.1+ eine der folgenden Möglichkeiten:

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

Für 2.x ist "dateiähnliches Objekt" zu vage, um es zu überprüfen, aber die Dokumentation für alle Funktionen, mit denen Sie sich befassen, wird Ihnen hoffentlich sagen, was sie tatsächlich benötigen. Wenn nicht, lesen Sie den Code.


Wie andere Antworten zeigen, müssen Sie zunächst fragen, worauf Sie genau achten. Normalerweise ist EAFP ausreichend und idiomatischer.

Das Glossar sagt „file-ähnliches Objekt“ ist ein Synonym für „Dateiobjekt“, was letztlich bedeutet , dass es eine Instanz von einem der drei ist abstrakte Basisklassen in definiert das ioModul , die sich alle Subklassen von sind IOBase. Die Art der Überprüfung ist also genau wie oben gezeigt.

(Die Überprüfung IOBaseist jedoch nicht sehr nützlich. Können Sie sich einen Fall vorstellen, in dem Sie eine tatsächliche dateiähnliche read(size)Funktion von einer Funktion mit einem Argument unterscheiden müssen read, die nicht dateiähnlich ist, ohne auch zwischen Textdateien und RAW unterscheiden zu müssen? Binärdateien? Sie möchten also eigentlich fast immer überprüfen, z. B. "Ist ein Textdateiobjekt", nicht "Ist ein dateiähnliches Objekt".)


Während das ioModul für 2.x seit 2.6+ existiert, sind integrierte Dateiobjekte keine Instanzen von ioKlassen, keines der dateiähnlichen Objekte in der stdlib und auch nicht die meisten dateiähnlichen Objekte von Drittanbietern sind wahrscheinlich zu begegnen. Es gab keine offizielle Definition dessen, was "dateiähnliches Objekt" bedeutet; es ist nur „so etwas wie ein eingebautes Dateiobjekt “, und verschiedene Funktionen bedeuten verschiedene Dinge durch „wie“. Solche Funktionen sollten dokumentieren, was sie bedeuten; Wenn dies nicht der Fall ist, müssen Sie sich den Code ansehen.

Die gebräuchlichsten Bedeutungen sind jedoch "hat read(size)", "hat read()" oder "ist eine Iteration von Zeichenfolgen", aber einige alte Bibliotheken erwarten möglicherweise readlineanstelle einer dieser, einige Bibliotheken mögen close()Dateien, die Sie ihnen geben, andere erwarten dies, wenn filenovorhanden ist, dann sind andere Funktionen verfügbar usw. Und ähnlich für write(buf)(obwohl es in dieser Richtung viel weniger Optionen gibt).

abarnert
quelle
1
Schließlich ist jemand es real zu halten.
Anthony Rutledge
16
Die einzig nützliche Antwort. Warum StackOverflowers weiterhin abstimmt "Hör auf zu tun, was du versuchst, weil ich es besser weiß ... und PEP 8, EAFP und so!" Beiträge sind jenseits meiner zerbrechlichen Vernunft. ( Vielleicht weiß Cthulhu es? )
Cecil Curry
1
Weil wir auf viel zu viel Code gestoßen sind, der von Leuten geschrieben wurde, die nicht vorausgedacht haben, und der kaputt geht, wenn Sie etwas übergeben, das fast, aber keine Datei ist, weil sie explizit prüfen. Die ganze Sache mit der EAFP-Ententypisierung ist kein Bullshit-Reinheitstest. Es ist eine tatsächliche egineering Entscheidung,
drxzcl
1
Dies kann als besseres Engineering angesehen werden und ich würde es persönlich bevorzugen, aber möglicherweise nicht funktionieren. Es ist im Allgemeinen nicht erforderlich, dass dateiähnliche Objekte von erben IOBase. Zum Beispiel geben Ihnen Pytest-Scheinwerfer, _pytest.capture.EncodedFiledie von nichts erben.
Tomáš Gavenčiak
45

Wie andere gesagt haben, sollten Sie solche Überprüfungen generell vermeiden. Eine Ausnahme ist, wenn das Objekt zu Recht unterschiedliche Typen hat und Sie je nach Typ ein unterschiedliches Verhalten wünschen. Die EAFP-Methode funktioniert hier nicht immer, da ein Objekt wie mehr als eine Entenart aussehen kann!

Ein Initialisierer könnte beispielsweise eine Datei, einen String oder eine Instanz seiner eigenen Klasse verwenden. Sie könnten dann Code haben wie:

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

Die Verwendung von EAFP kann hier alle möglichen subtilen Probleme verursachen, da jeder Initialisierungspfad teilweise ausgeführt wird, bevor eine Ausnahme ausgelöst wird. Im Wesentlichen ahmt diese Konstruktion die Überlastung der Funktion nach und ist daher nicht sehr pythonisch, kann jedoch bei vorsichtiger Verwendung nützlich sein.

Nebenbei bemerkt, Sie können die Dateiprüfung in Python 3 nicht auf die gleiche Weise durchführen isinstance(f, io.IOBase). Stattdessen benötigen Sie so etwas wie .

Scott Griffiths
quelle
27

Das vorherrschende Paradigma ist hier EAFP: leichter um Vergebung als um Erlaubnis zu bitten. Verwenden Sie die Dateischnittstelle, behandeln Sie dann die resultierende Ausnahme oder lassen Sie sie an den Aufrufer weitergeben.

drxzcl
quelle
9
+1: Wenn xes nicht dateiähnlich ist, x.read()wird eine eigene Ausnahme ausgelöst. Warum eine zusätzliche if-Anweisung schreiben? Verwenden Sie einfach das Objekt. Es wird entweder funktionieren oder brechen.
S.Lott
3
Behandle nicht einmal die Ausnahme. Wenn jemand etwas übergeben hat, das nicht der erwarteten API entspricht, ist dies nicht Ihr Problem.
Hablabit
1
@ Aaron Gallagher: Ich bin nicht sicher. Ist Ihre Aussage wahr, auch wenn es mir schwer fällt, einen konsistenten Zustand aufrechtzuerhalten?
Meister
1
Um einen konsistenten Status beizubehalten, können Sie "try / finally" (aber keine Ausnahme!) Oder die neue Anweisung "with" verwenden.
drxzcl
Dies steht auch im Einklang mit dem Paradigma "Schnell scheitern und laut scheitern". Wenn Sie nicht akribisch vorgehen, können explizite hasattr (...) Überprüfungen gelegentlich dazu führen, dass eine Funktion / Methode normal zurückgegeben wird, ohne die beabsichtigte Aktion auszuführen.
Ben Burns
11

Es ist oft nützlich, einen Fehler durch Überprüfen einer Bedingung auszulösen, wenn dieser Fehler normalerweise erst viel später ausgelöst wird. Dies gilt insbesondere für die Grenze zwischen "User-Land" - und "API" -Code.

Sie würden keinen Metalldetektor auf einer Polizeistation an der Ausgangstür platzieren, Sie würden ihn am Eingang platzieren! Wenn das Nichtprüfen einer Bedingung bedeutet, dass möglicherweise ein Fehler auftritt, der 100 Zeilen früher oder in einer Superklasse abgefangen werden könnte, anstatt in der Unterklasse ausgelöst zu werden, ist die Überprüfung nichts Falsches.

Die Überprüfung auf korrekte Typen ist auch dann sinnvoll, wenn Sie mehr als einen Typ akzeptieren. Es ist besser, eine Ausnahme mit der Meldung "Ich benötige eine Unterklasse von Basisstreifen oder ODER-Datei" auszulösen, als nur eine Ausnahme auszulösen, da einige Variablen keine Suchmethode haben ...

Dies bedeutet nicht, dass Sie verrückt werden und dies überall tun. Zum größten Teil stimme ich dem Konzept zu, dass Ausnahmen sich selbst auslösen. Wenn Sie jedoch Ihre API drastisch klarstellen oder unnötige Codeausführung vermeiden können, weil eine einfache Bedingung nicht erfüllt ist tun Sie dies!

Ben DeMott
quelle
1
Ich bin damit einverstanden, aber im Sinne, dass man nicht überall verrückt danach wird - viele dieser Bedenken sollten sich während des Testens herausstellen, und einige der Fragen, wo man dies fängt / wie man es dem Benutzer anzeigt, werden durch Usability-Anforderungen beantwortet.
Ben Burns
7

Sie können versuchen, die Methode aufzurufen und dann die Ausnahme abfangen:

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

Wenn Sie nur eine Lese- und eine Schreibmethode wünschen, können Sie dies tun:

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

Wenn ich du wäre, würde ich mit der try / Except-Methode gehen.

Nadia Alramli
quelle
Ich würde vorschlagen, die Reihenfolge der Beispiele zu ändern. tryist immer die erste Wahl. Die hasattrSchecks sind nur - aus einem wirklich unbekannten Grund - nicht einfach zu verwenden try.
S.Lott
1
Ich schlage vor, fp.read(0)anstelle von fp.read()zu verwenden, um zu vermeiden, dass der gesamte Code in den tryBlock eingefügt wird, wenn Sie die Daten fpanschließend verarbeiten möchten .
Miau
3
Beachten Sie, dass fp.read()bei großen Dateien die Speichernutzung sofort erhöht wird.
Kyrylo Perevozchikov
Ich verstehe, dass dies pythonisch ist, aber dann müssen wir die Datei unter anderem zweimal lesen. Zum Beispiel habe Flaskich dies getan und festgestellt, dass das zugrunde liegende FileStorageObjekt nach dem Lesen einen Zeiger zurücksetzen muss.
Adam Hughes
2

In den meisten Fällen ist dies am besten nicht der Fall. Wenn eine Methode ein dateiähnliches Objekt verwendet und sich herausstellt, dass das übergebene Objekt nicht vorhanden ist, ist die Ausnahme, die ausgelöst wird, wenn die Methode versucht, das Objekt zu verwenden, nicht weniger informativ als jede Ausnahme, die Sie möglicherweise explizit ausgelöst haben.

Es gibt jedoch mindestens einen Fall, in dem Sie diese Art der Überprüfung durchführen möchten, und dann wird das Objekt nicht sofort von dem verwendet, an das Sie es übergeben haben, z. B. wenn es im Konstruktor einer Klasse festgelegt wird. In diesem Fall würde ich denken, dass das Prinzip der EAFP durch das Prinzip "schnell scheitern" übertroffen wird. Ich würde das Objekt überprüfen, um sicherzustellen, dass es die Methoden implementiert, die meine Klasse benötigt (und dass es sich um Methoden handelt), z.

class C():
    def __init__(self, file):
        if type(getattr(file, 'read')) != type(self.__init__):
            raise AttributeError
        self.file = file
Robert Rossney
quelle
1
Warum getattr(file, 'read')statt nur file.read? Dies macht genau das Gleiche.
abarnert
1
Noch wichtiger ist, dass diese Prüfung falsch ist. Es wird ausgelöst, wenn filebeispielsweise eine tatsächliche Instanz angegeben wird. (Methoden von Instanzen von eingebauten / C-Erweiterungstypen sind vom Typ builtin_function_or_method, während die von Klassen alten Stils sind instancemethod). Die Tatsache, dass dies eine Klasse alten Stils ist und ==für Typen anstelle von ininstanceoder verwendet wird issubclass, sind weitere Probleme, aber wenn die Grundidee nicht funktioniert, spielt das kaum eine Rolle.
Abarnert
2

Ich bin auf Ihre Frage gestoßen, als ich eine openähnliche Funktion geschrieben habe, die einen Dateinamen, einen Dateideskriptor oder ein vorgeöffnetes dateiähnliches Objekt akzeptieren kann.

Anstatt nach einer readMethode zu testen , wie die anderen Antworten vermuten lassen, habe ich am Ende geprüft, ob das Objekt geöffnet werden kann. Wenn dies möglich ist, handelt es sich um eine Zeichenfolge oder einen Deskriptor, und ich habe aus dem Ergebnis ein gültiges dateiähnliches Objekt in der Hand. Wenn a openausgelöst wird TypeError, ist das Objekt bereits eine Datei.

Verrückter Physiker
quelle