環境
次の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_functionここでは、単純にnsリストの各要素を調べ、結果を累積しながら3回に分けます。このスクリプトを実行すると、次のような出力になります。
2.0
1 /(2 ^ 3)*(1 + 3 + 12)= 2なので、
ここで、(何らかの理由で、おそらくデバッグやロギングなどの理由で)が実行している中間ステップに関するある種の情報を表示したいとしましょうexample_function。多分私はこの関数を次のようなものに書き直します:
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
これで、以前と同じ引数を使用して呼び出されると、以下が出力されます。
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
これは私が意図したものを正確に達成します。ただし、これは関数が1つのことだけを行うべきであるという原則に少し反し、現在のコードexample_functionは少し長く、複雑です。そのような単純な関数の場合、これは問題ではありませんが、私のコンテキストでは、相互に呼び出す非常に複雑な関数があり、印刷ステートメントは、ここに示すよりも複雑なステップを含むことが多く、コードの複雑さが大幅に増加します(1つの私の関数には、実際の目的に関連する行よりも、ロギングに関連するコードの行の方が多かった!)
さらに、後で自分の関数に印刷ステートメントが不要になったと判断した場合、example_functionすべてのprintステートメントを手動で実行し、この機能に関連する変数をすべて削除する必要があります。これは、面倒でエラーの多いプロセスです。 -傾向がある。
関数の実行中に常に印刷または印刷しない可能性があり、2つの非常に類似した関数(1つはprintステートメントがある、1つはない)を宣言する場合、状況はさらに悪化します。次のようなものを定義するには:
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
これにより、の単純な場合でも、膨らみ、(うまくいけば)不必要に複雑な機能が発生しますexample_function。
質問
の元の機能から印刷機能を「分離」するためのpythonicな方法はありexample_functionますか?
より一般的には、オプションの機能を関数の主な目的から切り離すためのPythonの方法はありますか?
これまでに試したこと:
私が現時点で見つけた解決策は、デカップリングにコールバックを使用することです。たとえば、次の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
次に、必要な印刷機能を実行するコールバック関数を定義します。
def print_callback(locals):
print(locals['number'])
次のexample_functionように呼び出します:
ns = [1, 3, 12]
example_function(ns, 3, callback=print_callback)
次に出力します:
0.5
0.25
0.125
1.5
0.75
0.375
6.0
3.0
1.5
2.0
これにより、印刷機能がの基本機能から切り離されexample_functionます。ただし、このアプローチの主な問題は、コールバック関数がの特定の部分example_function(この場合、現在の数を半分にした直後)でしか実行できず、すべての印刷がそこで正確に実行される必要があることです。これにより、コールバック関数の設計が非常に複雑になる場合があります(一部の動作が実現不可能になります)。
たとえば、質問の前の部分で行ったのとまったく同じタイプの印刷を実行したい場合(どの番号が処理されているか、および対応する半分になっていることを示しています)、結果のコールバックは次のようになります。
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)
これにより、以前とまったく同じ出力が得られます。
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
しかし、書き込み、読み取り、デバッグが面倒です。
loggingここでモジュールがどのように役立つかわかりません。私の質問ではprint、コンテキストを設定するときにステートメントを使用していますが、実際には、任意のタイプのオプション機能を関数の主な目的から切り離す方法の解決策を探しています。たとえば、実行中に物事をプロットする関数が必要な場合があります。その場合、loggingモジュールは適用できないと思います。
loggingデモンストレーションを使用するための提案として)、任意のコードを分離する方法はできません。
loggingモジュールをチェックしてください