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?
fmark
źródło
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
    
Tamás
źródło
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.

systempuntoout
źródło
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.

Tendayi Mawushe
źródło
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.

JudoWill
źródło
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.

John La Rooy
źródło
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.

stefanB
źródło
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)

cobbal
źródło
4

Instrukcja with działa z tak zwanymi menedżerami kontekstu:

http://docs.python.org/release/2.5.2/lib/typecontextmanager.html

Chodzi o to, aby uprościć obsługę wyjątków, wykonując niezbędne czyszczenie po opuszczeniu bloku „z”. Niektóre z wbudowanych Pythona działają już jako menedżery kontekstu.

zefciu
źródło
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.

bgse
źródło
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 ()

Tushar.PUCSD
źródło