[解決!Python]nonlocal文でスコープにない変数をノンローカルであると宣言するには解決!Python

Pythonではあるスコープの外側で定義されている変数の値を変更しようとするときには、nonlocal文でその変数にアクセスできるようにする必要がある。その方法や注意点を紹介する。

» 2023年10月17日 05時00分 公開
[かわさきしんじDeep Insider編集部]
「解決!Python」のインデックス

連載目次

def make_func0():
    greet = 'hello'
    def func(to):
        print(f'{greet} {to}'# make_func0関数のローカル変数greetを参照
    return func

myfunc = make_func0()
myfunc('world'# hello world

def make_func1():
    greet = 'hello'
    def func(to):
        greet = 'goodbye'  # func関数のローカル変数greetを定義
        print(f'{greet} {to}'# func関数のローカル変数greetを参照
    return func

myfunc = make_func1()
myfunc('world'# goodbye world(func関数のローカル変数greetを参照)

def check_var():
    greet = 'hello'
    def func(to):
        greet = 'goodbye'
        print(f'{greet} {to}')
    func('world')
    print(f'greet after calling func: {greet}')

check_var()
# 出力結果:
#goodbye world
#greet after calling func: hello

def make_func2():
    greet = 'hello'
    def func(to):
        nonlocal greet  # greetが外側のスコープで定義されていることを宣言
        greet = 'goodbye'  # ローカル変数ではなく外側のスコープにあるgreetに代入
        print(f'{greet} {to}')
    return func

myfunc = make_func2()
myfunc('world'# goodbye world(make_func2関数のローカル変数greetを参照)

def check_nonlocal():
    greet = 'hello'
    print('before def func:', greet)
    def func(to):
        nonlocal greet
        greet = 'goodbye'
        print(f'{greet} {to}')
    func('world'# 上で定義しているローカル関数funcを呼び出す
    print('after call func:', greet)  # この関数のローカル変数greetの値を確認

check_nonlocal()
# 出力結果:
#before def func: hello
#goodbye world
#after call func: goodbye

def make_func3():
    greet = 'hello'
    def func(to):
        print(greet)  # SyntaxError:func関数のローカル変数greetを宣言前に使用
        nonlocal greet
        greet = 'goodbye'
        print(f'{greet} {to}')
    return func

def make_counter():
    #cnt = 0
    def counter():
        nonlocal cnt  # SyntaxError:変数cntが定義されていない
        cnt += 1
        return cnt
    return counter


nonlocal文

 Pythonのnonlocal文とは次のようなものだ。

  • 関数の内部で定義する関数(ローカル関数)などで、現在のスコープではなく、その外側のスコープにある変数の値を変更したいときに、「この変数はローカル変数ではない(ノンローカルである)」と宣言して現在のスコープから参照できるようにする
  • ノンローカル宣言していない場合、外側のスコープにある変数は参照可能だが、代入はできない
  • 外側のスコープにある変数と同名の変数に現在のスコープで代入すると、ローカル変数が新たに作成される
  • ノンローカル宣言する変数は、nonlocal文よりも前に使うことはできない
  • ノンローカル宣言する変数は、事前に外側のスコープで定義されている必要がある
  • グローバル変数をノンローカル宣言して使えるようにはできない

nonlocal文によるノンローカル宣言

 まずはnonlocal文を使わない例を紹介する。以下のmake_func0関数はローカル関数を作成して、それを戻り値とする。そして、ローカル関数の中ではmake_func0関数のローカル変数を参照している。

def make_func0():
    greet = 'hello'
    def func(to):
        print(f'{greet} {to}'# make_func0関数のローカル変数greetを参照
    return func


 ローカル関数funcは、make_func0関数のローカル変数であるgreetの値を参照している。このように、内側のスコープから外側のスコープにある変数の値を参照することは自由に行える。以下はその呼び出し例だ。

myfunc = make_func0()
myfunc('world'# hello world


 make_func0関数が返してきた関数の内部で、make_func0関数のローカル変数greetの値である'hello'を使って出力が行われているのが分かる。

 make_func0関数のローカル変数であるgreetの値を内部のローカル関数から変更したいとする。以下はその例だ。

def make_func1():
    greet = 'hello'
    def func(to):
        greet = 'goodbye'  # func関数のローカル変数greetを定義
        print(f'{greet} {to}'# func関数のローカル変数greetを参照
    return func

myfunc = make_func1()
myfunc('world'# goodbye world(func関数のローカル変数greetを参照)


 実行結果を見ると、変数greetの値は'hello'ではなく、'goodbye'になっている。しかし、これはfunc関数内でローカル変数greetが新たに定義されるだけで、make_func1関数のローカル変数greetの値は変更されない。このことを確認するために、make_func1関数を基に以下に示すcheck_var関数を定義して呼び出してみよう。

def check_var():
    greet = 'hello'
    def func(to):
        greet = 'goodbye'
        print(f'{greet} {to}')
    func('world')
    print(f'greet after calling func: {greet}')


 check_var関数は内部でローカル関数を定義した後、それを返すのではなく、呼び出してから、ローカル変数greetの値を画面に表示するものだ。実行結果を以下に示す。

check_var()
# 出力結果:
#goodbye world
#greet after calling func: hello


 「func('world')」行でローカル関数が呼び出され、(func関数のローカル変数である)greetの値が'goodbye'になるので画面には「goodbye world」と表示されるが、その後のprint関数呼び出しの結果を見ると分かる通り、外側の関数のスコープにある変数greetの値は'hello'のままで変わっていないことが分かる。

 そのため、外側のスコープにある変数の値を変更したいときにはnonlocal文で対象の変数がノンローカルであることを宣言する。

def make_func2():
    greet = 'hello'
    def func(to):
        nonlocal greet  # greetが外側のスコープで定義されていることを宣言
        greet = 'goodbye'  # ローカル変数ではなく外側のスコープにあるgreetに代入
        print(f'{greet} {to}')
    return func

myfunc = make_func2()
myfunc('world'# goodbye world(make_func2関数のローカル変数greetを参照)


 nonlocal文はある変数が現在のスコープよりも外側で定義されていることを宣言する。上の例ではmake_func2関数で定義されている変数greetを(変更可能な変数として)参照するためにローカル関数funcの中で「nonlocal greet」文を実行している。これにより、「greet = 'goodbye'」行ではmake_func2関数のローカル変数greetに代入が行われるようになる。実行結果を見るだけだと、「goodbye world」と出力されているので、違いがよく分からないので、先ほどのcheck_var関数と同様なcheck_nonlocal関数を作成して実行してみたものが以下だ。

def check_nonlocal():
    greet = 'hello'
    print('before def func:', greet)
    def func(to):
        nonlocal greet
        greet = 'goodbye'
        print(f'{greet} {to}')
    func('world'# 上で定義しているローカル関数funcを呼び出す
    print('after call func:', greet)  # この関数のローカル変数greetの値を確認

check_nonlocal()
# 出力結果:
#before def func: hello
#goodbye world
#after call func: goodbye


 外側のcheck_nonlocal関数では変数greetの値を'hello'にしている。内部で定義しているfunc関数ではnonlocal文でそれを参照できるようにして、その値を'goodbye'に変更している。func関数を定義した後で、それを呼び出すと、変数greetの値が変更されるので、check_nonlocal関数で行われている2つのprint関数呼び出しでは、その値が変わっている。

 なお、nonlocal文に記述する変数は、nonlocal文よりも前に使うことはできず、そうしたコードは関数定義時にSyntaxError例外を発生させる。

def make_func3():
    greet = 'hello'
    def func(to):
        print(greet)  # SyntaxError:func関数のローカル変数greetを宣言前に使用
        nonlocal greet
        greet = 'goodbye'
        print(f'{greet} {to}')
    return func


 また、global文では未定義のグローバル変数を「global undefined_var」のように記述できたが、nonlocal文で記述できるのは既に外側のスコープで定義されている変数のみである。未定義の変数をnonlocal文でノンローカル宣言しようとするとSyntaxError例外が発生する。

def make_counter():
    #cnt = 0
    def counter():
        nonlocal cnt  # SyntaxError:変数cntが定義されていない
        cnt += 1
        return cnt
    return counter


 最後に、nonlocal文ではグローバル変数を指定できないことも覚えておこう。ローカルなスコープでグローバル変数の値を変更したければglobal文を使用すればよい。

「解決!Python」のインデックス

解決!Python

Copyright© Digital Advantage Corp. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。