Contesto
Supponiamo che io abbia il seguente codice Python:
def example_function(numbers, n_iters):
sum_all = 0
for number in numbers:
for _ in range(n_iters):
number = halve(number)
sum_all += number
return sum_all
ns = [1, 3, 12]
print(example_function(ns, 3))
example_functionqui sta semplicemente esaminando ciascuno degli elementi nsnell'elenco e dimezzandoli 3 volte, accumulando i risultati. L'output dell'esecuzione di questo script è semplicemente:
2.0
Dal 1 / (2 ^ 3) * (1 + 3 + 12) = 2.
Ora, supponiamo che (per qualsiasi motivo, forse il debug o la registrazione), vorrei visualizzare un tipo di informazioni sui passaggi intermedi che example_functionsta eseguendo. Forse vorrei quindi riscrivere questa funzione in qualcosa del genere:
def example_function(numbers, n_iters):
sum_all = 0
for number in numbers:
print('Processing number', number)
for i_iter in range(n_iters):
number = number/2
print(number)
sum_all += number
print('sum_all:', sum_all)
return sum_all
che ora, quando viene chiamato con gli stessi argomenti di prima, genera quanto segue:
Processing number 1
0.5
0.25
0.125
sum_all: 0.125
Processing number 3
1.5
0.75
0.375
sum_all: 0.5
Processing number 12
6.0
3.0
1.5
sum_all: 2.0
Questo raggiunge esattamente quello che intendevo. Tuttavia, questo va un po 'contro il principio che una funzione dovrebbe fare solo una cosa, e ora il codice per example_functionè leggermente più lungo e più complesso. Per una funzione così semplice questo non è un problema, ma nel mio contesto ho funzioni abbastanza complicate che si chiamano a vicenda e le istruzioni di stampa spesso implicano passaggi più complicati di quelli mostrati qui, con conseguente aumento sostanziale della complessità del mio codice (per uno delle mie funzioni c'erano più righe di codice relative alla registrazione di quante fossero le righe relative al suo scopo reale!).
Inoltre, se in seguito decido che non voglio più alcuna istruzione di stampa nella mia funzione, dovrei passare attraverso example_functioned eliminare tutte le printistruzioni manualmente, insieme a tutte le variabili correlate a questa funzionalità, un processo che è sia noioso che di errore -prone.
La situazione peggiora ancora se vorrei avere sempre la possibilità di stampare o non stampare durante l'esecuzione della funzione, portandomi a dichiarare due funzioni estremamente simili (una con le printistruzioni, una senza), che è terribile da mantenere, oppure per definire qualcosa come:
def example_function(numbers, n_iters, debug_mode=False):
sum_all = 0
for number in numbers:
if debug_mode:
print('Processing number', number)
for i_iter in range(n_iters):
number = number/2
if debug_mode:
print(number)
sum_all += number
if debug_mode:
print('sum_all:', sum_all)
return sum_all
che si traduce in una funzione gonfia e (si spera) inutilmente complicata, anche nel nostro semplice caso example_function.
Domanda
Esiste un modo pitonico per "disaccoppiare" la funzionalità di stampa dalla funzionalità originale di example_function?
Più in generale, esiste un modo pitonico per separare la funzionalità opzionale dallo scopo principale di una funzione?
Quello che ho provato finora:
La soluzione che ho trovato al momento sta usando i callback per il disaccoppiamento. Ad esempio, si può riscrivere in example_functionquesto modo:
def example_function(numbers, n_iters, callback=None):
sum_all = 0
for number in numbers:
for i_iter in range(n_iters):
number = number/2
if callback is not None:
callback(locals())
sum_all += number
return sum_all
e quindi definire una funzione di callback che esegue qualunque funzionalità di stampa che voglio:
def print_callback(locals):
print(locals['number'])
e chiamando example_functioncosì:
ns = [1, 3, 12]
example_function(ns, 3, callback=print_callback)
che quindi genera:
0.5
0.25
0.125
1.5
0.75
0.375
6.0
3.0
1.5
2.0
Questo disaccoppia con successo la funzionalità di stampa dalla funzionalità di base di example_function. Tuttavia, il problema principale con questo approccio è che la funzione di callback può essere eseguita solo in una parte specifica del example_function(in questo caso subito dopo aver dimezzato il numero corrente), e tutta la stampa deve avvenire esattamente lì. Questo a volte costringe il design della funzione di callback ad essere abbastanza complicato (e rende impossibile ottenere alcuni comportamenti).
Ad esempio, se si desidera ottenere esattamente lo stesso tipo di stampa che avevo fatto in una parte precedente della domanda (mostrando quale numero viene elaborato, insieme alle sue corrispondenti metà) il callback risultante sarebbe:
def complicated_callback(locals):
i_iter = locals['i_iter']
number = locals['number']
if i_iter == 0:
print('Processing number', number*2)
print(number)
if i_iter == locals['n_iters']-1:
print('sum_all:', locals['sum_all']+number)
che si traduce esattamente nello stesso output di prima:
Processing number 1.0
0.5
0.25
0.125
sum_all: 0.125
Processing number 3.0
1.5
0.75
0.375
sum_all: 0.5
Processing number 12.0
6.0
3.0
1.5
sum_all: 2.0
ma è una seccatura scrivere, leggere e debug.
loggingmodulo possa aiutare qui. Sebbene la mia domanda utilizzi le printdichiarazioni durante l'impostazione del contesto, in realtà sto cercando una soluzione su come disaccoppiare qualsiasi tipo di funzionalità opzionale dallo scopo principale di una funzione. Ad esempio, forse voglio una funzione per tracciare le cose mentre corre. In tal caso, credo che il loggingmodulo non sarebbe nemmeno applicabile.
loggingdimostrano i suggerimenti da utilizzare ), ma non come separare il codice arbitrario.
loggingmodulo python