# スコープってなに?

スコープとは
名前空間をわける仕切り
です。

WARNING

重箱の隅をつついているので 軽く読み流していただけると嬉しいです。 スコープの概念を少しでも明瞭にさせて、 次の記事「クロージャと nonlcal」につなげることを目標にしています。

# 1. ざっくり全体像

スコープは、仕切られた範囲ようなものです。 簡単には Python のスコープは、3つに分けることができます。  built-in スコープ   global スコープ   local スコープ  です。

local, global, built-in の各スコープの名称については以下の PEP 227 に準拠します。

古い言語仕様 (2.0 ないしそれ以前のもの) は、名前を解決するために3つの名前空間を厳密に定義していました -- local, global, built-in という名前空間です。 ここで新たに nested スコープを追加することで、まだ束縛されていないローカルな名前解決を、 より外側の関数で囲まれた名前空間の中で、できるようにします。
The old language definition (2.0 and before) defines exactly three namespaces that are used to resolve names -- the local, global, and built-in namespaces. The addition of nested scopes allows resolution of unbound local names in enclosing functions' namespaces.
PEP 227 - Statically Nested Scopes

また、正確には4つで、もう1つ nested スコープというスコープがあるのですが。 これについては、次のページでご紹介させていただく予定ですが、現在加筆修正中です。

# 1.1. スコープの考え方

その範囲の中で定義された変数は、 「外から中」は  見ることもできない  触ることもできない  けど、 「中から外」なら  触ることはできない  けど  見ることならできます 

参照 変更
外から中 できない できない
中から外 できる できない



# 1.2. スコープの場所

local スコープは、関数定義文とクラス定義文の直下です。 global スコープは、スクリプトの直下です。 built-in スコープは、目に見える範囲にはありません。

#
# global スコープ
# 
def f():
    #
    # local スコープ
    #
#
# global スコープ
#
class C:
    #
    # local スコープ
    #
#
# global スコープ
#

C 言語などほかの言語だと関数定義文だけではなく for 文などにもスコープが与えられます。 よくこの違いが他言語から来られた方が戸惑う箇所です。 書籍「Readable Code」にも、Python の注意点として書かれていました。

ところで built-in スコープはどこにあるのでしょうか?  built-in スコープは、目に見えるところにはありません。  このあと、ご紹介いたします。

# 1.4. スコープの順番

狭いスコープの方が強いです。 参照の順番は local スコープ、次に global スコープ、次に built-in スコープを参照しています。 内側から外側に向けて名前を探していきます。 こういうのを難しい言葉で 名前解決 と呼ばれたりします。

変数と定数のことをまとめて名前と呼んだりします。

# 1.5. 確認問題

駆け足で概要を流しました。 いくつか問題を解いて、 まず global スコープと local スコープの挙動を確認していきたいと思います。

問題

実行結果1と等しいものはどれですか?

# コピペで実行できます。
a = 0
def f():
    print(a)

f()  # 実行結果1
問題

実行結果2と等しいものは、どれですか?

# コピペで実行できます。
def g():
    b = 1

g()
print(b)  # 実行結果2
問題

変数 list には、リスト型が代入されています。

list
>>> list
<class 'list'>
>>>

では、ここで問題です。この変数 list に新しく別のリストを代入したらどうなるでしょうか? 実行結果 1 に正しいものを選択してください。

list = [0, 1, 2]
print(list)
# 実行結果3
問題

実行結果4にはなにが表示されるでしょうか?

print(list)  # <class 'list'>

list = [0, 1, 2]
print(list)  # [0, 1, 2]

def f():
    list = 'Hello, world!'
    print(list)

f()  # 実行結果4

# 問題

実行結果 3 にはなにが表示されるでしょうか?

print(list)  # <class 'list'>

list = [0, 1, 2]
print(list)  # [0, 1, 2]

def f():
    list = 'Hello, world!'
    print(list)

f()  # 'Hello, world!'

print(list)  # [0, 1, 2]

del list
print(list)  # 実行結果 3



外から中を触ることができない例も示したかったのですが、 いい例が思いつきませんでした。

ポイント
- 参照変更
外から中できないできない
中から外できるできない
各スコープ built-in, global, local のご紹介

# 2. built-in スコープ

built-in スコープとは Python 全体のスコープです。 min, max, sum と言った組み込み関数や、int, list, str と言った組み込み型は この built-in スコープに組み込まれています。

min, max, int, str なんて空気のように使っているので、 built-in スコープの中にはいっているという意識さえありませんでした。

# 2.1. built-in スコープはどこにあるの?

Python の対話モード >>> を起動したいな、 あるいはスクリプトを実行したいなと思ったとき、 python コマンドを実行します。

このとき起動した Python が built-in スコープに、 「組み込み関数」や「組み込み型」の変数にオブジェクトを 裏でせっせと設定してくれています。

-v オプションを使って Python を起動するとたくさんの何かが裏で import されている様子が見れます(小文字のブイです)。 ただ具体的になにをしているかは、自分もわかりません。

python -v

-v
モジュールが初期化されるたびに、それがどこ(ファイル名やビルトインモジュール) から ロードされたのかを示すメッセージを出力します。

組み込み関数とは import しなくても使える関数です。 例えば min, max があります。 組み込み型とは import しなくても使える型(クラス)です。 例えば int, bool があります。

built-in スコープは、直接見ることはできせん。 built-in スコープは、組み込み関数または組み込み型の名前を保存しています。 built-in スコープは、頑張れば触ることができます。

# 2.2. __builtins__

built-in スコープにはいっているオブジェクトは、 __builtins__ の属性から参照することができます。 また新しい属性を追加することで built-in スコープに、 新しい名前を追加することができます。

__builtins__.min is min          # True
__builtins__.name = '岩倉玲音'   #            <- built-in スコープに直接名前を書き込む
name                             # '岩倉玲音' <- built-in スコープを参照している。

import builtins                  #            <- 標準ライブラリ builtins も合わせてご紹介致します。
assert builtins is __builtins__  # True       <- builtins と __builtins__ は同じものを指している。

このモジュールは Python の全ての「組み込み」識別子に直接アクセスするためのものです。 例えば builtins.open は組み込み関数 open() の完全な名前です。 ドキュメントは 組み込み関数 と 組み込み定数 を参照してください。
29.3. builtins — 組み込みオブジェクト

# 2.3. 実際に触ってみよう

ここで大事なのは Python 全体のスコープだということです。 built-in スコープに名前を追加することで、 スクリプト間で共通の名前を定義できてしまいます。

# Step 1.

まず sample.py に次のようなコードを書きます。

# sample.py
print(name)

# Step 2.

次に対話モード >>> を起動し sample.py を import します。

import sample
>>> import sample
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/sho/sample.py", line 1, in <module>
    print(name)
NameError: name 'name' is not defined
>>>

はじかれます。 なぜなら name は定義されていないからです。

# Step 3.

では変数 name を定義したらどうでしょうか?

name = 'こんにちは、世界!'
import sample
>>> name = 'こんにちは、世界!'
>>> import sample
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/sho/sample.py", line 1, in <module>
    print(name)
NameError: name 'name' is not defined
>>> 

これでもダメです。

# Step 4.

名前 name を定義していないのに import できるのでしょうか? __builtins__ の属性に代入すれば、できます。

__builtins__.name = 'Hello, world!'
import sample
>>> __builtins__.name = 'Hello, world!'
>>> import sample
Hello, world!
>>>

これはなにが起きたかというと sample.py の global スコープに name という変数がなかったので built-in スコープを参照した

# 2.4. でも使ってはいけない黒魔法です。

しかし、PEP 8 の from namespace import * が、 非推奨であると言われていることを考えると。 buitlins の属性にオブジェクトを直接代入する方法は、 絶対にぜーったいにやったらダメな方法だと言えるでしょう。

import - PEP 8
ワイルドカードを使った import (from <module> import *) は避けるべきです。 なぜなら、どの名前が名前空間に存在しているかをわかりにくくし、コードの読み手や多くのツールを混乱させるからです。

グローバル変数をモジュール間で共有するにはどうしたらいいですか? - Python よくある質問
単に設定モジュールをアプリケーションのすべてのモジュールにインポートしてください。

ポイント

built-in スコープが存在する。

# 3. global スコープ

モジュール全体で使う共通の値を定義したい時に使います。 global スコープは、 local スコープから見ることはできませんが、触ることはできるスコープです。

問題

実行結果5に近いものはどれですか?

a = 0  # global スコープ

def f():
    a = 1  # local スコープ

f()
print(a)  # 実行結果5

# 3.1. global 文

global スコープは local スコープから触ることができません。 gloabl 文を使うことで触ることができるようになります。

問題

実行結果6に近いものはどれですか?

a = 0

def f():
    global a
    a = 1

print(a)  # 実行結果6

7.12. global 文 - Python 言語リファレンス
global 文は、現在のコードブロック全体で維持される宣言文です。 global 文は、列挙した識別子をグローバル変数として解釈するよう指定することを意味します。

# 3.2. globals 関数

globals 関数をもちいて global スコープを取得することができます。 ここで驚きなのは globals 関数で取得したオブジェクトは、辞書型 dict だということです。 つまり、スコープも内部的には辞書で実装されていたということです。

# 辞書を更新すると
globals()['a'] += 1

# 変数も更新される
a  # 2

# スコープは辞書型 dict
type(globals())  # <class 'dict'>

globals() Python 標準ライブラリ
現在のグローバルシンボルテーブルを表す辞書を返します。 これは常に現在のモジュール (関数やメソッドの中では、それを呼び出したモジュールではなく、それを定義しているモジュール) の辞書です。

# 3.3. 名前空間は目に見えるスコープ

しかし名前空間が目に見えるスコープとは、どういうことでしょうか? 以下のコードを見てください。 実際に Python の属性、さらには変数までもが(即ちスコープそのものが)、 辞書で実装されていたりします。

# スコープも属性も辞書でしかない

# global スコープ
#   var = 0
#
globals().update({'var': 0})
print(var)

#
# 属性
#   obj.attr = 1
#
class Cls:
    pass

obj = Cls()
obj.__dict__.update({'attr': 1})
print(obj.attr)
>>> print(var)
0
>>> 
>>> print(obj.attr)
1
>>> 

 スコープ (scope) とは、ある 名前空間 が直接アクセスできるような、 Python プログラムのテキスト上の領域です。  "直接アクセス可能" とは、修飾なしに (訳注: spam.egg ではなく単に egg のように) 名前を参照した際に、 その名前空間から名前を見つけようと試みることを意味します。
9.2. Python のスコープと名前空間 - Python チュートリアル

名前空間 (namespace) とは、名前からオブジェクトへの対応付け (mapping) です。 ほとんどの名前空間は、現状では Python の 辞書 として実装されています
9.2. Python のスコープと名前空間 - Python チュートリアル

覚える必要は一切ないのですが、 クラス名の文字列から動的にインスタンスオブジェクトを生成することができます。

class Cls:
    pass

Kls = globals()['Cls']
obj = Kls()

もっと簡単には eval 関数で取ってこれたりしますが...

class Cls:
    pass

Kls = eval('Cls')
obj = Kls()
ポイント

スコープと名前空間と辞書

# 4. local スコープ

# 4.1. 関数のローカルスコープ

何度か触れてきたので省略させていただきます。

# 4.2. クラスのローカルスコープ

関数定義文 def の中にローカルスコープがあるように、 クラス定義文 class の中にもローカルスコープがあります。 そしてこの class 定義文の中で定義された変数は、  クラス変数  と呼ばれます。

class Person:
    species = "Homo sapiens"

# 4.3. ネストするローカルスコープ

 クラスや関数は、それぞれ自分自身のローカルスコープを持ちます。  そしてローカルスコープはネストすることができます。 この次の記事ではネストしたスコープに焦点を当ててお話を進めていきます。

class Person:
    # 
    # Person クラスのローカルスコープ
    #
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender
        #
        # __init__ 関数のローカルスコープ
        #

person = Person('岩倉玲音', 14, 'Female')
person.name
person.age
person.gender
>>> person = Person('岩倉玲音', 14, 'Female')
>>> person.name
'岩倉玲音'
>>> person.age
14
>>> person.gender
'Female'
>>> 

ちなみに関係ありませんが、この local スコープも locals 関数で取得できます。 オススメはできませんが、こんなことができます。

# 対話モード >>> に
# コピペで実行できます。
class Person:
    def __init__(self, name, age, gender):
        self.__dict__.update(locals())
        print(locals())
        del self.self

person = Person('岩倉玲音', 14, 'Female')
person.name
person.age
person.gender
>>> person = Person('岩倉玲音', 14, 'Female')
{
 'self': <__main__.Person object at 0x1015d33c8>,  <- この self を del 文で消しました。
 'name': '岩倉玲音', 
 'age': 14,
 'gender': 'Female'
}
>>> person.name
'岩倉玲音'
>>> person.age
14
>>> person.gender
'Female'
>>> 

locals() - Python 標準ライブラリ
現在のローカルシンボルテーブルを表現する辞書を更新して返します。
Update and return a dictionary representing the current local symbol table.

自由変数は locals 関数によって関数ブロックの中で呼び出された時に返されますが、 クラスブロックの中で呼び出された時は自由変数は返されません。
Free variables are returned by locals() when it is called in function blocks, but not in class blocks.

モジュールの直下で呼び出した時は locals 関数は globals 関数と同じ辞書を返します。
Note that at the module level, locals() and globals() are the same dictionary.

 注釈 この辞書の内容は変更してはいけません; 変更しても、インタプリタが使うローカル変数や自由変数の値には影響しません。  

ポイント

関数のローカルスコープ

クラスのローカルスコープ

スコープに関する経験則

# 5. スコープは狭い方が良い

global スコープ, local スコープの
メリット、デメリット
番号内容globallocal
1書きやすさox
2読みやすさxo
3デッバクxo

一般にスコープは小さい方が良い、とされています。 global スコープは使わずに local スコープを使った方がよいということです。

local スコープを使うとは、関数間では引数を使ってオブジェクトをやり取りする。

global スコープを使うとは、関数間では global 変数を使ってオブジェクトをやり取りすると言えます。

# スコープが広い
#   グローバル変数を使って値をやり取りする。
def add(x, y):
    global a
    a = x + y

def mul(x, y):
    global a
    a = x * y

add(1, 2)
mul(3, a)
print(a)  # 9
# スコープが狭い
#   ローカル変数を使って値をやり取りする。
def add(x, y):
    return x + y

def mul(x, y):
    return x * y

t = add(1, 2)
a = mul(3, t)
print(a)  # 9

これだけ短いコードだと、 メリット、デメリットがまったくハッキリしないのですが、 いくつかご紹介させていただきます。

# 5.1. 書きやすさ

local スコープは都度引数を渡さないといけないので、 global スコープに比べれば、書くのは煩雑になります。

複数の関数に複数の引数を与えて、 実行結果をテストし、実行時間を計測する小さいコードを書きました。 global スコープを使ったものと、local スコープを使ったものです。

読んでいただく必要性は、まったくないのですが local スコープを使って、 引数でひたすら渡し続けるのは、正直辛かったです。

このコードを local スコープのみで使ったものと global スコープで実装したものと local スコープのコードををぱっと見で比較すると、 global スコープの方がスッキリしていて、書きやすそうです。

# 5.2. 読みやすさ - 可読性

個々の関数に区切って見た時に、 local スコープは何が操作対象になっているか明確です。 例えば引数とそこで関数の中で新たに作った 変数(このような変数を難しい言葉で自由変数と言います)だけを意識しておけばよいです。

しかし global 変数が存在すると、 その存在を覚えておかないといけません。 あれ?この変数は、どこで定義されているんだっけ? というところから始めないといけません。

コードを読むこともさることながら、 コードの中から必要なことを探し出すことがとても面倒になります。 global スコープを使うというのは、 コードを global に把握しておかなければならなくなるのです。 その分だけ負担が大きくなります。

# 5.3. デバッグ面

デバッグ面での問題としては、 なぜかと言うとグローバルスコープは、問題が起こったときに特定することが困難だからです。 例えば、あるグローバル変数が原因でバグが発生したとします。 あることがわかったときに、一体、どこの関数、メソッドがこのグローバル変数を書き換えたのか、特定するのが困難です。

なぜなら どの関数がグローバル変数を使っていたのか知らないといけないです。 最初はモジュールの中の全ての関数が捜査対象になります。 実際にグローバル変数を使っている関数は、文字列検索をかければいいだけですが、 わかったところで、今度は、そのグローバル変数を参照し得るすべての関数とメソッドが捜査対象になるからです。

# 5.4. いつ広いスコープを使っていいのか?

では、いつ global スコープを使っても良いのでしょうか? それはおそらく1度定義、すなわち import したら、変更することがない値、 すなわちイミュータブルな値は使ってもいいかなと思います。

もちろん必ずしもグローバルスコープが悪というわけではありません。 もちろん、コードが小さい時は、有効ですが大きくなってくると利点よりも欠点が目立つようになります。

逆に言えば、変更することがない関数や、変更することのない定数なら、グローバル変数にしても問題ないかなと思います。 Django, Flask などのウェブアプリケーションでは設定は、モジュールのグローバル変数として、取り扱われています。

以下の記事は、スコープの広さについて、 経験に基づいて色々な視点から語ってくれていて。とても面白いです。

「変数のスコープは狭いほど良い」と妄信する - 分裂勘違い君劇場 by ふろむだ
変数でもメソッド名でもクラス名でも言えることだが、 単純に「スコープは狭いほどよい」という方針でプログラムすると、 逆に保守性も可読性も悪いプログラムができあがることがけっこうある*1。