2012-05-04 18 views
10

Piszę aplikację opartą na MPI (ale MPI nie ma znaczenia w moim pytaniu, wspominam o tym tylko po to, aby odsłonić uzasadnienie) iw niektórych przypadkach, gdy jest mniej elementów pracy niż procesów, muszę stworzyć nowy komunikator wykluczający procesy, które nie mają nic wspólnego. Wreszcie, nowy komunikator musi zostać uwolniony przez procesy, które mają pracę do wykonania (i tylko przez nich).Menedżer kontekstów Pythona: warunkowo wykonujące ciało?

Zgrabny sposobem na to byłoby napisać:

with filter_comm(comm, nworkitems) as newcomm: 
    ... do work with communicator newcomm... 

ciało wykonywane tylko przez procesy, które mają do zrobienia.

Czy istnieje sposób w menedżerze kontekstu, aby uniknąć wykonywania ciała? Rozumiem, że menedżerowie kontekstu zostali słusznie zaprojektowani, aby uniknąć ukrywania przepływów kontrolnych, ale zastanawiam się, czy można to obejść, ponieważ w moim przypadku myślę, że byłoby to uzasadnione ze względu na jasność.

+0

Jeśli wyrzuciłbyś wyjątek w '__init __()' lub '__enter __()', to może on pominąć twoje ciało ... – moooeeeep

+0

@moooeeep: Tak, ale zamiast tego ... wyrzuciłby wyjątek. –

+0

@NiklasB. każde podejście ma swoje zalety i wady! Używanie jawnego warunku 'if' jest prawdopodobnie [bardziej pythonic way] (http://www.python.org/dev/peps/pep-0020/) ... Rzeczywiście. – moooeeeep

Odpowiedz

6

Zdolność do warunkowo pominąć kontekstu ciało Manager został zaproponowany i odrzucone jako udokumentowane w PEP 377.

Oto niektóre metody uzyskania tej funkcjonalności.

Po pierwsze to, co nie działa: nieodparcie z generatora kontekstmana.

@contextlib.contextmanager 
def drivercontext(): 
    driver, ok = driverfactory() 
    try: 
    if ok: 
     yield driver 
    else: 
     print 'skip because driver not ok' 
    finally: 
    driver.quit() 

with drivercontext() as driver: 
    dostuff(driver) 

Nieustąpienie spowoduje RuntimeException podniesionego przez contextmanager. Co najmniej finally jest niezawodnie wykonywane.

Metoda 1: pomiń ciało ręcznie.

@contextlib.contextmanager 
def drivercontext(): 
    driver, ok = driverfactory() 
    try: 
    yield driver, ok 
    finally: 
    driver.quit() 

with drivercontext() as (driver, ok): 
    if ok: 
    dostuff(driver) 
    else: 
    print 'skip because driver not ok' 

To, choć jednoznaczne, neguje znaczną część zwięzłości treści menedżera kontekstu. Logika, która powinna być ukryta w menedżerze kontekstu, rozlewa się na ciało i musi się powtarzać przy każdym wywołaniu.

Metoda 2: nadużycie generatora.

def drivergenerator(): 
    driver, ok = driverfactory() 
    try: 
    if ok: 
     yield driver 
    else: 
     print 'skip because driver not ok' 
    finally: 
    driver.quit() 

for driver in drivergenerator(): 
    dostuff(driver) 

ten zachowuje się bardzo podobnie do menedżera kontekstowego, które może pominąć ciało. Niestety wygląda bardzo podobnie do pętli.

Metoda 3: wykonaj wszystkie czynności ręcznie.

driver, ok = driverfactory() 
try: 
    if ok: 
    dostuff(driver) 
    else: 
    print 'skip because driver not ok' 
finally: 
    driver.quit() 

Bah. Co to jest? Gadatliwość rywalizuje z Javą.

Uogólniając to, można wykonać tylko za pomocą wywołania zwrotnego.

def withdriver(callback): 
    driver, ok = driverfactory() 
    try: 
    if ok: 
     callback(driver) 
    else: 
     print 'skip because driver not ok' 
    finally: 
    driver.quit() 

withdriver(dostuff) 

No cóż. Menedżer kontekstu jest abstrakcyjny w wielu przypadkach.Ale zawsze pojawiają się pęknięcia. To przypomina mi the law of leaky abstractions.


Oto kod demonstrujący te metody i niektóre inne metody.

import contextlib 
import functools 

# ---------------------------------------------------------------------- 
# this code is a simulation of the code not under my control 
# report and ok and fail are variables for use in the simulation 
# they do not exist in the real code 
# report is used to report certain checkpoints 
# ok is used to tell the driver object whether it is ok or not 
# fail is used tell dostuff whether it should fail or not 

class Driver(object): 
    def __init__(self, report, ok): 
    # driver can be ok or not ok 
    # driver must always quit after use 
    # regardless if it is ok or not 
    print 'driver init (ok: %s)' % ok 
    self.report = report 

    def drivestuff(self): 
    # if driver is not ok it is not ok to do stuff with it 
    self.report.drivestuffrun = True 

    def quit(self): 
    # driver must always quit regardless of ok or not 
    print 'driver quit' 
    self.report.driverquit = True 

def driverfactory(report, ok=True): 
    # driver factory always returns a driver 
    # but sometimes driver is not ok 
    # this is indicated by second return value 
    # not ok driver must still be quit 
    return Driver(report, ok), ok 

class DoStuffFail(Exception): 
    pass 

def dostuff(driver, fail=False): 
    # this method does a lot of stuff 
    # dostuff expects an ok driver 
    # it does not check whether the driver is ok 
    driver.drivestuff() 
    # do stuff can also fail independent of driver 
    if fail: 
    print 'dostuff fail' 
    raise DoStuffFail('doing stuff fail') 
    else: 
    print 'dostuff' 

# ---------------------------------------------------------------------- 
class AbstractScenario(object): 
    def __init__(self, driverfactory, dostuff): 
    self.driverfactory = functools.partial(driverfactory, report=self) 
    self.dostuff = dostuff 
    self.driverquit = False 
    self.drivestuffrun = False 

# ---------------------------------------------------------------------- 
class Scenario0(AbstractScenario): 

    def run(self): 
    print '>>>> not check driver ok and not ensure driver quit' 
    driver, ok = self.driverfactory() 
    self.dostuff(driver) 
    driver.quit() 

# ---------------------------------------------------------------------- 
class Scenario1(AbstractScenario): 

    def run(self): 
    print '>>>> check driver ok but not ensure driver quit' 
    driver, ok = self.driverfactory() 
    if ok: 
     self.dostuff(driver) 
    else: 
     print 'skip because driver not ok' 
    driver.quit() 

# ---------------------------------------------------------------------- 
class Scenario2(AbstractScenario): 

    def run(self): 
    print '>>>> check driver ok and ensure driver quit' 
    driver, ok = self.driverfactory() 
    try: 
     if ok: 
     self.dostuff(driver) 
     else: 
     print 'skip because driver not ok' 
    finally: 
     driver.quit() 

# ---------------------------------------------------------------------- 
class Scenario3(AbstractScenario): 

    @contextlib.contextmanager 
    def drivercontext(self, driverfactory): 
    driver, ok = driverfactory() 
    try: 
     if ok: 
     yield driver 
     else: 
     print 'skip because driver not ok' 
    finally: 
     driver.quit() 

    def run(self): 
    print '>>>> skip body by not yielding (does not work)' 
    with self.drivercontext(self.driverfactory) as driver: 
     self.dostuff(driver) 

# ---------------------------------------------------------------------- 
class Scenario4(AbstractScenario): 

    @contextlib.contextmanager 
    def drivercontext(self, driverfactory): 
    driver, ok = driverfactory() 
    try: 
     yield driver, ok 
    finally: 
     driver.quit() 

    def run(self): 
    print '>>>> skip body manually by returning flag with context' 
    with self.drivercontext(self.driverfactory) as (driver, ok): 
     if ok: 
     self.dostuff(driver) 
     else: 
     print 'skip because driver not ok' 

# ---------------------------------------------------------------------- 
class Scenario5(AbstractScenario): 

    def drivergenerator(self, driverfactory): 
    driver, ok = driverfactory() 
    try: 
     if ok: 
     yield driver 
     else: 
     print 'skip because driver not ok' 
    finally: 
     driver.quit() 

    def run(self): 
    print '>>>> abuse generator as context manager' 
    for driver in self.drivergenerator(self.driverfactory): 
     self.dostuff(driver) 

# ---------------------------------------------------------------------- 
def doscenarios(driverfactory, dostuff, drivestuffrunexpected=True): 
    for Scenario in AbstractScenario.__subclasses__(): 
    print '-----------------------------------' 
    scenario = Scenario(driverfactory, dostuff) 
    try: 
     try: 
     scenario.run() 
     except DoStuffFail as e: 
     print 'dostuff fail is ok' 
     if not scenario.driverquit: 
     print '---- fail: driver did not quit' 
     if not scenario.drivestuffrun and drivestuffrunexpected: 
     print '---- fail: drivestuff did not run' 
     if scenario.drivestuffrun and not drivestuffrunexpected: 
     print '---- fail: drivestuff did run' 
    except Exception as e: 
     print '----- fail with exception' 
     print '--------', e 

# ---------------------------------------------------------------------- 
notokdriverfactory = functools.partial(driverfactory, ok=False) 
dostufffail = functools.partial(dostuff, fail=True) 

print '============================================' 
print '==== driver ok and do stuff will not fail ==' 
doscenarios(driverfactory, dostuff) 

print '============================================' 
print '==== do stuff will fail =================' 
doscenarios(driverfactory, dostufffail) 

print '===========================================' 
print '===== driver is not ok ===================' 
doscenarios(notokdriverfactory, dostuff, drivestuffrunexpected=False) 

I dane wyjściowe.

============================================ 
==== driver ok and do stuff will not fail == 
----------------------------------- 
>>>> not check driver ok and not ensure driver quit 
driver init (ok: True) 
dostuff 
driver quit 
----------------------------------- 
>>>> check driver ok but not ensure driver quit 
driver init (ok: True) 
dostuff 
driver quit 
----------------------------------- 
>>>> check driver ok and ensure driver quit 
driver init (ok: True) 
dostuff 
driver quit 
----------------------------------- 
>>>> skip body by not yielding (does not work) 
driver init (ok: True) 
dostuff 
driver quit 
----------------------------------- 
>>>> skip body manually by returning flag with context 
driver init (ok: True) 
dostuff 
driver quit 
----------------------------------- 
>>>> abuse generator as context manager 
driver init (ok: True) 
dostuff 
driver quit 
============================================ 
==== do stuff will fail ================= 
----------------------------------- 
>>>> not check driver ok and not ensure driver quit 
driver init (ok: True) 
dostuff fail 
dostuff fail is ok 
---- fail: driver did not quit 
----------------------------------- 
>>>> check driver ok but not ensure driver quit 
driver init (ok: True) 
dostuff fail 
dostuff fail is ok 
---- fail: driver did not quit 
----------------------------------- 
>>>> check driver ok and ensure driver quit 
driver init (ok: True) 
dostuff fail 
driver quit 
dostuff fail is ok 
----------------------------------- 
>>>> skip body by not yielding (does not work) 
driver init (ok: True) 
dostuff fail 
driver quit 
dostuff fail is ok 
----------------------------------- 
>>>> skip body manually by returning flag with context 
driver init (ok: True) 
dostuff fail 
driver quit 
dostuff fail is ok 
----------------------------------- 
>>>> abuse generator as context manager 
driver init (ok: True) 
dostuff fail 
driver quit 
dostuff fail is ok 
=========================================== 
===== driver is not ok =================== 
----------------------------------- 
>>>> not check driver ok and not ensure driver quit 
driver init (ok: False) 
dostuff 
driver quit 
---- fail: drivestuff did run 
----------------------------------- 
>>>> check driver ok but not ensure driver quit 
driver init (ok: False) 
skip because driver not ok 
driver quit 
----------------------------------- 
>>>> check driver ok and ensure driver quit 
driver init (ok: False) 
skip because driver not ok 
driver quit 
----------------------------------- 
>>>> skip body by not yielding (does not work) 
driver init (ok: False) 
skip because driver not ok 
driver quit 
----- fail with exception 
-------- generator didn't yield 
----------------------------------- 
>>>> skip body manually by returning flag with context 
driver init (ok: False) 
skip because driver not ok 
driver quit 
----------------------------------- 
>>>> abuse generator as context manager 
driver init (ok: False) 
skip because driver not ok 
driver quit 
+0

Uznanie za trud, jaki włożył w tę odpowiedź. Był bardzo pomocny. –

6

Ta funkcja wydaje się być rejected. programiści Pythona często preferują wyraźny wariant:

if need_more_workers(): 
    newcomm = get_new_comm(comm) 
    # ... 

Można również użyć funkcji wyższego rzędu:

def filter_comm(comm, nworkitems, callback): 
    if foo: 
     callback(get_new_comm()) 

# ... 

some_local_var = 5 
def do_work_with_newcomm(newcomm): 
    # we can access the local scope here 

filter_comm(comm, nworkitems, do_work_with_newcomm) 
+0

Dzięki za referencję. Napisałem na listę mailingową Python-Dev. – pch

+1

Rozumiem, że powiedzenie "Nie" jest ważną częścią utrzymania produktu w czystości i prostocie, ale często mam wrażenie, że Guido mówi to zbyt często. IE, bez dopasowywania wzorca, bez literałów zasięgu, a teraz bez warunków dla menedżerów kontekstów. – ArtOfWarfare

0

Jak o czymś takim Zamiast:

@filter_comm(comm, nworkitems) 
def _(newcomm): # Name is unimportant - we'll never reference this by name. 
    ... do work with communicator newcomm... 

można wdrożyć filter_comm dekorator zrobić cokolwiek prace powinno się comm i nworkitems, a następnie w oparciu o te wyniki zdecydować, czy wykonać funkcję to owiniętą wokół lub nie, przekazując w newcomm.

Nie jest tak elegancka jak with, ale myślę, że jest to nieco bardziej czytelne i bliższe temu, co chcesz, niż inne propozycje. Możesz nazwać wewnętrzną funkcję inną niż _, jeśli nie podoba ci się ta nazwa, ale poszedłem z nią, ponieważ jest to normalna nazwa używana w Pythonie, gdy gramatyka wymaga nazwy, której nigdy nie użyjesz.