Se devi eseguire sempre una pulizia, prendi in considerazione l'utilizzo di un gestore del contesto :
from contextlib import contextmanager
@contextmanager
def normalized(sub_item):
... # normalize
yield resource
... # cleanup
Se la pulizia è necessaria anche con un'eccezione:
@contextmanager
def normalized(sub_item):
... # normalize
try:
yield resource
finally:
... # cleanup
È possibile modificare le altre funzioni in modo che richiedano la risorsa dal contesto come argomento, impedendo che vengano richiamate al di fuori di tale contesto. Poi:
for index, sub_item in enumerate(item.sub_items):
with normalized(sub_item) as resource:
process(sub_item)
if index == 0:
calculator(sub_item, resource)
I.e., usa le dipendenze del flusso di dati per imporre un particolare ordine. Questo non è perfetto in Python perché l'ambito della variabile resource
sarà piuttosto ampio, ma è molto più difficile fare un errore accidentale.
Il test per index == 0
è il modo più pulito per aggiungere un'ulteriore elaborazione per il primo elemento. Esistono alternative come l'utilizzo esplicito di iteratori, ma implicano una certa quantità di duplicazione del codice e sono più difficili da leggere:
sub_items_iter = iter(item.sub_items)
# consume first item from iterator
for sub_item in sub_items_iter:
with normalized(sub_item) as resource:
process(sub_item)
calculator(sub_item, resource)
break
# consume remaining items
for sub_item in sub_items_iter:
with normalized(sub_item) as resource:
process(sub_item)
L'uso del ciclo for per consumare un singolo elemento da un iteratore non è ovvio, ma è ancora più chiaro della variante di de-sugared:
try:
sub_item = next(sub_items_iter)
has_first = True
except StopIteration:
has_first = False
if has_first:
with normalized(sub_item) as resource:
process(sub_item)
calculator(sub_item, resource)