Le contexte
Supposons que j'ai le code Python suivant:
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_function
ici, il suffit de parcourir chacun des éléments de la ns
liste et de les diviser par 3, tout en accumulant les résultats. Le résultat de l'exécution de ce script est simplement:
2.0
Puisque 1 / (2 ^ 3) * (1 + 3 + 12) = 2.
Maintenant, disons que (pour une raison quelconque, peut-être le débogage ou la journalisation), je voudrais afficher un certain type d'informations sur les étapes intermédiaires que example_function
prend. Peut-être que je réécrirais alors cette fonction en quelque chose comme ceci:
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
qui maintenant, lorsqu'il est appelé avec les mêmes arguments que précédemment, génère ce qui suit:
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
Cela réalise exactement ce que je voulais. Cependant, cela va un peu à l'encontre du principe selon lequel une fonction ne devrait faire qu'une seule chose, et maintenant le code de example_function
est légèrement plus long et plus complexe. Pour une fonction aussi simple, ce n'est pas un problème, mais dans mon contexte, j'ai des fonctions assez compliquées qui s'appellent les unes les autres, et les instructions d'impression impliquent souvent des étapes plus compliquées que celles présentées ici, ce qui entraîne une augmentation substantielle de la complexité de mon code (pour un de mes fonctions, il y avait plus de lignes de code liées à la journalisation que de lignes liées à son objectif réel!).
De plus, si je décide plus tard que je ne veux plus imprimer d'instructions dans ma fonction, je devrais parcourir example_function
et supprimer toutes les print
instructions manuellement, ainsi que toutes les variables liées à cette fonctionnalité, un processus à la fois fastidieux et erroné. -enclin.
La situation devient encore pire si je souhaite toujours avoir la possibilité d'imprimer ou de ne pas imprimer pendant l'exécution de la fonction, ce qui m'amène à déclarer deux fonctions extrêmement similaires (une avec les print
instructions, une sans), ce qui est terrible pour la maintenance, ou pour définir quelque chose comme:
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
ce qui se traduit par une fonction gonflée et (espérons-le) inutilement compliquée, même dans le cas simple de notre example_function
.
Question
Existe-t-il un moyen pythonique de "découpler" la fonctionnalité d'impression de la fonctionnalité d'origine du example_function
?
Plus généralement, existe-t-il un moyen pythonique de dissocier les fonctionnalités facultatives de l'objectif principal d'une fonction?
Ce que j'ai essayé jusqu'à présent:
La solution que j'ai trouvée pour le moment utilise des rappels pour le découplage. Par exemple, on peut réécrire example_function
comme ceci:
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
puis en définissant une fonction de rappel qui exécute la fonctionnalité d'impression que je souhaite:
def print_callback(locals):
print(locals['number'])
et appeler example_function
comme ça:
ns = [1, 3, 12]
example_function(ns, 3, callback=print_callback)
qui sort ensuite:
0.5
0.25
0.125
1.5
0.75
0.375
6.0
3.0
1.5
2.0
Cela dissocie avec succès la fonctionnalité d'impression de la fonctionnalité de base de example_function
. Cependant, le principal problème avec cette approche est que la fonction de rappel ne peut être exécutée que sur une partie spécifique de example_function
(dans ce cas, juste après la réduction de moitié du nombre actuel), et toute l'impression doit se produire exactement là. Cela oblige parfois la conception de la fonction de rappel à être assez compliquée (et rend certains comportements impossibles à réaliser).
Par exemple, si l'on souhaite obtenir exactement le même type d'impression que je l'ai fait dans une partie précédente de la question (montrant quel numéro est en cours de traitement, avec ses moitiés correspondantes), le rappel résultant serait:
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)
ce qui donne exactement la même sortie que précédemment:
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
mais est pénible à écrire, à lire et à déboguer.
logging
module pourrait aider ici. Bien que ma question utilise des print
instructions lors de la configuration du contexte, je cherche en fait une solution pour dissocier tout type de fonctionnalité facultative de l'objectif principal d'une fonction. Par exemple, je veux peut-être qu'une fonction trace les choses en cours d'exécution. Dans ce cas, je pense que le logging
module ne serait même pas applicable.
logging
montrent les suggestions d'utilisation ), mais pas comment séparer le code arbitraire.
logging
module python