Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

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の優先順位

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

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

05_03

  • builtinsimport 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に入っていることがわかる

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
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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
ユーザーは見つかりませんでした