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