2014-07-11 16 views
11

Niedawno napisałem metodę, która zwróciła sekwencję otwartych plików; Innymi słowy, coś takiego:Czy dobrą praktyką jest poddawanie się z poziomu menedżera kontekstu?

# this is very much simplified, of course 
# the actual code returns file-like objects, not necessarily files 
def _iterdir(self, *path): 
    dr = os.path.join(*path) 
    paths = imap(lambda fn: os.path.join(dr, fn), os.listdir(dr)) 

    return imap(open, paths) 

Składniowo, mam nie spodziewać się zamknąć powstałych obiektów, jeśli zrobię coś takiego:

for f in _iterdir('/', 'usr'): 
    make_unicorns_from(f) 
    # ! f.close() 

W rezultacie postanowiłem owinąć _iterdir w menedżera kontekstowego:

def iterdir(self, *path): 
    it = self._iterdir(*path) 

    while 1: 
     with it.next() as f: 
      yield f 

To wydaje się działać poprawnie.

Co mnie interesuje, to czy jest to dobra praktyka. Czy napotkam jakiekolwiek problemy wynikające z tego wzorca (być może, jeśli zostaną zgłoszone wyjątki)?

+0

Wygląda dobrze, zrobiłem szybką kontrolę z niestandardowym menedżerem kontekstu i '__exit__' został nazwany dobrze nawet, gdy wystąpił wyjątek. –

Odpowiedz

7

Widzę dwa problemy. Jednym z nich jest, że jeśli spróbuje użyć więcej niż jeden plik na raz, wszystko rozbić:

list(iterdir('/', 'usr')) # Doesn't work; they're all closed. 

Drugim jest mało prawdopodobne w CPython, ale jeśli masz cyklu odniesienia, lub jeśli kod jest zawsze uruchamiane na innej implementacji Pythona, problem może się zamanifestować.

Jeśli wyjątek dzieje się w make_unicorns_from(f):

for f in iterdir('/', 'usr'): 
    make_unicorns_from(f) # Uh oh, not enough biomass. 

Plik uzywasz nie będzie zamknięta aż generator jest śmieci zebrane. W tym momencie zostanie wywołana metoda generatora close, rzucając wyjątek GeneratorExit w punkcie ostatniego yield, a wyjątek spowoduje, że menedżer kontekstu zamknie plik.

Z licznikiem odwołań CPython zwykle dzieje się to natychmiast. Jednak w przypadku realizacji bez zliczeń referencyjnych lub w obecności cyklu odniesienia generator może nie zostać zebrany, dopóki nie zostanie uruchomione przejście GC z wykrywaniem cyklu. To może trochę potrwać.


Mój gut mówi, aby pozostawić zamknięcie plików dla dzwoniącego. Można zrobić

for f in _iterdir('/', 'usr'): 
    with f: 
     make_unicorns_from(f) 

a oni wszystko zamknięte natychmiast, nawet bez with w generatorze, a nawet jeśli jest wyjątek. Nie wiem, czy jest to lepszy pomysł, niż generowanie przejęcia przez generator.

+0

Pierwsza sprawa, o której wspomniałeś, wygląda na coś, co byłoby niezwykle trudne do debugowania, gdybyś zapomniał o przyczynie! Myślę, że z tego powodu ten wzorzec jest złym pomysłem. – sapi

+0

Ponadto, i nieco stycznie, dziękuję za wyjaśnienie, że można bezpiecznie zagnieżdżać instrukcje 'with' (tak jak w przypadku zastosowania kontekstu do już otwartego pliku). Nie wiedziałem, że to możliwe. – sapi

5

Cały punkt with polega na unifikacji otwierania i zamykania z wyjątkowymi cyklami bezpieczeństwa i jawności. Twoja abstrakcja usuwa niektóre z nich, ale nie wszystkie.

Oto całkowicie uproszczony przykład:

def with_open(): 
    with open(...) as f: 
     yield f 

Rozważmy wyjątek jego użytkowania:

for _ in with_open(): 
    raise NotImplementedError 

nie zakończy się pętlę, a więc plik zostanie otwarte. Prawdopodobnie na zawsze.

Rozważmy wyjścia w oparciu niekompletny, bez wyjątków, zbyt:

for _ in with_open(): 
    break 

for _ in with_open(): 
    return 

next(with_open()) 

Jedną z opcji jest, aby powrócić do samego menedżera kontekstowe, tak, że można zrobić:

def with_open(): 
    yield partial(open, ...) 

for filecontext in with_open(): 
    with filecontext() as f: 
     break 

Innym, bardziej bezpośrednim rozwiązaniem byłoby zdefiniowanie funkcji jako

from contextlib import closing 

def with_open(self, *path): 
    def inner(): 
     for file in self._iterdir(*path): 
      with file: 
       yield file 

    return closing(inner()) 

i używać go jako

with iterdir() as files: 
    for file in files: 
     ... 

To gwarantuje zamknięcie bez konieczności przesuwania otwieranie plików do rozmówcy.

+0

Dzięki za opinię. Nie wiedziałem, że zerwanie lub powrót nie posprząta menedżera; to poważny problem. – sapi

Powiązane problemy