Python 3.0 Hacks

第1回nonlocalでクロージャが便利に

Pythonでプログラムを書いていると、よく関数の中で関数を作ることがあります。

Python はクロージャに対応していて、関数の中で作られた関数は、外側の関数のローカル変数を参照することができます。

クロージャが一番役に立つ場面がデコレータです。次の例では、引数を取るデコレータを作るために関数内関数を2段階作成し、それぞれのローカル変数を一番内側から参照しています(このプログラムは、Python 2.6とPython 3.0の両方で動きます⁠⁠。

リスト1
# coding: utf-8
from __future__ import print_function
import sys

def log(out=sys.stderr):
  def decorator(func):
    name = func.__name__
    def decorated(*args, **kwargs):
      ### 外側の関数の変数 name, out を利用できる
      print("%s start" % name, file=out)
      func(*args, **kwargs)
      print("%s end" % name, file=out)
    return decorated
  return decorator

@log(sys.stdout)
def foo(a, b):
  print(a + b)

foo(1, 2)
図1  リスト1の実行結果
foo start
3
foo end

Pythonのクロージャには、外側の変数への代入ができないという制限がありました。ためしに、リスト1のdecorator関数に、関数を呼び出した回数をカウントする機能をつけてみます。

リスト2 decorator関数の変更部分
def decorator(func):
  name = func.__name__
  called = 0 ### カウンタ
  def decorated(*args, **kwargs):
    called += 1 ### UnboundLocalError: local variable 'called' referenced before assignment
    print("%s starts %d times" % (name, called), file=out)
    func(*args, **kwargs)
    print("%s ends %d times" % (name, called), file=out)
  return decorated
図2 変更後のプログラムの実行結果
$ python2.6 test2.py
Traceback (most recent call last):
  File "test2.py", line 22, in 
    foo(1, 2)
  File "test2.py", line 11, in decorated
    called += 1
UnboundLocalError: local variable 'called' referenced before assignment

Pythonでは、代入=ローカル変数の宣言になるので、上の例では called += 1 しようとしたときにcalledはdecoratedのローカル変数という扱いになり、未初期化変数へ加算代入しようとしたのでエラーになってしまいます。

このような場合、従来では更新したい値を変数に入れる代わりにリストに入れる、といったテクニックを利用する必要がありました。

リスト3 Python 2.6までの方法
called = [0] ### リストの最初の要素がカウンタ
def decorated(*args, **kwargs):
    called[0] += 1 ### 変数ではなくリストの要素を更新

Python 3.0ではnonlocalというキーワードが導入され、外側の変数の更新が可能になりました。nonlocalの使い方はglobalキーワードと同じです。

リスト4 Python 3.0での方法
called = 0
def decorated(*args, **kwargs):
    nonlocal called  ### calledはローカル変数ではない
    called += 1  ### O.K.

この制限は、Python以外の言語でクロージャに慣れ親しんだ人がPythonで同じ事をしようとすると、必ずつまずくポイントでした。 nonlocalの導入により、クロージャがいっそう便利になります。

おすすめ記事

記事・ニュース一覧