2008年9月14日日曜日

Python でネストした関数の定義と、呼び出し

1. 関数の中の関数

Python では、関数の中に、関数を定義することができる。

例えば、ITmedia エンタープライズ:2.4への機能強化で広がるPythonの世界 (3/4) には、以下のような例が書かれている。

def declareArgs(*argTypes):
 def checkArguments(func):
  assert func.func_code.co_argcount == len(argTypes)
  def wrapper(*args, **kwargs):
   ...

ネストした関数を呼び出すには、関数の呼び出しで、順に引数を与えれば良い。

7.5 関数定義 に、呼び出し方の例が書かれている。

func = f1(arg)(f2(func))

 

2. 関数のネストと、スコープ

E. 用語集 によると、

ネストされたスコープ (nested scope)
ある文が何らかの定義に囲われているとき、定義の外で使われている変数をその文から参照できる機能です。例えば、ある関数が別の関数の中で定義されている場合、内側の関数は外側の関数中の変数を参照できますネストされたスコープは変数の参照だけができ、変数の代入はできないので注意してください。

「内側の関数から、外側の変数を参照することはできても、変更できない」、ということに注意。

 

3. ネストした関数の定義

最初に、適当に関数 a を定義し、呼び出す

def a():
    print "a"

a()

実行すると、結果は`a' と表示される。

次に、関数 a の中に、関数 f をネストさせる。返り値は、ネストした関数 f とする。

def a():
    def f():
        print "f"
    print "a"
    return f

a()()

結果は、

a
f

上記の a()() による関数の呼出しが、どのように関数が実行されるか、順を追って考えてみる。

まず、080914-007a() による関数呼び出し。

  1. 最初、関数 a の中の、ネストした関数 f が評価される。
  2. 次に、ネストした関数 f より下の文が実行される。
  3. 最後の return により、関数 f が返される。
  4. よって、a() の結果は、関数 f となる。 … (1)

このため、a()() は、 関数 f を呼び出したことと同じになる。 … (2)

関数 a は、print 文がネストした関数の後に書かれている。print 文を、ネストした関数の前に書いても、結果は同じ。ただし、関数 a() 内で、関数 f() を呼出す場合、呼出しよりも前にネストした関数を書く必要がある。そのため、ネストした関数は先頭に書いておく習慣にしておくのが良さげ。

関数 a の呼び出し方を見て、JavaScript の無名関数の呼出し

()()

を思い出した。

これも、関数の呼び出し方としては、同じこと。

 

4. ネストした関数において、ネストした関数を定義する

080914-002ネストした関数で、更にネストした関数を定義することを試しておく。

def a():
    def f():
        def g():
            print "g"
        print "f"
        return g
    print "a"
    return f

a()()()

慣れてないと読みづらい… ^^;

結果は、

a
f
g

 

5. ファーストクラスオブジェクトとは

7.5 関数定義 に、「一級のオブジェクト」という言葉が使われている。

関数は一級の (first-class) オブジェクトです。関数定義内で``def'' 形式を実行すると、戻り値として返したり引き渡したりできるローカルな関数を定義します。ネストされた関数内で自由変数を使うと、def 文の入っている関数のローカル変数にアクセスすることができます。

第一級オブジェクト - Wikipedia によると、

  • 無名のリテラルとして表現可能である。
  • 変数に格納可能である。
  • データ構造に格納可能である。
  • それ自体が独自に存在できる(名前とは独立している)。
  • 他のものとの等値性の比較が可能である。
  • プロシージャ関数のパラメータとして渡すことができる。
  • プロシージャや関数の戻り値として返すことができる。
  • 実行時に構築可能である。
  • ...
  • Python の関数は、ファーストクラスオブジェクトなので、「変数に格納できる」。先ほど定義した、関数 g の後に、次のように書いて実行する。

    g = a()()
    g()

    また、以下の記述も試してみる。

    f = a()
    g = f()
    g()

    実行すると、先ほどと同じ結果が得られる。

    a
    f
    g

    この動作から、Haskell の部分適用を連想した。

    例えば、2つの値を足し合わせる関数 sum を定義して、実行する。

    def sum(x, y):
        return x + y
    
    print sum(1, 2)    #=> 3

    これをネストした関数を使い、書き換える。

    def sum(x):
        def _sum(y):
            return x + y
        return _sum
    
    print sum(1)(2)    #=> 3

    関数を、変数に代入してから使うなら、

    add1 = sum(1)
    print add1(2)   #=> 3
    print add1(3)   #=> 4

     

    6. Callable

    ところで、定義した関数に対して、() を余分に付けると、次のようなエラーがでる。

    例えば、先ほど定義した関数で、

    def sum(x, y):
        return x + y
    
    print sum(1, 2)()

    と書いて、実行すると、

    TypeError: 'int' object is not callable

    callable の意味は、「呼出し可能」。エラーの内容は、「int オブジェクトは、呼び出し可能ではない」ということ。

    「呼び出し可能」は、特殊メソッドの中の一つとして、挙げられている。

    3.3.4 呼び出し可能オブジェクトをエミュレートする によると、

    __call__( self[, args...])

    インスタンスが関数として ``呼ばれた'' 際に呼び出されます; このメソッドが定義されている場合、x(arg1, arg2, ...)x.__call__(arg1, arg2, ...) を短く書いたものになります。

    では、Python において、「呼出し可能」とはどういう意味だろう?

     

    呼び出し可能型

    3.2 標準型の階層 には、Python における組み込みの型が一覧されている。これを見ると、 int, float, String などの型がどのように位置付けられているかがわかる。

    その中の一つに、「呼び出し可能型」が挙げられている。

    呼び出し可能型 (callable type)
    関数呼び出し操作 ( 5.3.4 節、``呼び出し (call)'' 参照) を行うことができる型です:

    「呼び出し可能型」の下に、「ユーザ定義関数」がある。

    ユーザ定義関数 (user-defined function)
    ユーザ定義関数オブジェクトは、関数定義を行うことで生成されます ( 7.5 節、``関数定義'' 参照)。関数は、仮引数 (formal parameter) リストと同じ数の要素が入った引数リストとともに呼び出されます。

    また、同階層に、「クラスインスタンス」がある。

    クラスインスタンス (class instance)
    クラスインスタンスは後で詳しく説明します。クラスインスタンスはクラスが __call__() メソッドを持っている場合にのみ呼び出すことができます; x(arguments) とすると、 x.__call__(arguments) 呼び出しを短く書けます。

    Python では、関数も型として定義されている。