Programmierkurs
für Naturwissenschaftler/innen

スコープ

スコープとは

一言で言うと変数(等)の有効範囲のことである。

簡単な復習

復習がてら、変数について見てみよう。

これまでは、ある変数は、初めて使った (定義した) ところから最後まで使うことができた。

# test

a = 10

print(a)

この場合、3行目以降で変数 a を使うことができる。

関数の引数

関数 では、引数はその関数内でのみ使用できると学んだ。 これについて、きちんと確認してみよう。

例えば、次の関数を使ってみよう。 このとき、 num_from はこの関数内でのみ使用できる変数である。

def calc_sum3(num_from):
    summation = 3 * num_from + 3
    return summation

従って、次のプログラムはエラーとなる:

def calc_sum3(num_from):
    summation = 3 * num_from + 3
    return summation


print(num_from)    # エラー

もしかすると、「 calc_sum3() を実行していないせいで num_from がまだ作られていないのでは?」と思うかもしれない。 しかし、次のプログラムもやはりエラーとなる。

def calc_sum3(num_from):
    summation = 3 * num_from + 3
    return summation


print(calc_sum3(1))
print(num_from)    # エラー

つまり、引数はその関数内でしか使えない。

関数内で定義された変数

もう一度、次の関数を見てみよう。

def calc_sum3(num_from):
    summation = 3 * num_from + 3
    return summation

このとき summation もこの関数内でのみ使える変数である。 従って、次のプログラムはエラーとなる

def calc_sum3(num_from):
    summation = 3 * num_from + 3
    return summation


print(summation)    # エラー

では、次の場合はどうなるであろうか?

次のプログラムを実行すると何が表示されるか予想せよ。 その後、実際に入力・実行してみて確認せよ。 なぜそのような結果になったか、再度検討してみよ。

summation = 10

def calc_sum3(num_from):
    summation = 3 * num_from + 3
    print("summation in calc_sum3():", summation)
    return summation


print("summation:", summation)
print("result of calc_sum3():", calc_sum3(1))
print("summation:", summation)

実行結果

このプログラムでは 3 つの変数が使われている。 一つは num_from 、もう一つは関数の summation 、 そして、関数の summation である。 最後の二つはたまたま同じ summation という名前を持っているが、実際には別の変数である。

もう少し詳しく説明すると、まず1行目で summation が作られる。 その後 calc_sum3() が呼ばれる (最後から二行目) と、新しい summation が作られる。 このとき1行目で定義された summation は見えなくなるが、消えてはいない。 calc_sum3() の実行が終わると、 calc_sum3() で作られた方の summation は消えてしまい、その結果、もともとの summation が再び見えるようになる。 そのため最後の print(summation) では、一行目で作られた summation の値である 10 が表示される。

(復習) 関数の引数や、関数内で作られた変数は、その関数内でのみ使用可能である

スコープ

上記のような変数の有効範囲をスコープと呼ぶ。 関数の場合は、変数の有効範囲はその関数内に限られており、これをローカルスコープと呼ぶ。 また、ローカルスコープ内の変数をローカル変数と呼ぶ。

上の練習問題において一行目の summation は原則としてプログラム全体で有効であるので、この有効範囲を「グローバルスコープ」と言い、 このような変数を「グローバル変数」と呼ぶ。

ただし、 グローバル変数は原則使用すべきではない。 短いプログラムの場合は問題が発生しにくいが、プログラムの規模が大きくなってくると様々な弊害が出てくる。 なにか問題が判明したとき、それがグローバル変数に起因していると、特にその変数がプログラムのあちこちで使われていると、 問題の発生箇所を特定するのが難しい。 例えば、ある変数に想定外の値が入っていた場合、それがローカル変数であった場合は、その関数内の処理が間違っているか、その関数の呼び出し (引数) の問題である可能性が高い。 しかし、その変数がグローバル変数であった場合は、原因箇所の絞り込みが難しくなりがちである。

従って、変数のスコープはできるだけ狭くするべきである。

変数のスコープをできる限り狭めることで、問題発生時の原因追求がやりやすくなる (ことが多い)

そしてこのことから、次のことが言える。

関数とのやりとりは、引数と返り値でのみ行うべきである

言い方を変えると、関数は

  • 引数のみを通じて外部の情報 (外部からの指示) を受けとり、

  • 返り値でのみ外部へ情報を渡し、

  • それ以外、外部には一切影響を与えない

ように書くべきである。

もう少し具体的に言いかえるなら、

  • 関数では、引数とその関数内で定義した変数のみを使用し、

  • 関数外にある変数を関数内で直接読んだり変更したりしないようにすべきである

と言える。

繰り返しになるが、良いプログラムというのは、間違いが起こりにくい、あるいは万一起こっても発見がしやすいようなものである。 最初のうちは実践するのは難しいであろうが、このことは頭の片隅には置いておいて欲しい。

補足

他の言語を知っていると、 iffor などではどうなるか気になるかもしれない。 Python ではこれらはローカルスコープに ならない

例えば、以下を実行してみると確認できる。

x = 10

if x > 5:
    x = 100
else:
    x = 0

print(x)

このとき、このプログラム内の全ての x は同一の変数である。

補足

ここでは少しややこしい話をする。 最初は読み飛ばして構わない。 特に上記内容の理解に自信が無い場合は、却って混乱してしまう可能性があるので、読まないことをおすすめする。

重要なことは、原則として グローバル変数を使うべきではない ということだけである。

次のプログラムではなにが起こるであろうか。

# ! 良くない書き方 !

summation = 10

def test():
    print(summation)


test()

すでに説明したように「関数内で『定義された』変数」はその関数内でしか使えない。 ただし、関数内で定義せずに使った場合は (もし存在すれば) グローバル変数が使われる。 この場合、関数の中からグローバル変数を「読む」ことはできるので、このプログラムを実行すると (エラーにならずに) 10 と表示される。

しかしこのプログラムでは summation がどこで定義・変更されているか確認するのが大変難しい。 そもそも意図してこのように書いたのか、間違ってこう書いたのか判断しにくい。 したがってこのような書き方は避けるべきである。 繰り返しになるが、関数は引数と返り値でのみ外部とやりとりすべきである。

このような問題を避ける一つの方法は、 main() 関数を作成することである。 (名前は何でも良いが、普通は main という名前を使う。)

例えば次のようなプログラムがあったとしよう。

def calc_sum3(num_from):
    summation = 3 * num_from + 3
    return summation

n = 10
print(calc_sum(n))

これは、次のように書くとより良い。

def calc_sum3(num_from):
    summation = 3 * num_from + 3
    return summation

def main():
    n = 10
    print(calc_sum(n))

main()

もともとのプログラムでは n はグローバル変数であったが、書き直したバージョンではグローバル変数は使われていない。 プログラム本体は最後の main() の一行のみであり、それ以外は (ここでは) 全て関数定義である。

このくらい短いプログラムであればわざわざこのように書くメリットはないが、プログラムの規模が大きくなってくると変数管理はどんどん難しくなっていくので、変数のスコープを狭めて管理しやすくする事は非常に重要である。

なお、関数内からグローバル変数を変更することはできない (そのような方法もあるが、使うべきではない)。