Czy można bezpiecznie poddawać się blokowi " z " w Pythonie (i dlaczego)?
Kombinacja koroutinów i pozyskiwania zasobów wydaje się mieć niezamierzone (lub nieintuicyjne) konsekwencje.
Podstawowe pytanie brzmi, czy coś takiego działa, czy nie:
def coroutine():
with open(path, 'r') as fh:
for line in fh:
yield line
/ Align = "left" / (Możesz to przetestować!)
Głębsza troska polega na tym, że with
ma być czymś alternatywnym dla finally
, gdzie zapewniamy, że zasób zostanie wydany na końcu bloku. Coroutines może zawiesić i wznowić wykonywanie z w obrębie with
blok, więc jak rozwiązuje się konflikt?
Na przykład, jeśli otworzysz plik z odczytem/zapisem zarówno wewnątrz, jak i na zewnątrz coroutine, podczas gdy coroutine jeszcze nie powrócił:
def coroutine():
with open('test.txt', 'rw+') as fh:
for line in fh:
yield line
a = coroutine()
assert a.next() # Open the filehandle inside the coroutine first.
with open('test.txt', 'rw+') as fh: # Then open it outside.
for line in fh:
print 'Outside coroutine: %r' % repr(line)
assert a.next() # Can we still use it?
Update
W poprzednim przykładzie chodziło mi o zastrzeżenie obsługi plików z blokadą zapisu, ale ponieważ większość systemów operacyjnych przydziela filehandle dla każdego procesu, nie będzie tam żadnych zastrzeżeń. (Wyrazy uznania dla @ Miles za wskazanie przykładu nie miało zbyt dużego sensu.) Oto moja poprawiona przykład, który pokazuje rzeczywisty stan impasu:
import threading
lock = threading.Lock()
def coroutine():
with lock:
yield 'spam'
yield 'eggs'
generator = coroutine()
assert generator.next()
with lock: # Deadlock!
print 'Outside the coroutine got the lock'
assert generator.next()
5 answers
Nie bardzo rozumiem, o jaki konflikt pytasz, ani problem z przykładem: dobrze jest mieć dwa współistniejące, niezależne Uchwyty do tego samego pliku.
Jednej rzeczy nie wiedziałem, że dowiedziałem się w odpowiedzi na twoje pytanie, że istnieje nowa metoda close () na generatorach:
close()
generuje nowy wyjątekGeneratorExit
wewnątrz generatora, aby zakończyć iterację. Po otrzymaniu tego wyjątku kod generatora musi podnieśćGeneratorExit
lubStopIteration
.
close()
jest wywoływany, gdy generator jest zbierany śmieci, więc oznacza to, że kod generatora dostaje ostatnią szansę na uruchomienie, zanim generator zostanie zniszczony. Ta ostatnia szansa oznacza, żetry...finally
wypowiedzi w generatorach mogą być teraz zagwarantowane, że zadziałają; klauzulafinally
będzie teraz zawsze miała szansę działać. Wydaje się to drobną ciekawostką językową, ale użycie generatorów itry...finally
jest w rzeczywistości konieczne, aby zaimplementowaćwith
oświadczenie opisane przez PEP 343.Http://docs.python.org/whatsnew/2.5.html#pep-342-new-generator-features
Tak, że obsługuje sytuację, w której instrukcja with
jest używana w generatorze, ale daje w środku, ale nigdy nie powraca-metoda menedżera kontekstu __exit__
zostanie wywołana, gdy generator zostanie usunięty.
Edit :
Jeśli chodzi o problem z obsługą plików: czasami zapominam, że istnieją platformy, które nie są podobne do POSIX. :)
[13]}Jeśli chodzi o zamki, myślę, że Rafał Dowgird uderza głową w gwoździe, gdy mówi: "trzeba mieć świadomość, że generator jest jak każdy inny obiekt, który przechowuje zasoby."Nie wydaje mi się, abywith
stwierdzenie było tutaj tak istotne, ponieważ ta funkcja cierpi na te same problemy z impasem:
def coroutine():
lock.acquire()
yield 'spam'
yield 'eggs'
lock.release()
generator = coroutine()
generator.next()
lock.acquire() # whoops!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-26 18:56:11
Nie sądzę, żeby to był prawdziwy konflikt. Musisz tylko mieć świadomość, że generator jest jak każdy inny obiekt, który przechowuje zasoby, więc obowiązkiem twórcy jest upewnienie się, że jest on prawidłowo sfinalizowany (i aby uniknąć konfliktów/impasu z zasobami posiadanymi przez obiekt). Jedynym (drobnym) problemem, jaki tu widzę jest to, że generatory nie implementują protokołu zarządzania kontekstem (przynajmniej w Pythonie 2.5), więc nie można po prostu:
with coroutine() as cr:
doSomething(cr)
Ale zamiast tego mają do:
cr = coroutine()
try:
doSomething(cr)
finally:
cr.close()
Śmieciarz i tak robi close()
, ale to zła praktyka polegać na tym, aby uwolnić zasoby.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-26 10:44:15
Ponieważ yield
może wykonać dowolny kod, byłbym bardzo ostrożny trzymając blokadę nad instrukcją yield. Podobny efekt można uzyskać na wiele innych sposobów, włączając w to wywołanie metody lub funkcji, które mogły zostać nadpisane lub w inny sposób zmodyfikowane.
close()
, albo po prostu zbieraniem śmieci. Zamknięcie generatora powoduje wyrzucenie GeneratorExit
wyjątku wewnątrz generatora i w ten sposób uruchamia się w końcu klauzulach, ze stwierdzeniami, itp. Możesz przechwycić wyjątek, ale musisz rzucić lub zamknąć funkcję (np. rzucić wyjątek StopIteration
), zamiast oddać. To chyba kiepska praktyka polegania na garbage collector, aby zamknąć generator w przypadkach, jak napisałeś, bo to może się zdarzyć później niż chcesz, a jeśli ktoś zadzwoni sys._exit (), wtedy twoje sprzątanie może w ogóle nie nastąpić.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-26 10:44:54
Tak bym się tego spodziewał. Tak, Blok NIE WYDA swoich zasobów, dopóki się nie zakończy, więc w tym sensie zasób uciekł z leksykalnego zagnieżdżania. Jednak nie różni się to niczym od wywołania funkcji, która próbowała użyć tego samego zasobu w bloku with - nic nie pomaga w przypadku, gdy blok ma nie jeszcze zakończony, z bez względu na powód. To nie jest nic konkretnego do generatorów.
Jedna rzecz, która może być warto jednak martwić się o zachowanie, jeśli generator jest nigdy wznowione. Spodziewałbym się, że blok with
będzie działał jak blok finally
i wywoła __exit__
część po zakończeniu, ale tak się nie wydaje.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-26 10:19:42
Dla TLDR, spójrz na to w ten sposób:
with Context():
yield 1
pass # explicitly do nothing *after* yield
# exit context after explicitly doing nothing
Context
kończy się po zakończeniu pass
(tzn. nic), pass
wykonuje po zakończeniu yield
(tzn. wznawia wykonywanie). Tak więc with
Kończy się po kontrola jest wznowiona w yield
.
TLDR: a with
kontekst pozostaje utrzymany, gdy yield
zwalnia kontrolę.
Istnieją właściwie tylko dwie zasady, które są tu istotne:
-
Kiedy
with
wypuści swój zasób?To tak raz i bezpośrednio po jego blok jest wykonywany. Pierwszy oznacza, że nie wypuszcza podczas a
yield
, ponieważ może się to zdarzyć kilka razy. Później oznacza, że wypuszcza poyield
zakończono. -
Kiedy kończy się
yield
?Pomyśl o
yield
jako o wywołaniu odwrotnym: kontrola jest przekazywana do dzwoniącego, a nie do wywołanego. Podobnie,yield
Kończy się, gdy kontrola jest przekazywana z powrotem do niej, tak jak gdy wywołanie powraca Kontrola.
Zauważ, że zarówno with
jak i yield
działają zgodnie z przeznaczeniem! Celem with lock
jest ochrona zasobu i pozostaje on chroniony podczas yield
. Zawsze można wyraźnie zwolnić tę ochronę:
def safe_generator():
while True:
with lock():
# keep lock for critical operation
result = protected_operation()
# release lock before releasing control
yield result
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-19 15:03:36