2014-10-28 47 views
6

Czytałem, że plik otwarty jak to zostanie automatycznie zamknięte po opuszczeniu z bloku:z i zamykanie plików w Pythonie

with open("x.txt") as f: 
    data = f.read() 
    do something with data 

jeszcze podczas otwierania z sieci, muszę to:

from contextlib import closing 
from urllib.request import urlopen 

with closing(urlopen('http://www.python.org')) as page: 
    for line in page: 
     print(line) 

dlaczego i jaka jest różnica? (Używam Python3)

+0

Chciałem zasugerować, że ktoś powinien zgłosić błąd do dokumentacji na 'contextlib', a jeśli to nie ty, ktokolwiek to powinien przyznać ... ale zanim skończyłem, Martijn już zgłosił błąd, z linkiem wróć tutaj. :) – abarnert

Odpowiedz

6

Dane trochę technicznych, więc zacznijmy od prostej wersji:

Niektóre wiedzą, jak stosować w with oświadczeniu. Obiekty plików, takie jak to, co otrzymałeś z numeru open, są przykładem takiego typu. Jak się okazuje, obiekty, które otrzymasz od urllib.request.urlopen, są również przykładem takiego typu, więc twój drugi przykład może być napisany tak samo jak pierwszy.

Ale niektóre typy nie wiedzą, jak używać w oświadczeniu with.Funkcja closing jest przeznaczona do owijania takich typów - o ile mają one metodę close, wywoła ona ich metodę close po zakończeniu instrukcji with.

Oczywiście niektóre typy nie wiem, w jaki sposób mają być wykorzystywane w with oświadczenia, a także nie może być używany z closing ponieważ ich metoda oczyszczania nie jest nazwany close (lub ponieważ ich czyszczenie jest bardziej skomplikowana niż tylko ich zamknięcie). W takim przypadku musisz napisać niestandardowy menedżer kontekstów. Ale nawet to nie jest zazwyczaj takie trudne.


W kategoriach technicznych:

with stwierdzenie wymaga context manager, obiekt z __enter__ i __exit__ metod. Wywołuje metodę __enter__ i podaje wartość zwróconą przez tę metodę w klauzuli as, a następnie wywoła metodę __exit__ na końcu instrukcji with.

obiekty dziedziczą z io.IOBase pliku, który jest menedżerem kontekst, którego metoda __enter__ zwraca się, a którego __exit__ rozmowy self.close().

Obiekt zwrócony przez urlopen jest (zakładając http lub https URL) jest HTTPResponse, które, jak docs powiedzieć, „może być używany wraz z oświadczeniem with”.

closing funkcja:

Powrót kierownik kontekst, który zamyka rzeczy po zakończeniu bloku. Jest to w zasadzie równoważne:

@contextmanager 
def closing(thing): 
    try: 
     yield thing 
    finally: 
     thing.close() 

To nie zawsze w 100% jasne w docs, które typy są menedżerowie kontekst i jakie typy nie są. Zwłaszcza, że ​​od czasu wydania 3.1 nastąpiła poważna zmiana, aby uczynić wszystko, co może być menedżerem kontekstu, jednym (i, o ile to możliwe, uczynić wszystko, co jest w większości podobne do pliku, rzeczywistą wersją IOBase), ale nadal nie jest to możliwe. 100% ukończone od 3.4.

Zawsze możesz po prostu spróbować i zobaczyć. Jeśli otrzymasz numer AttributeError: __exit__, obiekt nie będzie można użyć jako menedżera kontekstu. Jeśli uważasz, że tak, należy zgłosić błąd sugerujący zmianę. Jeśli nie dostaniesz tego błędu, ale dokumenty nie wspominają, że jest to zgodne z prawem, zgłoś błąd sugerujący aktualizację dokumentów.

+0

dzięki za szczegółowe wyjaśnienie! – nekomimi

7

Nie. urlopen('http://www.python.org') powraca menedżera kontekst też:

with urlopen('http://www.python.org') as page: 

Jest to udokumentowane na urllib.request.urlopen() page:

for FTP plików i adresów URL dane i żądania jawnie obsługiwane przez spuścizny URLopener i FancyURLopener klas, funkcja ta zwraca urllib.response.addinfourl obiekt , który może pracować jako menedżer kontekstu [...].

Podkreśl moje. Dla odpowiedzi HTTP, http.client.HTTPResponse() object jest zwracany, który jest także kierownik kontekst:

Odpowiedź jest iterable obiektu i mogą być używane w instrukcji with.

The Examples section wykorzystuje również obiekt jako kierownik kontekstu:

Jako strona python.org używa kodowania UTF-8, jak określono w jego tag, będziemy korzystać z tego samego do dekodowania bajtów obiekt.

>>> with urllib.request.urlopen('http://www.python.org/') as f: 
...  print(f.read(100).decode('utf-8')) 
... 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtm 

zwracane przez obiekty open()context managers też; implementują specjalne metody: object.__enter__() i object.__exit__().

Urządzenie contextlib.closing() documentation używa przykładu z urlopen(), który jest nieaktualny; w Pythonie 2 poprzednik urllib.request.urlopen() nie wygenerował menedżera kontekstu i trzeba było użyć tego narzędzia, aby automatycznie zamknąć połączenie z menedżerem kontekstu. Zostało to naprawione w przypadku problemów 5418 i 12365, ale ten przykład nie został zaktualizowany. Stworzyłem issue 22755 z prośbą o inny przykład.

+0

Właśnie o tym napisałem :-). Kluczem jest to, że dokumenty mówią, że zwraca obiekt "podobny do pliku". Jeśli nie może być używany jako menedżer kontekstu, nie jest to plik podobny do pliku. – mgilson

+0

, ale dlaczego jest to przykład w dokumentacji Pythona, a następnie? https://docs.python.org/3/library/contextlib.html – nekomimi

+1

@nekomimi: prawdopodobnie holdover z Python 2, gdzie obiekt nie był menedżerem kontekstu. –