Kontext
Angenommen, ich habe den folgenden Python-Code:
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_functionHier gehen Sie einfach jedes der Elemente in der nsListe durch und halbieren sie dreimal, während Sie die Ergebnisse akkumulieren. Die Ausgabe dieses Skripts lautet einfach:
2.0
Da 1 / (2 ^ 3) * (1 + 3 + 12) = 2.
Nehmen wir nun an, ich möchte (aus irgendeinem Grund, vielleicht zum Debuggen oder Protokollieren) Informationen zu den Zwischenschritten anzeigen, die example_functiondas Unternehmen ausführt. Vielleicht würde ich diese Funktion dann in so etwas umschreiben:
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
Wenn jetzt mit denselben Argumenten wie zuvor aufgerufen wird, wird Folgendes ausgegeben:
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
Damit wird genau das erreicht, was ich beabsichtigt habe. Dies widerspricht jedoch ein wenig dem Prinzip, dass eine Funktion nur eines tun sollte, und jetzt ist der Code für example_functionetwas länger und komplexer. Für eine so einfache Funktion ist dies kein Problem, aber in meinem Kontext habe ich ziemlich komplizierte Funktionen, die sich gegenseitig aufrufen, und die Druckanweisungen umfassen oft kompliziertere Schritte als hier gezeigt, was zu einer erheblichen Zunahme der Komplexität meines Codes führt (zum einen) Von meinen Funktionen gab es mehr Codezeilen im Zusammenhang mit der Protokollierung als Zeilen im Zusammenhang mit dem eigentlichen Zweck!).
Wenn ich später beschließe, dass ich keine Druckanweisungen mehr in meiner Funktion haben möchte, müsste ich außerdem example_functionalle printAnweisungen zusammen mit allen Variablen, die mit dieser Funktionalität zusammenhängen, manuell durchgehen und löschen. Dies ist ein Prozess, der sowohl langwierig als auch fehlerhaft ist -anfällig.
Die Situation wird noch schlimmer, wenn ich während der Funktionsausführung immer die Möglichkeit haben möchte, zu drucken oder nicht zu drucken, was mich dazu veranlasst, entweder zwei extrem ähnliche Funktionen zu deklarieren (eine mit den printAnweisungen, eine ohne), was für die Wartung schrecklich ist, oder etwas definieren wie:
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
was zu einer aufgeblähten und (hoffentlich) unnötig komplizierten Funktion führt, selbst im einfachen Fall unserer example_function.
Frage
Gibt es eine pythonische Möglichkeit, die Druckfunktionalität von der ursprünglichen Funktionalität der zu "entkoppeln"? example_function ?
Gibt es allgemein eine pythonische Möglichkeit, optionale Funktionen vom Hauptzweck einer Funktion zu entkoppeln?
Was ich bisher versucht habe:
Die Lösung, die ich im Moment gefunden habe, besteht darin, Rückrufe für die Entkopplung zu verwenden. Zum Beispiel kann man Folgendes umschreiben example_function:
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
und dann Definieren einer Rückruffunktion, die die gewünschte Druckfunktion ausführt:
def print_callback(locals):
print(locals['number'])
und so anrufen example_function:
ns = [1, 3, 12]
example_function(ns, 3, callback=print_callback)
welches dann ausgibt:
0.5
0.25
0.125
1.5
0.75
0.375
6.0
3.0
1.5
2.0
Dadurch wird die Druckfunktionalität erfolgreich von der Basisfunktionalität von entkoppelt example_function. Das Hauptproblem bei diesem Ansatz besteht jedoch darin, dass die Rückruffunktion nur an einem bestimmten Teil der ausgeführt werden kannexample_function (in diesem Fall direkt nach der Halbierung der aktuellen Nummer) ausgeführt werden kann und der gesamte Druck genau dort stattfinden muss. Dies erzwingt manchmal, dass das Design der Rückruffunktion ziemlich kompliziert ist (und macht es unmöglich, einige Verhaltensweisen zu erreichen).
Wenn man zum Beispiel genau die gleiche Art des Druckens wie in einem vorherigen Teil der Frage erzielen möchte (wobei angegeben wird, welche Nummer verarbeitet wird, zusammen mit den entsprechenden Halbierungen), wäre der resultierende Rückruf:
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)
was zu genau der gleichen Ausgabe wie zuvor führt:
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
Aber es ist ein Schmerz zu schreiben, zu lesen und zu debuggen.
loggingModul hier helfen würde. Obwohl meine Frage printbeim Einrichten des Kontexts Anweisungen verwendet , suche ich tatsächlich nach einer Lösung, wie jede Art von optionaler Funktionalität vom Hauptzweck einer Funktion entkoppelt werden kann. Zum Beispiel möchte ich vielleicht, dass eine Funktion Dinge zeichnet, während sie ausgeführt wird. In diesem Fall glaube ich, dass das loggingModul nicht einmal anwendbar wäre.
loggingzeigen), aber nicht, wie beliebiger Code getrennt werden kann.
logging