Python: Standardfunktion und Kontextmanager?

9

In Python gibt es viele Funktionen, die sowohl als Standardfunktionen als auch als Kontextmanager funktionieren. Zum Beispiel kann open() entweder wie folgt aufgerufen werden:

%Vor%

oder

%Vor%

Beide geben Ihnen ein Objekt my_file , mit dem Sie alles tun können, was Sie brauchen. Im Allgemeinen ist das Spätere vorzuziehen, aber es gibt Zeiten, in denen man auch Ersteres machen möchte.

Ich konnte herausfinden, wie man einen Kontextmanager schreibt, indem ich entweder eine Klasse mit den Funktionen __enter__ und __exit__ oder mit @contextlib.contextmanager dekorator für eine Funktion und yield anstelle von% erstelle. Code%. Aber wenn ich das tue, kann ich die Funktion nicht mehr direkt verwenden - mit dem Decorator zum Beispiel bekomme ich ein return -Objekt zurück statt das gewünschte Ergebnis. Natürlich würde ich, wenn ich es als Klasse machen würde, nur eine Instanz der Generatorklasse bekommen, von der ich annehme, dass sie im Wesentlichen die gleiche ist.

Wie kann ich also eine Funktion (oder Klasse) entwerfen, die entweder als Funktion funktioniert, ein Objekt oder einen Kontextmanager zurückgibt und _GeneratorContextManager oder ähnliches zurückgibt?

bearbeiten:

Beispiel: Ich habe eine Funktion wie die folgende (das ist stark vereinfacht):

%Vor%

Also nimmt die Funktion eine Reihe von Argumenten, macht Zeug mit ihnen und verwendet das Ergebnis dieser Dinge, um eine Klasse zu initialisieren, die sie dann zurückgibt. Das Endergebnis ist, dass ich eine Instanz von _GeneratorContextManager habe, genauso wie ich ein Objekt my_class hätte, wenn ich file aufgerufen hätte. Wenn ich diese Funktion als Kontextmanager verwenden möchte, kann ich sie wie folgt ändern:

%Vor%

Das funktioniert gut, wenn ich als Context-Manager aufrufe, aber ich erhalte nicht mehr eine Instanz von open , wenn ich als direkte Funktion aufrufe. Vielleicht mache ich nur etwas falsch?

Bearbeiten 2:

Beachten Sie, dass ich volle Kontrolle über my_class habe, einschließlich der Möglichkeit, Funktionen hinzuzufügen. Aus der untenstehenden Antwort konnte ich schließen, dass meine Schwierigkeit auf einem grundlegenden Missverständnis beruhte: Ich dachte, dass alles, was ich angerufen habe ( my_class im obigen Beispiel), die Funktionen my_func und __exit__ haben muss. Das ist nicht richtig. In der Tat ist es nur, was die Funktion zurückgibt ( __enter__ im obigen Beispiel), die die Funktionen benötigt, um als Kontextmanager zu arbeiten.

    
ibrewster 11.02.2016, 21:59
quelle

2 Antworten

1

Die Schwierigkeit besteht darin, dass für eine Funktion, die sowohl als Kontextmanager ( with foo() as x ) als auch als reguläre Funktion ( x = foo() ) verwendet wird, das von der Funktion zurückgegebene Objekt beides haben muss __enter__ und __exit__ Methoden ... und im allgemeinen Fall gibt es keine großartige Möglichkeit, Methoden zu einem bestehenden Objekt hinzuzufügen.

Ein Ansatz könnte darin bestehen, eine Wrapper-Klasse zu erstellen, die __getattr__ verwendet, um Methoden und Attribute an das ursprüngliche Objekt zu übergeben:

%Vor%

Aber das wird subtile Probleme verursachen, weil es nicht genau so ist wie das Objekt, das von der ursprünglichen Funktion zurückgegeben wurde (zB isinstance Tests werden fehlschlagen, einige eingebaute wie iter(obj) wird nicht wie erwartet funktionieren, etc).

Sie können das zurückgegebene Objekt auch wie hier demonstriert dynamisch unterklassen: Ссылка :

%Vor%

Aber dieser Ansatz hat auch Probleme (wie in dem verlinkten Beitrag erwähnt), und es ist eine Ebene der Magie, die ich persönlich ohne starke Rechtfertigung nicht gerne vorstellen würde.

Im Allgemeinen bevorzuge ich das explizite Hinzufügen von __enter__ und __exit__ Methoden oder die Verwendung eines Helfers wie contextlib.closing :

%Vor%     
David Wolever 11.02.2016, 22:27
quelle
1

Nur zur Klarheit: Wenn Sie my_class ändern können, würden Sie natürlich die __enter__/__exit__ descriptors zu dieser Klasse hinzufügen.

Wenn Sie my_class nicht ändern können (was ich aus Ihrer Frage abgeleitet habe), ist dies die Lösung, auf die ich mich bezog:

%Vor%

Als Dekorateur:

%Vor%     
apex-meme-lord 12.02.2016 05:30
quelle

Tags und Links