Objekt aus einer Funktion mit Context Manager weiterverwenden?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
Judge
User
Beiträge: 129
Registriert: Mittwoch 13. Juni 2012, 22:27
Wohnort: Ratingen
Kontaktdaten:

Hallo zusammen,

ich stehe hier gerade ein bischen auf dem Schlauch und vergesse sicher etwas ganz einfaches und grundliegendes ... trotzdem: Ich komme nicht drauf:

Ich möchte gerne eine Funktion schreiben, die mit einem Context Manager ein Objekt erzeugt und zurückgibt. Dann möchte ich damit arbeiten und wenn ich fertig bin (falls das Script beendet wird), soll der Context Manager zuschlagen und die __exit__() routine des Objektes ausführen; sprich: Im folgendem Beispiel, die Datei schließen.

Grundsätzlich stelle ich mir das so vor:

Code: Alles auswählen

def get_file(path):
    with open(path, 'w') as f:
        return f


my_file = get_file("/tmp/foo.txt")

for x in range(1, 4):
    my_file.write(str(x))
Aber das geht so natürlich nicht, da so das Dateiobjekt direkt bei dem return geschlossen wird.

Mit yield komme ich zu ähnlichen Ergebnissen ...

Was ich im Endeffekt vorhabe ist mir damit mehrere gleiche DB Verbindungen zu erstellen, um nicht in jeder meiner Funktionen denselben Connection Code haben zu müssen ...

Wie genau geht das?
Sirius3
User
Beiträge: 17783
Registriert: Sonntag 21. Oktober 2012, 17:20

Der with-Block muß natürlich alles umfassen, das auf die Datei zugreift, aber das File-Objekt kannst Du natürlich ein einer eigenen Funktion erzeugen.

Code: Alles auswählen

def get_file(path):
    return open(path, 'w', encoding="utf8")


with get_file("/tmp/foo.txt") as may_file:
    for x in range(1, 4):
        may_file.write(str(x))
Benutzeravatar
Judge
User
Beiträge: 129
Registriert: Mittwoch 13. Juni 2012, 22:27
Wohnort: Ratingen
Kontaktdaten:

Yo, das wäre eine Möglichkeit.

Dachte, da gibt es ggf. noch einen Context-Manager - Kniff den ich nicht kenne mit `yield` oder ähnlichem ... aber das geht so natürlich auch.

Danke sehr!
Benutzeravatar
Dennis89
User
Beiträge: 1185
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

'yield' eher nicht. Das wird verwendet wenn du dir ein Generator-Objekt bauen willst.

https://stackoverflow.com/questions/175 ... -in-python

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
snafu
User
Beiträge: 6746
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Es gibt das atexit-Modul in der Standard-Bibliothek. Da könntest du jeweils die "Abräum-Methode" für deine Objekte registrieren lassen.

Beispiel-Code aus der Doku:

Code: Alles auswählen

def goodbye(name, adjective):
    print('Goodbye %s, it was %s to meet you.' % (name, adjective))

import atexit

atexit.register(goodbye, 'Donny', 'nice')
# or:
atexit.register(goodbye, adjective='nice', name='Donny')
atexit macht dann beim Beenden des Interpreters die entsprechenden Aufrufe. Geht mit Funktionen, aber auch mit Instanzen von Klassen. Bei letzterem sorgt das Registrieren dafür, dass das Objekt bis zum Schluss "am Leben" gehalten wird.
narpfel
User
Beiträge: 646
Registriert: Freitag 20. Oktober 2017, 16:10

Wenn du einen eigenen Contextmanager schreiben willst, gibt es `contextlib.contextmanager`:

Code: Alles auswählen

@contextlib.contextmanager
def open_files(p1, p2):
    with open(p1) as f1, open(p2) as f2:
        yield f1, f2
        print("closing files")

def use_files():
    with open_files("a", "b") as (a, b):
        print(a.read())
        print(b.read())
    print("files are closed")

Code: Alles auswählen

In [6]: use_files()
a

b

closing files
files are closed
Hier als Beispiel mit zwei geöffneten Dateien, weil du bei einer Datei auch einfach die Datei selbst zurückgeben kannst als Contextmanager, wie Sirius3 gezeigt hat.
Sirius3
User
Beiträge: 17783
Registriert: Sonntag 21. Oktober 2012, 17:20

@snafu: wenn man die Möglichkeit hat, den Code anzupassen, ist ein Kontextmanager immer die bessere Wahl zu atexit.
Benutzeravatar
snafu
User
Beiträge: 6746
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Mit yield konnte ich mich im Zusammenhang mit einem Context Manager noch nie anfreunden. Dann lieber eine Klasse mit explizitem __enter__() und __exit__(). Ist etwas mehr zu schreiben, aber IMHO wesentlich verständlicher.

Schon die Verwendung von yield innerhalb einer Schleife überfordert einige. Dass man yield bzw. den dahinter stehenden Generator grundsätzlich auch für das Erzeugen eines einzelnen Rückgabewerts benutzen kann, um den Code an der Stelle quasi anzuhalten und später diesen Zustand abzurufen, dürfte bei so manchem Programmierer für Gehirnverknotungen sorgen. Ich vermeide deshalb solche Konstrukte, aber ich spreche hier natürlich nicht für alle. Habe bisher auch so meine Probleme mit asynchroner Programmierung (was ja so ein bisschen in die Richtung geht). Aber wer's mag... :mrgreen:
Benutzeravatar
__blackjack__
User
Beiträge: 13170
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Also ich mag das lieber als eine Klasse mit `__enter__()` und `__exit__()` weil es einfacher und übersichtlicher ist. Und ich denke auch nicht das man Gehirnverknotungen bekommt wenn man da gar nicht so genau drüber nachdenkt, sondern sich einfach das Muster aus der Dokumentation anschaut:

Code: Alles auswählen

Typical usage:

    @contextmanager
    def some_generator(<arguments>):
        <setup>
        try:
            yield <value>
        finally:
            <cleanup>

This makes this:

    with some_generator(<arguments>) as <variable>:
        <body>

equivalent to this:

    <setup>
    try:
        <variable> = <value>
        <body>
    finally:
        <cleanup>
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
Benutzeravatar
snafu
User
Beiträge: 6746
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Noch kürzer wäre ``atexit.register(some_object.cleanup)``, weil man dafür weder eine Hilfsklasse, noch eine Hilfsfunktion benötigt. Das ist zumindest dann praktisch, wenn man schon vorab weiß, dass man die Methode wirklich erst am Schluss aufrufen möchte (was ja hier anscheinend der Fall ist). Aber jeder wie er mag. Die Diskussion ist mal wieder typisch Python-Forum, wo alternative Ansätze nur sehr selten angenommen werden und ebenso selten erklärt wird, warum der andere Ansatz denn so schlecht ist. Es ist einfach so und sollte nicht näher hinterfragt werden. ;)
Benutzeravatar
__blackjack__
User
Beiträge: 13170
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Das weiss man aber nicht, dass das erst am Schluss aufgerufen werden soll. Man schliesst dann aus, dass man da jemals irgendwann mal etwas machen will wo das nicht der Fall ist. Das fängt schon bei so etwas simplen wie Unit-Tests an. Und es werden Objekte bis ans Ende am Leben erhalten. Das ist wie bei `sys.exit()`: man sollte das wirklich nur in der `main()`-Funktion oder sehr ”nahe” des Hauptprogramms machen und nicht irgendwo im Programm. Ähnlich wie `sys.exit()` ist das sonst ganz schnell ein hässlicher Hack mit Folgen für die Weiterentwicklung, weil man sich irgendwo um eine saubere, ordentliche Ressourcenverwaltung drücken wollte und die ”praktische” Abkürzung genommen hat.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
Benutzeravatar
snafu
User
Beiträge: 6746
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@Judge
Was sind das eigentlich konkret für Objekte (von welcher Schnittstelle)? Sofern die eine close()-Methode haben, dann könntest du auch einfach ``contextlib.closing()`` benutzen, anstatt den Kontext-Manager selber schreiben zu müssen.
Antworten