間違ってたら、指摘お願い致します
Scopeの話
以下は、cPython の実装での話をしている.
名前空間とは?
- 名前空間とは、変数が所属している領域のこと
- 普通は、関数によって区切られる
- 各名前空間では、変数とオブジェクトの対応がディクショナリによって保持されている(そのディクショナリは、locals()で確認できる)
- Moduleの持つ名前空間をglobalといい、どこからでもglobals()で確認できる
- globalは、同じModule内ならどこからでもアクセスできる
例えば、
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の優先順位
- 変数を評価するとき、それに参照するオブジェクトを探すために、その変数を持つ名前空間の検索が始まる
- 何も指定していなければ以下の順に変数が検索される
- local(自分の関数内)
- nonlocal(localでもglobalでもない中間領域)のうち、よりlocalに近い内側のものから先に検索される
- global(自分のModule)
- builtins(len(), print(), str()などが入っている)
- より正確にはグローバル変数である
__builtins__
の属性を検索している(付録1参照)
- より正確にはグローバル変数である
print(spam)
がある名前空間から見た、Scope
-
builtins
はimport builtins
で明示的にimportできる - builtinsはglobalより優先度が低い
import builtins
builtins.spam = "hello"
print(spam)
#=>hello
spam = "python"
print(spam)
#=>python
明示的なScopeの指定
-
指定できるScopeの種類は2種類あり、それぞれが上で述べた階層に対応する
- nonlocal
- global
-
localでない変数に代入する時は、新しくlocalの変数を作ってしまわなうようにScopeを指定しなければならない
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内から変数を探すことができる
spam = 0
def a():
spam = 1
def b():
# global を指定することで、通常優先度が高いnonlocalではなくglobalの変数を使うことができる
global spam
return spam
return b()
print(a())
#=>0
- pythonチュートリアルより引用
- 以下のコードが理解できれば名前空間とScopeはわかっていると思われる
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について
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
python3
import builtins
def say_hello():
builtins.print("hello from my_module")
このことから、__builtins__
の属性がScopeに入っていることがわかる