Pythonのいくつかの高度な文法概念の浅い分析(lambda式閉包装飾器)

4809 ワード

1.匿名関数匿名関数(anonymous function)とは、識別子にバインドされていない関数を指し、functional programming languagesの分野で多く用いられ、典型的な応用例:1)パラメータとして高次関数(higher-order function)に伝達される.pythonのbuilt-in関数filter/map/reduceのように、典型的な高次関数2)は、高次関数の戻り値(ここでは「値」は実際には関数オブジェクトであるが)として名前付き関数(named function)に比べて、関数が1回または有限回しか呼び出されない場合、匿名関数は構文的に軽量である.具体的な構文では、pythonはlambda構文によって関数体が式である匿名関数をサポートします.すなわち、pythonのlambda式は本質的に匿名関数ですが、その関数体は式であり、他の文を含めることはできません.さらに、高度なダイナミック言語では、匿名関数を使用して、閉パッケージやデコレータなどの高度な構文が実装されることが多い.場合によってはlambda式の使用によりpythonプログラムが非常に簡潔に見えます.たとえば、valueに基づいてdict要素をソートするコードの例を次に示します.

>>> foo = {'father' : 65, 'mother' : 62, 'sister' : 38, 'brother' : 29, 'me' : 28}
>>> sorted(foo.iteritems(), key=lambda x: x[1])
[('me', 28), ('brother', 29), ('sister', 38), ('mother', 62), ('father', 65)]

2.クローズド・パッケージ(closure)は本質的にその参照環境(reference environment)を含む関数または関数参照であり、ここでの「参照環境」は通常、関数がアクセスする非局所変数(non-local variables)の参照を格納するテーブルによって維持される.C言語の関数ポインタと比較して、閉パッケージではネストされた関数がその役割ドメイン外のnon-local変数にアクセスできます.これはPython解釈器の変数に対する役割ドメイン検索規則に関係しています(PythonはLEGBの検索規則をサポートしています.深く考えてみると、第4版第17章Scopesの役割ドメインおよび検索規則に関する詳細な説明を参照したり、この文章を見て迅速に理解したりすることができます).通常、実行時のメモリ割り当てモデルが線形スタック上にローカル変数を作成する言語(典型的にはC言語など)では、閉パッケージをサポートすることは困難です.これらの言語の最下位実装では、関数が返されると、関数で定義されたローカル変数は、関数スタックが回収されるにつれて破棄されます.しかし、閉パケットは、下位実装でアクセスするnon−local変数が閉パケットが実行されるまで有効であることが要求され、この閉パケットのライフサイクルが終了するまで、これらのnon−local変数は、使用されないと判断された場合にのみ破棄され、これらの変数を定義する関数に従って破棄されることはできない.したがって、gcメカニズムは、変数が参照されなくなった場合にのみシステムによってメモリ領域が破棄され、回収されることを保証するため、パケット閉鎖を生まれつきサポートする言語は、通常garbage collection方式でメモリを管理する.具体的な構文では、閉パケットは通常、関数ネスト定義を伴う.Pythonを例にとると、簡単な閉パッケージの例は以下の通りです.

#!/bin/env python
#-*- encoding: utf-8 -*-

def startAt_v1(x):
 def incrementBy(y):
  return x + y 
 print 'id(incrementBy)=%s' % (id(incrementBy))
 return incrementBy

def startAt_v2(x):
 return lambda y: x + y 

if '__main__' == __name__:
 c1 = startAt_v1(2)
 print 'type(c1)=%s, c1(3)=%s' % (type(c1), c1(3))
 print 'id(c1)=%s' % (id(c1))
 
 c2 = startAt_v2(2)
 print 'type(c2)=%s, c2(3)=%s' % (type(c2), c2(3))

実行結果は次のとおりです.

id(incrementBy)=139730510519782
type(c1)=, c1(3)=5
id(c1)=139730510519782
type(c2)=, c2(3)=5

上記の例ではstartAt_v 1とstartAt_v 2はいずれも閉パケットを実現し、v 1はネスト定義関数によって実現される.v 2はlambda式/匿名関数によって実現される.v 1を例に閉パケットについて説明する:1)関数startAt_v 1は1つのパラメータを受け入れ、1つの関数オブジェクトを返し、この関数オブジェクトの動作はネスト定義関数incrementByによって実現される.2)関数incrementByにとって、変数xはいわゆるnon-local変数であり(xはその関数が定義した局所変数ではなく、一般的な意味でのグローバル変数ではないため)、incrementByは具体的な関数挙動を実現し、戻る.3)mainエントリのc 1が受信した戻り値は関数オブジェクトであり,id(incrementBy)==id(c 1)から,c 1の「指向」オブジェクトと関数名incrementByの「指向」オブジェクトは実は同じ関数オブジェクトであると断定できる.4)Pythonの閉パッケージのサポートにより、c 1が指すオブジェクトは、通常の関数のオブジェクトと比較して、その関数の役割ドメイン内にないnon-local変数にアクセスすることができ、この変数はincrementByの外層パッケージ関数startAt_v 1のパラメータは、c 1が指す関数オブジェクトに相当する外層パッケージ関数のパラメータに「記憶」機能を有し、外層パッケージ関数を呼び出して閉パッケージを作成すると、異なるパラメータが内層関数によって参照環境として維持される.5)c 1(3)を呼び出すと,入力されたパラメータは環境保全の外層パッケージ関数を参照するパラメータとともに演算され,最終結果が得られる.以上の手順の解析は,閉パケットの作成から実行までの基本原理を説明し,このcaseを理解した後,閉パケットの概念も明らかになるはずである.
3.装飾pythonは装飾(decorator)構文をサポートします.装飾器の概念は初心者にとって難解であり、関数式プログラミングのいくつかの概念(例えば匿名関数、閉パッケージ)に関連しているため、これも本稿で先に匿名関数と閉パッケージを紹介する理由である.
この文章の装飾器の定義を引用します:A decorator is a function that takes a function object as an argument,and returns a function object as a return value.この定義から分かるように、デコレータは本質的に1つの関数にすぎず、閉パッケージの構文を用いて1つの関数(被装飾関数とも呼ばれる)の動作を修正する.すなわちdecoratorは、被装飾関数名(この関数名は実際には1つの関数オブジェクトの参照)を入参として、閉パッケージ内で被装飾関数の動作を修正した後、新しい関数オブジェクトを返します.特に、decoratorは関数として表示する必要はありません.この文書で示した例を参照して、呼び出すことができる任意のオブジェクト、例えばclass形式で表示することができます.関数デコレーションを定義した上で、デコレーション関数を外部から呼び出すと、decoratorの構文糖はPython解釈器によってデコレーション関数を先に実行し、デコレーションから返された新しい関数オブジェクト上で残りの文を実行し続けるように解釈されます.例を分析してみましょう.

#!/bin/env python
#-*- encoding: utf-8 -*-

def wrapper(fn):
 def inner(n, m):
  n += 1
  print 'in inner: fn=%s, n=%s, m=%s' % (fn.__name__, n, m)
  return fn(n, m) + 6 //    return     int  
 return inner

@wrapper
def foo(n, m):
 print 'in foo: n=%s, m=%s' % (n, m)
 return n * m

print foo(2, 3)

上記の例では、fooは@wrapper構文糖によってその装飾器がwrapperであることを宣言し、wrapperではネストされたinner関数(この関数のパラメータリストは被装飾関数fooのパラメータリストと一致しなければならない)を定義し、装飾器wrapperがfooの動作を修正した後、innerを返します(注意:innerの戻り値はintオブジェクトであるため、wrpperは最終的にintオブジェクトを返します).foo(2,3)を呼び出すと、Pythonインタプリタはwrapperを呼び出してfooを書き換え、intオブジェクトを返します.推測に難くありませんが、上記のコードの実行結果は以下の通りです.

in inner: fn=foo, n=3, m=3
in foo: n=3, m=3
foo(2, 3)=15