[Python入門]関数のローカル変数とスコープPython入門(2/3 ページ)

» 2023年10月16日 05時00分 公開
[かわさきしんじDeep Insider編集部]

名前解決

 プログラム実行時に何かの名前(変数名や関数名など)を発見すると、Pythonはそれを解決するために、3つ(またはそれ以上)のスコープを「ローカルスコープ→グローバルスコープ→ビルトインスコープ」の順にたどっていく。つまり、関数の実行時に名前が見つかると、まずは関数のローカルスコープからその名前を検索する。ローカルスコープにその名前があれば、それ(ローカル変数やパラメーターなど)の値を利用する。見つからなければ、グローバルスコープ→ビルトインスコープという順番で、その名前を検索していき、見つかったところでその値を利用する。最終的に名前が見つからなければ、先ほどの画像にあった「NameError」を発生させる。

 以下では、名前を解決する順序についてコード例と共に見ていこう。

ローカルスコープのみ

 ここでmyfunc関数についてもう一度見てみよう。

def myfunc():
    a = 'Python'
    print('a:', a)

a = 'Ruby'
myfunc()
print('a:', a)

ローカル変数aはローカルスコープに存在する

 myfunc関数本体の1行目では、ローカル変数aを定義している。これは既に述べた通り、ローカルスコープを使って定義されている。これに対して、グローバル変数の方の変数aはグローバルスコープ(=モジュールのローカルスコープ)を使って定義されている。

 何げなく書いているが、Pythonの大きな特徴の一つに、「変数に代入をするときには、通常、ローカルスコープが使われる」ことが挙げられる。「ローカル変数」の説明では「関数内での変数への代入はローカル変数の定義や、ローカル変数(やパラメーター)の値の変更と解釈する」と述べたが、これは同じことを別の言い方で表したものだ。

 myfunc関数の中にある「a = 'Python'」という代入文は、この規則に従って、ローカルスコープに変数aを定義しているということだ(関数内でグローバル変数を(その値を変更することを目的に)使用することを明示的に宣言することも可能だ。これについては後述する)。

 関数本体の2行目にあるprint関数呼び出しで「a」という名前が見つかると、まずはローカルスコープを使って名前を解決しようとする。この場合は、その名前がローカルスコープにあるので、この時点で「a」という名前が解決され、ローカル変数aの値が使われて、print関数が呼び出される。

ローカルスコープ→グローバルスコープ

 ここでmyfunc関数の定義を次のように変更してみよう。

def myfunc():
    print('b:', b)

関数定義内に存在しない変数を利用するコード

 今度はローカル変数を定義せず、「print('b:', b)」行では変数bの値を画面に表示するようにしている(変数名を「a」から「b」に変更したのは、説明の都合からだ)。これを呼び出すコードも追加して、以下を実行するとどうなるだろう。

def myfunc():
    print('b:', b)

myfunc()

変数bがどこでも定義されていないのにmyfunc関数を呼び出してみる

 もちろん、どこでも変数bを定義していないので、これは次のようにエラーとなる。

変数bが定義されていないのでエラーとなる 変数bが定義されていないのでエラーとなる

 では、先ほどと同様にmyfunc関数を呼び出す前にモジュールレベルでグローバル変数bを定義してみよう。

b = 'Python'
myfunc()

グローバル変数bを定義

 すると、今度はエラーにならずに文字列'Python'が表示された。

myfunc関数からグローバル変数bの値を参照している myfunc関数からグローバル変数bの値を参照している

 ここではどのような順序で名前が解決されているのだろう。最初はローカルスコープを見るので、次のようになる。

1. ローカルスコープ→ローカル変数bはない→×

 ローカルスコープで名前が見つからなければ、次にグローバルスコープから「b」という名前が探される。

2. グローバルスコープ→グローバル変数bがある→解決終了

 このようにグローバルスコープ(やビルトインスコープ)にある変数でも、それより内側のスコープから、その値を参照する(知ること)はできる。ただし、その値は変更できない。先ほども「関数内での変数への代入はローカル変数の定義や、ローカル変数(やパラメーター)の値の変更と解釈する」と述べたが、関数内部でグローバル変数と同名の変数へ代入しようとするとローカルスコープに新しくローカル変数が作られる。

 よって、通常はあくまでもグローバル変数の値は「参照」できるが「変更」できないということだ。

ローカルスコープ→グローバルスコープ→ビルトインスコープ

 最後にローカルスコープにもグローバルスコープにもない名前の場合が残ったが、これは既に見ている。そう。print関数だ。これは組み込み関数で、その名前はビルトインスコープに存在する。これまであまり気にせず、print関数をはじめとするPythonの組み込み関数を使ってきたが、それらを呼び出すときには、この経路で名前が解決されていたということだ。

global文

 既に述べたように、関数内で変数に代入すると、通常はローカル変数が定義される。関数内ではグローバル変数の値を参照はできるが、変更はできない。だが、グローバル変数の値を関数内で変更したいときもある。そのようなときにはglobal文を使って、それがローカル変数ではなくて、グローバル変数であると「宣言」できる。例えば、myfunc関数を次のようにしてみよう。

def myfunc():
    global a      # 「a」はグローバル変数aであると宣言
    a = 'Python'  # グローバル変数aの値を変更
    print('a:', a)

a = 'Ruby'
myfunc()
print(a)

変数aをグローバル宣言する

 変数をグローバル宣言するには「global」に続けて、その変数の名前を列挙する(複数あるときにはカンマで区切って並べる)。この例では、変数aが関数にローカルな変数ではなく、グローバル変数aであることを宣言している。よって、「a = 'Python'」という代入文はグローバル変数への代入となる。ほんとうかどうか、実行してみよう。

グローバル変数aの値が文字列'Python'に変わっている グローバル変数aの値が文字列'Python'に変わっている

 今度はmyfunc関数呼び出しで「a: Python」と表示された後に、モジュールレベルでのprint関数呼び出しでも「Python」と表示された。これにより、myfunc関数内で使用している変数aがグローバル変数であることが分かった。なお、ローカルスコープ内で、それよりも外側にあるスコープの変数を(値を変更する目的で)使用することを宣言するには、global文に加えてnonlocal文も使える。これについては次回取り上げる。

 関数を呼び出す前後でグローバル変数の値が突然変わるのは、あまり想定されていることではない。本来なら関数を呼び出して、その戻り値をグローバル変数に代入する形で変更するべきであり、そうすればそうした事態は避けられる。通常、関数内ではグローバル変数の値を変更できないのもそうした理由からだ。

 だが、何らかの事情で関数内でグローバル変数の値を変更する必要があることもある。global文は「この関数ではグローバル変数の値を変更するよ」というプログラマーの意図を示すためのものであり、関数のコードを読むことで、他のプログラマーにもそのことが伝わるようになっている。

Copyright© Digital Advantage Corp. All Rights Reserved.

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

注目のテーマ

AI for エンジニアリング
「サプライチェーン攻撃」対策
1P情シスのための脆弱性管理/対策の現実解
OSSのサプライチェーン管理、取るべきアクションとは
Microsoft & Windows最前線2024
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

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

メールマガジン登録

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