Python における名前空間と、Scopeについて

間違ってたら、指摘お願い致します

## Scopeの話

以下は、cPython の実装での話をしている．

### 名前空間とは？

+ 名前空間とは、変数が所属している領域のこと
+ 普通は、関数によって区切られる
+ 各名前空間では、変数とオブジェクトの対応がディクショナリによって保持されている(そのディクショナリは、locals()で確認できる)
+ Moduleの持つ名前空間をglobalといい、どこからでもglobals()で確認できる
+ globalは、同じModule内ならどこからでもアクセスできる

例えば、

```python3
def a():
	# 関数a内部の名前空間
	l = 0
	s = 'apple'

	def b():
		# 関数b内部の名前空間
		m = 1
		t = 'banana'
		print('in function b:', locals())
		print('in global from b:', globals())

	print('in function a:',locals())
	b()

# globalの名前空間
n = 2
u = 'cake'
a()
print('in global:', globals())

#=>in function a: {'b': <function a.<locals>.b at 0x104c29488>, 's': 'apple', 'l': 0}
#=>in function b: {'t': 'banana', 'm': 1}
#=>in global from b: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'a': <function a at 0x10910eea0>, 'n': 2, 'u': 'cake'}
#=>in global: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'a': <function a at 0x10910eea0>, 'n': 2, 'u': 'cake'}
```
とすると、関数aの内部、関数b内部と、globalに全部で3つの名前空間ができ、

+ 関数a内部では `{l: 0, s: 'apple', b: 関数オブジェクトb}`
+ 関数b内部では `{m: 1, t: 'banana'}`
+ 関数b内部から見たglobalには `{a: 関数オブジェクトa, n: 2, u: 'banana', など}`
+ globalには `{a: 関数オブジェクトa, n: 2, u: 'banana', など}`

というデータを含む


### Scopeとは？

+ Scopeとは、ある名前空間から(直接)アクセスすることができる名前空間の範囲のこと
+ 直接アクセスするとは`a.b`のように`.`を使って対象を指定せず、`b`のようにアクセスする方法のこと
+ Scopeは視野のようなものであり、ある名前空間に立ったとき、そこから見える名前空間はどこまでかということである
+ 基本的に、自分より外側の変数が、スコープに含まれる
+ スコープにはいくつかの種類がある


### Scopeの優先順位

+ 変数を評価するとき、それに参照するオブジェクトを探すために、その変数を持つ名前空間の検索が始まる
+ 何も指定していなければ以下の順に変数が検索される
	1. local(自分の関数内)
	2. nonlocal(localでもglobalでもない中間領域)のうち、よりlocalに近い内側のものから先に検索される
	3. global(自分のModule)
	4. builtins(len(), print(), str()などが入っている)
		+ より正確にはグローバル変数である`__builtins__`の属性を検索している(付録1参照)

`print(spam)`がある名前空間から見た、Scope

![05_03](https://raw.githubusercontent.com/tsg-ut/sig-beginners-2017/master/05/img/05_03.png)

+ `builtins` は `import builtins`で明示的にimportできる
+ builtinsはglobalより優先度が低い

```python3
import builtins
builtins.spam = "hello"

print(spam)
#=>hello

spam = "python"
print(spam)
#=>python
```

### 明示的なScopeの指定
+ 指定できるScopeの種類は2種類あり、それぞれが上で述べた階層に対応する
	+ nonlocal
	+ global

+ localでない変数に代入する時は、新しくlocalの変数を作ってしまわなうようにScopeを指定しなければならない

```python3
spam = 0
def a():
	#globalを変更しようとしても、localに新しく変数ができてしまう
	spam = 1

a()
print(spam)
#=>0

def b():
	#globalを指定すれば、globalの変数に代入することが可能
	global spam
	spam = 1

b()
print(spam)
#=>1
```

+ 変数を使うとき、Scopeを指定することで、そのScope内から変数を探すことができる

```python3
spam = 0
def a():
	spam = 1
	def b():
		# global を指定することで、通常優先度が高いnonlocalではなくglobalの変数を使うことができる
		global spam
		return spam
	return b()

print(a())
#=>0
```

+ pythonチュートリアルより引用
+ 以下のコードが理解できれば名前空間とScopeはわかっていると思われる

```python3
def scope_test():
    def do_local():
        spam = "local spam"

    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam"

    def do_global():
        global spam
        spam = "global spam"

    spam = "test spam"
    do_local()
    print("After local assignment:", spam)
    do_nonlocal()
    print("After nonlocal assignment:", spam)
    do_global()
    print("After global assignment:", spam)

scope_test()
print("In global scope:", spam)

#=>After local assignment: test spam
#=>After nonlocal assignment: nonlocal spam
#=>After global assignment: nonlocal spam
#=>In global scope: global spam
```

### 付録1 builtinsについて

```python3
import builtins #__builtins__を書き換えると組み込み関数が使えなくなり、importすらできなくなるので先にもとを読み込んでおく
import my_module

print(__builtins__)
#=><module 'builtins' (built-in)>

# __builtins__を独自のmoduleに置き換える
__builtins__ = my_module

# __builtins__の属性を確認
builtins.print(builtins.dir(__builtins__))
#=>['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'builtins', 'say_hello']

# globalにsay_hello()関数がないことを確認
builtins.print(builtins.locals())
#=>{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'my_module' from 'F:\\Users\\Yuki\\my_module.py'>, 'my_module': <module 'my_module' from 'F:\\Users\\Yuki\\my_module.py'>, 'builtins': <module 'builtins' (built-in)>}

# 独自に定義した関数が組み込み関数のように使える
say_hello()
#=>hello from my_module
```


```my_module.py:python3
import builtins
def say_hello():
	builtins.print("hello from my_module")
```

このことから、`__builtins__`の属性がScopeに入っていることがわかる