閉鎖
Python クロージャを紹介する記事はインターネット上に多数ありますが、この記事では要件問題を解きながらクロージャについて学びます。
この要件は次のようなもので、学習時間を分単位で記録し続ける必要があります。例えば、2分勉強したら2を返し、しばらくして10分勉強したら12を返し、このように学習時間が積み重なっていきます。
この要求に直面して、私たちは通常、時間を記録するためのグローバル変数を作成し、各学習時間を追加するメソッドを使用します。これは通常、次の形式で記述されます。
time = 0 def insert_time(min): time = time + min return time print(insert_time(2)) print(insert_time(10))
真剣に考えるそれ、何か問題はありますか?
実際には、これは Python でエラーを報告します。次のエラーが報告されます:
UnboundLocalError: local variable 'time' referenced before assignment
これは、Python では、関数がグローバル変数と同じ名前を使用して変数の値を変更すると、その変数はローカル変数になり、これにより、定義せずに関数内で参照することになるため、このエラーが報告されます。
本当にグローバル変数を参照して関数内で変更したい場合は、どうすればよいでしょうか?
global キーワードを使用できます。具体的な変更は次のとおりです:
time = 0 def insert_time(min): global time time = time + min return time print(insert_time(2)) print(insert_time(10))
出力結果は次のとおりです:
2 12
ただし、ここではグローバル変数が使用されています。開発中は最善を尽くします。グローバル変数の使用はできるだけ避けてください。さまざまなモジュールやさまざまな関数がグローバル変数に自由にアクセスできるため、グローバル変数は予測できない場合があります。たとえば、プログラマ A がグローバル変数 time の値を変更し、次にプログラマ B も time を変更すると、エラーがあった場合、そのようなエラーを見つけて修正するのは困難です。
グローバル変数により、関数またはモジュール間の汎用性が低下します。さまざまな関数またはモジュールがグローバル変数に依存します。同様に、グローバル変数はコードの可読性を低下させ、読者は呼び出される特定の変数がグローバル変数であることを認識しない可能性があります。
もっと良い方法はありますか?
現時点では、クロージャを使用して問題を解決しています。コードを直接見てみましょう:
time = 0 def study_time(time): def insert_time(min): nonlocal time time = time + min return time return insert_time f = study_time(time) print(f(2)) print(time) print(f(10)) print(time)
出力結果は次のとおりです:
2 0 12 0
最も直接的な表現ここにグローバル変数 time があります。これまでのところ、最終的には変更されていません。nonlocal キーワードはここでもまだ使用されており、関数または他のスコープでの外部 (非グローバル) 変数の使用を示しています。では、上記のコードの具体的な実行プロセスは何でしょうか。以下の図を見てみましょう:
外部関数のローカル スコープ内の変数が内部関数のローカル スコープでアクセスできるこの種の動作クロージャと呼ばれます。より直接的に表現すると、関数がオブジェクトとして返されるときに外部変数が含まれ、クロージャが形成されます。 k
クロージャはグローバル変数の使用を回避し、さらにクロージャを使用すると、関数をその動作対象のデータ (環境) に関連付けることができます。また、クロージャを使用すると、コードをよりエレガントにすることができます。また、次の記事で説明するデコレータもクロージャに基づいて実装されています。
ここで質問になりますが、これは閉鎖だと思いますか、それとも閉鎖だと思いますか?この関数がクロージャであることを確認する方法はありますか?
はい、すべての関数には __closure__ 属性があります。関数がクロージャの場合、セルで構成されるタプル オブジェクトを返します。 cell オブジェクトの cell_contents プロパティは、クロージャに格納される変数です。
これを印刷して体験してみましょう:
time = 0 def study_time(time): def insert_time(min): nonlocal time time = time + min return time return insert_time f = study_time(time) print(f.__closure__) print(f(2)) print(time) print(f.__closure__[0].cell_contents) print(f(10)) print(time) print(f.__closure__[0].cell_contents)
印刷結果は次のとおりです:
(<cell at 0x0000000000410C48: int object at 0x000000001D6AB420>,) 2 0 2 12 0 12
印刷結果から、渡された値は常に次の場所に格納されていることがわかります。クロージャの cell_contents です。これがクロージャの最大の機能であり、親関数の変数を内部定義された関数にバインドできます。クロージャを生成した親関数が解放された場合でも、クロージャはまだ存在します。
クロージャのプロセスは、実際にはクラス (親関数) がインスタンス (クロージャ) を生成するのと似ています。違いは、親関数は呼び出されたときにのみ実行されることです。実行後、その環境は解放されますが、クラスはファイル内で実行されます。クラスはその時に作成され、スコープは通常、プログラムの実行後に解放されます。そのため、再利用する必要があり、クラスとして定義できない一部の関数については、クロージャを使用した方が占有するリソースが少なくなります。クラスを使用するよりも軽量で柔軟です。