Pythonでのクロージャについて(追記あり)

Pythonでリファレンスを見ながら初めてコーディングしたためクロージャの実装で悩んだ。

最初、C#とかと同じ乗りで

def nabeatsu():
    value = 1
    def reader():
        isAho = value % 3 == 0 or str(value).find('3') >= 0
        isDog = value % 5 == 0
        if isAho:
            print 'あほ'
        if isDog:
            print 'わん!'
        if not isAho and not isDog:
            print value
        value = value + 1 #5/10まで間違ったサンプルになっていたのを修正(5/11)
        return reader
runner = nabeatsu()
for value in xrange(1,40,1):
    runner()

としたら、runner()を実行したときに、

valueなんてローカル変数無いよ。

と言われてしまった。

どうも、このままだと外側のコードブロックにある変数を見てくれないらしい。

だから、どうやってやるんだろうといろいろと調べていたら、どうも、下記コードのように配列とすると、外側のコードブロックにある変数にアクセスできるっぽい。

def nabeatsu():
    value = [0]
    value[0] = 1
    def reader():
        isAho = value[0] % 3 == 0 or str(value[0]).find('3') >= 0
        isDog = value[0] % 5 == 0
        if isAho:
            print 'あほ'
        if isDog:
            print 'わん!'
        if not isAho and not isDog:
            print value[0]
        value[0] = value[0] + 1
        return reader
runner = nabeatsu()
for value in xrange(1,40,1):
    runner()

でも、配列にする必要が無いので、ちょっと気持ち悪いと思って更に調べたら定義した関数にプロパティを持つことが可能らしく、結局下記のコードで落ち着いた。

追記:

下記の例でPythonのクロージャとして落ち着いたと書きましたが、runner = nabea2()でファンクションを取得した後、runner.value = 値で外部から変更できてしまうので(つまりvalueのスコープが広すぎる。)、結局、二つ目の配列を使った例が(気持ち悪いけど)クロージャなのかなと。

def nabea2():
    def reader():
        isAho = reader.value % 3 == 0 or str(reader.value).find('3') >= 0
        isDog = reader.value % 5 == 0
        if isAho:
            print 'あほ'
        if isDog:
            print 'わん!'
        if not isAho and not isDog:
            print reader.value
        reader.value += 1
    reader.value= 1
    return reader
runner = nabea2()
for value in xrange(1,40,1):
    runner()

nabea2メソッドの中でreaderメソッドを定義し、その後readerメソッドをreaderメソッドのvalueという名前のプロパティを1に初期化した上で戻り値として返しています。

Share