Do czego służy instrukcja „with” w pythonie?


418

Po raz pierwszy natknąłem się na withinstrukcję Python . Używam Pythona od kilku miesięcy i nawet nie wiedziałem o jego istnieniu! Biorąc pod uwagę jego nieco niejasny status, pomyślałem, że warto zapytać:

  1. Do czego służy instrukcja Python with?
  2. Do czego tego używasz?
  3. Czy są jakieś problemy, o których muszę wiedzieć, lub powszechne anty-wzorce związane z ich użyciem? Jakieś przypadki, w których lepiej jest użyć try..finallyniż with?
  4. Dlaczego nie jest stosowany szerzej?
  5. Jakie standardowe klasy bibliotek są z nim zgodne?

5
Dla przypomnienia, tutaj jestwith w dokumentacji Python 3.
— Aleksiej

pochodzący ze środowiska Java, pomaga mi zapamiętać go jako odpowiednią „próbę z zasobami” w Javie, nawet jeśli może to nie być całkowicie poprawne.
— vefthym

Odpowiedzi:


399
  1. Sądzę, że inni użytkownicy przede mną już na to odpowiedzieli, dlatego dodaję to tylko ze względu na kompletność: withoświadczenie upraszcza obsługę wyjątków, zamykając typowe zadania przygotowania i czyszczenia w tak zwanych menedżerach kontekstu . Więcej szczegółów można znaleźć w PEP 343 . Na przykład openinstrukcja jest sama w sobie menedżerem kontekstu, który pozwala otworzyć plik, pozostawić otwarty, dopóki wykonanie jest w kontekście withinstrukcji, w której go użyłeś, i zamknąć go, gdy tylko opuścisz kontekst, bez względu na to, czy go opuściłeś z powodu wyjątku, czy podczas regularnego przepływu kontroli. withOświadczenie może być więc wykorzystywane w sposób podobny do wzoru RAII w C ++ jakiś zasób jest nabywany przezwithinstrukcja i zwolniona, gdy opuścisz withkontekst.

  2. Oto kilka przykładów: otwieranie plików za pomocą with open(filename) as fp:, uzyskiwanie blokad za pomocą with lock:(gdzie lockjest instancja threading.Lock). Możesz także zbudować własne menedżery kontekstu za pomocą contextmanagerdekoratora z contextlib. Na przykład często używam tego, gdy muszę tymczasowo zmienić bieżący katalog, a następnie wrócić do miejsca, w którym byłem:

    from contextlib import contextmanager
    import os
    
    @contextmanager
    def working_directory(path):
        current_dir = os.getcwd()
        os.chdir(path)
        try:
            yield
        finally:
            os.chdir(current_dir)
    
    with working_directory("data/stuff"):
        # do something within data/stuff
    # here I am back again in the original working directory
    

    Oto kolejny przykład, który tymczasowo przekierowania sys.stdin, sys.stdouta sys.stderrdo innego uchwytu pliku i przywraca je później:

    from contextlib import contextmanager
    import sys
    
    @contextmanager
    def redirected(**kwds):
        stream_names = ["stdin", "stdout", "stderr"]
        old_streams = {}
        try:
            for sname in stream_names:
                stream = kwds.get(sname, None)
                if stream is not None and stream != getattr(sys, sname):
                    old_streams[sname] = getattr(sys, sname)
                    setattr(sys, sname, stream)
            yield
        finally:
            for sname, stream in old_streams.iteritems():
                setattr(sys, sname, stream)
    
    with redirected(stdout=open("/tmp/log.txt", "w")):
         # these print statements will go to /tmp/log.txt
         print "Test entry 1"
         print "Test entry 2"
    # back to the normal stdout
    print "Back to normal stdout again"
    

    I na koniec kolejny przykład, który tworzy folder tymczasowy i czyści go po opuszczeniu kontekstu:

    from tempfile import mkdtemp
    from shutil import rmtree
    
    @contextmanager
    def temporary_dir(*args, **kwds):
        name = mkdtemp(*args, **kwds)
        try:
            yield name
        finally:
            shutil.rmtree(name)
    
    with temporary_dir() as dirname:
        # do whatever you want
    

20
Dziękujemy za dodanie porównania do RAII. Jako programista C ++, który powiedział mi wszystko, co musiałem wiedzieć.
— Fred Thomsen

OK, więc pozwól mi to wyjaśnić. Mówisz, że withinstrukcja ma na celu wypełnienie zmiennej danymi, dopóki instrukcje w niej nie zostaną ukończone, a następnie zwolnić zmienną?
— Musixauce3000

Ponieważ użyłem go do otwarcia skryptu py. with open('myScript.py', 'r') as f: pass. Spodziewałem się, aby móc nazwać zmienną faby zobaczyć treść tekstu dokumentu, jak to, co wydaje się, jeżeli dokument zostały przypisane fpoprzez regularne openoświadczenie: f = open('myScript.py').read(). Ale zamiast tego mam następujący: <_io.TextIOWrapper name='myScript.py' mode='r' encoding='cp1252'>. Co to znaczy?
— Musixauce3000,

3
@ Musixauce3000 - użycie withnie usuwa potrzeby readrzeczywistego pliku. Do withrozmowy open- nie wie, co trzeba zrobić z nim - może chcesz zrobić szukać na przykład.
— Tony Suffolk 66

@ Musixauce3000 withInstrukcja może wypełnić zmienną danymi lub wprowadzić inne zmiany w środowisku, dopóki instrukcje w niej zawarte nie będą kompletne, a następnie przeprowadzi niezbędne czyszczenie. Rodzaje czyszczenia, które można wykonać, to na przykład zamknięcie otwartego pliku lub, jak w tym przykładzie @Tamas, zmiana katalogów z powrotem do poprzedniej lokalizacji itp. Ponieważ Python ma funkcje usuwania śmieci, zwolnienie zmiennej nie jest ważne przypadek użycia. withjest zwykle używany do innych rodzajów czyszczenia.
— Bob Steinke,

89

Sugerowałbym dwa ciekawe wykłady:

  • PEP 343 Oświadczenie „z”
  • Effbot Zrozumienie instrukcji „z” Pythona

1.with stwierdzenie jest stosowany do zawijania wykonanie bloku z metod zdefiniowanych przez menedżera kontekstu. Umożliwia try...except...finallyto enkapsulowanie typowych wzorców użytkowania w celu wygodnego ponownego użycia.

2. Możesz zrobić coś takiego:

with open("foo.txt") as foo_file:
    data = foo_file.read()

LUB

from contextlib import nested
with nested(A(), B(), C()) as (X, Y, Z):
   do_something()

LUB (Python 3.1)

with open('data') as input_file, open('result', 'w') as output_file:
   for line in input_file:
     output_file.write(parse(line))

LUB

lock = threading.Lock()
with lock:
    # Critical section of code

3. Nie widzę tu żadnego Antipattern.
Cytując Zanurz się w Pythonie :

spróbuj .. w końcu jest dobry. z jest lepsze.

4. Wydaje mi się, że jest to związane z nawykami programistów do używania try..catch..finallyinstrukcji z innych języków.


4
To naprawdę się sprawdza, gdy mamy do czynienia z obiektami synchronizacji wątków. Stosunkowo rzadko w Pythonie, ale kiedy ich potrzebujesz, naprawdę potrzebujesz with.
— detly

1
diveintopython.org jest wyłączony (na stałe?). Odzwierciedlenie
— przytula

Przykład dobrej odpowiedzi, otwarty plik jest doskonałym przykładem, który pokazuje za kulisami otwierania, zamykania, zamykania operacji na plikach, są one ukryte pod niestandardową nazwą referencyjną
— Angry 84

40

Instrukcja Python withjest wbudowaną obsługą języka Resource Acquisition Is Initializationidiomu powszechnie używanego w C ++. Ma on umożliwić bezpieczne pozyskiwanie i uwalnianie zasobów systemu operacyjnego.

withInstrukcja tworzy zasoby ramach zakresu / bloku. Pisz kod za pomocą zasobów w bloku. Po wyjściu z bloku zasoby są zwalniane bez względu na wynik kodu w bloku (tj. Czy blok kończy się normalnie, czy z powodu wyjątku).

Wiele zasobów w bibliotece Python, które są zgodne z protokołem wymaganym przez withinstrukcję i mogą być używane z nią od razu po wyjęciu z pudełka. Jednak każdy może tworzyć zasoby, które można wykorzystać w instrukcji with, poprzez wdrożenie dobrze udokumentowanego protokołu: PEP 0343

Używaj go za każdym razem, gdy nabywasz zasoby w swojej aplikacji, które muszą zostać jawnie zrzeczone, takie jak pliki, połączenia sieciowe, blokady i tym podobne.


27

Ponownie dla kompletności dodam mój najbardziej użyteczny przypadek użycia withinstrukcji.

Robię dużo obliczeń naukowych i do niektórych czynności potrzebuję Decimalbiblioteki do dowolnych obliczeń precyzji. Pewnej części mojego kodu potrzebuję wysokiej precyzji, a dla większości innych części potrzebuję mniejszej precyzji.

Ustawiłem moją domyślną precyzję na niską liczbę, a następnie używam, withaby uzyskać bardziej precyzyjną odpowiedź dla niektórych sekcji:

from decimal import localcontext

with localcontext() as ctx:
    ctx.prec = 42   # Perform a high precision calculation
    s = calculate_something()
s = +s  # Round the final result back to the default precision

Używam tego bardzo często w teście hipergeometrycznym, który wymaga podziału dużych liczb wynikających z silni. Podczas wykonywania obliczeń w skali genomowej należy uważać na błędy zaokrąglania i przepełnienia.


26

Przykładem antipatternu może być użycie withwewnętrznej pętli, gdy bardziej efektywne byłoby posiadanie withzewnętrznej pętli

na przykład

for row in lines:
    with open("outfile","a") as f:
        f.write(row)

vs

with open("outfile","a") as f:
    for row in lines:
        f.write(row)

Pierwszym sposobem jest otwieranie i zamykanie pliku dla każdego, rowco może powodować problemy z wydajnością w porównaniu z drugim sposobem z otwieraniem i zamykaniem pliku tylko raz.


10

Zobacz PEP 343 - Instrukcja „z” , na końcu znajduje się przykładowa sekcja.

... nowa instrukcja „with” w języku Python, aby umożliwić rozróżnienie standardowych zastosowań instrukcji try / wreszcie.


5

punkty 1, 2 i 3 są odpowiednio uwzględnione:

4: jest stosunkowo nowy, dostępny tylko w python2.6 + (lub python2.5 przy użyciu from __future__ import with_statement)



3

Innym przykładem gotowej obsługi, który na początku może być nieco zaskakujący, gdy przyzwyczaisz się do zachowania wbudowanych open(), są connectionobiekty popularnych modułów bazy danych, takich jak:

Te connectionobiekty są menedżerowie kontekście i jako takie mogą być używane out-of-the-box w miłym with-statement, jednak przy stosowaniu tego pamiętać, że:

Po with-blockzakończeniu, z wyjątkiem lub bez, połączenie nie zostanie zamknięte . W przypadku with-blockzakończenia z wyjątkiem transakcja jest wycofywana, w przeciwnym razie transakcja zostaje zatwierdzona.

Oznacza to, że programista musi zadbać o to, aby sam zamknął połączenie, ale pozwala uzyskać połączenie i używać go w wielu miejscach with-statements, jak pokazano w dokumentach psycopg2 :

conn = psycopg2.connect(DSN)

with conn:
    with conn.cursor() as curs:
        curs.execute(SQL1)

with conn:
    with conn.cursor() as curs:
        curs.execute(SQL2)

conn.close()

W powyższym przykładzie zauważysz, że cursorobiektami psycopg2są również menedżery kontekstu. Z odpowiedniej dokumentacji dotyczącej zachowania:

Po cursorwyjściu with-blockjest ono zamknięte, uwalniając wszelkie zasoby, które ostatecznie z nim są powiązane. Nie ma to wpływu na stan transakcji.


3

W pythonie zazwyczaj używana jest instrukcja „ with ” do otwarcia pliku, przetworzenia danych zawartych w pliku, a także do zamknięcia pliku bez wywoływania metody close (). Instrukcja „with” upraszcza obsługę wyjątków, zapewniając czynności czyszczenia.

Ogólna forma z:

with open(“file name”, “mode”) as file-var:
    processing statements

Uwaga: nie ma potrzeby zamykania pliku przez wywołanie close () po file-var.close ()

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.