閉鎖

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 キーワードはここでもまだ使用されており、関数または他のスコープでの外部 (非グローバル) 変数の使用を示しています。では、上記のコードの具体的な実行プロセスは何でしょうか。以下の図を見てみましょう:

a4e16c80e1df62cb932f66f1b8b91c9.png

外部関数のローカル スコープ内の変数が内部関数のローカル スコープでアクセスできるこの種の動作クロージャと呼ばれます。より直接的に表現すると、関数がオブジェクトとして返されるときに外部変数が含まれ、クロージャが形成されます。 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 です。これがクロージャの最大の機能であり、親関数の変数を内部定義された関数にバインドできます。クロージャを生成した親関数が解放された場合でも、クロージャはまだ存在します。

クロージャのプロセスは、実際にはクラス (親関数) がインスタンス (クロージャ) を生成するのと似ています。違いは、親関数は呼び出されたときにのみ実行されることです。実行後、その環境は解放されますが、クラスはファイル内で実行されます。クラスはその時に作成され、スコープは通常、プログラムの実行後に解放されます。そのため、再利用する必要があり、クラスとして定義できない一部の関数については、クロージャを使用した方が占有するリソースが少なくなります。クラスを使用するよりも軽量で柔軟です。

学び続ける
  • おすすめコース
  • コースウェアのダウンロード
現時点ではコースウェアはダウンロードできません。現在スタッフが整理中です。今後もこのコースにもっと注目してください〜