Python
 Computer >> コンピューター >  >> プログラミング >> Python

Pythonクロージャとは?ネスト関数の仕組みと実用例をわかりやすく解説

Pythonのクロージャ(closure)を正しく理解するには、まず「ネストされた関数(入れ子関数)」という概念を押さえておく必要があります。本記事では、ネスト関数の基本から、クロージャの仕組み、さらに__closure__属性を使った内部構造の確認方法まで、具体的なコード例とともにわかりやすく解説します。

一言でいえば、Pythonのクロージャとは「データをコードとともにカプセル化した関数」のことです。

Pythonにおけるネストされた関数とは

ある関数の中で定義された別の関数を、ネストされた関数(nested function)と呼びます。ネストされた関数は、それを囲む外側のスコープにある変数にアクセスすることができます。

以下のコードを見てみましょう。

def funcOut():
    print("これは外側の関数です。")
    def funcIn():
        print("この関数はfuncOutの中で定義されています。")
        print("このfuncInがネストされた関数です。")
    print("ここでネストされた関数を呼び出せます。")
    funcIn()

print("外側の関数内です。")
print("funcOutを呼び出します。")
funcOut()

出力結果

外側の関数内です。
funcOutを呼び出します。
これは外側の関数です。
ここでネストされた関数を呼び出せます。
この関数はfuncOutの中で定義されています。
このfuncInがネストされた関数です。

この例では、funcInfuncOutの中で定義されたネスト関数です。出力結果から、関数がどのような順序で呼び出されているかを読み取ることができます。

クロージャの基本:return funcIn の意味

もしfuncInの機能をfuncOutの外部でも利用したい場合はどうすればよいのでしょうか。その答えが「return funcIn」です。このように、内側の関数を戻り値として返す仕組みこそが、Pythonにおけるクロージャです。

つまり、クロージャとは「自分が生成された環境(囲むスコープ)を記憶している関数オブジェクト」のことを指します。

次の例では、クロージャを使って異なる初期値を持つインクリメント関数を作成しています。

def closureFunc(start):
    def incrementBy(inc):
        return start + inc
    return incrementBy

closure1 = closureFunc(9)
closure2 = closureFunc(90)

print('closure1(3) = %s' % (closure1(3)))
print('closure2(3) = %s' % (closure2(3)))

出力結果

closure1(3) = 12
closure2(3) = 93

closure1(3)は12を返し、closure2(3)は93を返します。closure1closure2はどちらも同じ関数incrementByを参照していますが、それぞれが記憶しているstartの値(9と90)が異なるため、結果も異なるのです。これがクロージャの重要な特徴であり、「同じ関数でも生成時の環境ごとに異なる状態を保持できる」という点がポイントです。

__closure__属性とセルオブジェクトで内部を確認する

クロージャがどのように環境を記憶しているのか、より詳しく知りたい場合は__closure__属性とセルオブジェクト(cell object)を利用すると便利です。

def closureFunc(start):
    def incrementBy(inc):
        return start + inc
    return incrementBy

a = closureFunc(9)
b = closureFunc(90)

print('type(a)=%s' % (type(a)))
print('a.__closure__=%s' % (a.__closure__))
print('type(a.__closure__[0])=%s' % (type(a.__closure__[0])))
print('a.__closure__[0].cell_contents=%s' % (a.__closure__[0].cell_contents))

print('type(b)=%s' % (type(b)))
print('b.__closure__=%s' % (b.__closure__))
print('type(b.__closure__[0])=%s' % (type(b.__closure__[0])))
print('b.__closure__[0].cell_contents=%s' % (b.__closure__[0].cell_contents))

出力結果

type(a)=<class 'function'>
a.__closure__=<cell at 0x057F8490: int object at 0x68A65770>

type(a.__closure__[0])=<class 'cell'>
a.__closure__[0].cell_contents=9

type(b)=<class 'function'>
b.__closure__=<cell at 0x0580BD50: int object at 0x68A65C80>

type(b.__closure__[0])=<class 'cell'>
b.__closure__[0].cell_contents=90

この出力結果から、各オブジェクトのセルが「生成された時点での値」を保持していることが確認できます。aのセルには9が、bのセルには90が格納されており、これこそがクロージャが環境を記憶する仕組みの正体です。

まとめ

Pythonのクロージャは、ネストされた関数をreturnで返すことで実現される強力な機能です。生成時のスコープの変数を記憶できるため、状態を持つ関数やファクトリ関数などを簡潔に実装できます。__closure__属性を活用すれば、その内部構造も簡単に確認できるので、ぜひ実際にコードを動かしながら理解を深めてみてください。

  1. Pythonのissubset()関数とは?部分集合の判定方法を実例付きで解説

    本記事では、Python標準ライブラリに用意されているissubset()関数の仕組みと使い方について詳しく解説します。 issubset()メソッドは、あるセット(集合)のすべての要素が、引数として渡した別のセットにも含まれている場合にブール値のTrueを返し、1つでも含まれていない要素があればFalseを返します。 下の図では、BはAの部分集合です。もしAとBが同一のセットであれば、両者は互いに部分集合の関係にあるといえます。つまり、両方のセットがまったく同じ要素を持っているということを意味します。 構文 <set 1>.issubset(<set 2>) 戻り値

  2. Pythonのintersection()関数とは?集合の共通要素(積集合)を求める方法

    この記事では、Pythonのセット(集合)に対して実行できるintersection()関数について詳しく解説します。数学における「積集合(インターセクション)」とは、2つの集合から共通する要素を見つけ出すことを指します。構文<セット名>.intersection(<セットa1>, <セットa2>, …)戻り値引数として渡されたすべての集合に共通する要素(積集合)が、新しいセットとして返されます。使用例set_1 = {t,u,t,o,r,i,a,l} set_2 = {p,o,i,n,t} set_3 = {t,u,t} # 2つの集合の積集合 print