Python 3.0 Hacks

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

この記事を読むのに必要な時間:およそ 1 分

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の導入により,クロージャがいっそう便利になります。

著者プロフィール

稲田直哉(いなだ なおき)

KLab株式会社 Kラボラトリー所属。

数年前,久しぶりにPerlを使おうと思ったらPerlのコードを全く書けなくなっていた。自分の記憶力のなさに絶望し,Pythonを始める。IPythonを知って,Python無しには生活できなくなる。

2008年,Pythonドキュメント翻訳プロジェクトに参加し,不定期に翻訳中。一緒に翻訳してくれる方募集中。

コメント

コメントの記入