23
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

意外とややこしい Python のスコープを理解するためのクイズ14問

イントロダクション

Python の変数のスコープ、参照のメカニズムは意外に直感的でない部分があり、初心者が罠にはまる可能性がある。しかし、一旦ルールを覚えればさほど複雑ではない。ここではその理解を助けるための問題を紹介する。

問題ごとに何が出力されるか、もしくはエラーが出力されるかどうかを答えよう。実行環境は Python 3 とする。難しい(というかマニアックな)問題は見出しが赤色になっている。

問題1

x = 1

if True:
    x = 2

print(x)

解答

2

Python では if 文はスコープを形成しない。そのため if 文内の x は外の x と同一の変数となる。

問題2

for i in range(10):
    x = i * 2

print(i, x)

解答

9 18

if と同様に for 文もスコープを形成しないので、ループ変数や内部で定義した変数はその for 文の後でもアクセスすることができる。

問題3

ls = [x * 2 for x in range(10)]

print(x)

解答

(print(x) のとこで)
NameError: name 'x' is not defined

リスト内包表記(list comprehension)が実行されるとき、新たにスコープが作られそこでループ変数が定義される。そのため、外側のスコープに存在する print(x) からは x にアクセスできない。

問題4

funs = []

for i in range(5):
    def f():
        print(i)
    funs.append(f)

for fun in funs:
    fun()

解答

4
4
4
4
4

関数 f の中から外側の変数 i を参照しているが、print(i) が実行される時点での i の値が使われることになる。5つの f が実行されるのはいずれも for 文の後であり、その時点では i4 であるため、すべて 4 を出力することになる。

なお、リスト内包表記についても同じことが起きる。

問題5

x = 0

def f():
    x = 1

f()
print(x)

解答

0

関数 f が形成するブロック1には x = 1 という代入文が存在するため、このブロックのスコープには x という新しい変数が作られることになる。つまり、トップレベルの xf によって変更されない。

問題6

x = 0

def f():
    print(x)
    x = 1

f()

解答

(print(x) のとこで)
UnboundLocalError: local variable 'x' referenced before assignment

1つ前の問題と同様に、関数 f が形成するブロックに x = 1 という代入文が存在するため、ブロックが実行されるにそのスコープに x という変数が作られる。しかし print(x) が実行される時点では x は値を持たない (unbound) であるため、例外が発生する。

問題7

x = 0

def f():
    x += 1

f()
print(x)

解答

(print(x) のとこで)
UnboundLocalError: local variable 'x' referenced before assignment

+= も代入文とみなされるため、1つ前の問題と同様に f が形成するブロックに変数 x が作られる。しかし、x += 1 が実行されるとき x の値は存在しないため、例外が発生する。

外側のスコープの変数の値を変更するには globalnonlocal を使う必要がある。

問題8

x = 0

def f():
    if False:
        x = 1
    print(x)

f()

解答

(print(x) のとこで)
UnboundLocalError: local variable 'x' referenced before assignment

if 文はブロックを形成しないため、これも1つ前の問題と同様に f が形成するブロックに x への代入文が存在し、変数 x が作られる。しかし実際には x に値は代入されないため例外が発生する。

問題9

x = 0

def f():
    del x

f()

解答

(del x のとこで)
UnboundLocalError: local variable 'x' referenced before assignment

実は del 文も代入文と同様に、そのブロックに変数を生成する効果がある。そのため、f が形成するブロックに変数 x が作られるが、x の値が存在しないまま del x が実行されるため例外が発生する。

このように変数を作る効果のある構文は代入文、del 文、for 文、クラス定義、関数定義、import 文、withexceptas がある。

問題10

x = 0

def f():
    def g():
        print(x)
    x = 1
    g()

f()

解答

1

g が実行される時点では、一つ外側のスコープ (f) に x が存在し値が 1 であるため 1 が出力される。

問題11

x = 0

def f():
    x = 1
    del x
    def g():
        print(x)
    g()

f()

解答

(print(x) のとこで)
NameError: free variable 'x' referenced before assignment in enclosing scope

del x が実行されても f のスコープに x は存在しつづける(値がなくなるだけ)。そのため、g 内の xf で定義された x を参照することになる。しかしその x は値を持たないので例外が発生する。

同じ理由で、以下の2つのコードでも同じ結果となる。

x = 0

def f():
    x = 1
    def g():
        print(x)
    del x
    g()

f()
x = 0

def f():
    def g():
        print(x)
    x = 1
    del x
    g()

f()

問題12

x = 1

def f():
    x = 2
    try:
        raise Exception()
    except Exception as x:
        pass

    def g():
        print(x)
    g()

f()

解答

(print(x) のとこで)
NameError: free variable 'x' referenced before assignment in enclosing scope

try 文で例外が発生し except 節が実行されると、その as で指定した変数は del と同様に値が削除される。そのため、g が実行される時点では f 内の x は存在するが値がないため例外が発生する。

なお、except 節が実行されなければ、該当する変数は削除されない。

問題13

x = 1

class C:
    x = 2
    def f():
        print(x)
    f()

解答

1

クラス定義内のコードはスコープに関して特殊であり、そこで定義された変数(クラス変数)はそのスコープからしかアクセスできず、クラス定義内の関数(つまりメソッド)からは直接アクセスできない

問題14

x = 0

def f():
    x = 1
    def g():
        print(eval("x"))
    g()

f()

解答

0

これは eval 関数の(直感に反する)振る舞いに関する問題である。eval 関数で実行したコードからアクセスできる変数は、実は「グローバル変数」と「eval が実行されたブロックで定義された変数」のみであり、その間で定義された変数にはアクセスできない。今のコードでは、eval 関数に渡されたコードからアクセスできる(ビルトイン以外の)変数は、1行目の xg 内で定義された変数(1つもない)である。よってトップレベルの x が参照され、0 が出力される。

なお、exec 関数も同様の振る舞いをする。

参考


  1. ブロックとはスコープ1つに対応するコードのまとまりであり、ブロックごとにスコープが形成される。ブロックを形成するのは関数、モジュール(つまりトップレベル)、クラス定義である。 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
23
Help us understand the problem. What are the problem?