IT/언어

[python] python의 변수 사용 범위 정리 (2)

개발자 두더지 2020. 8. 21. 22:25
728x90

지난번에 이것 저것 찾아가면서 나름대로 python 변수와 스코프에 대해 정리해봤는데, 여전히 머릿속에 의문이 가시질 않아서 다른 포스팅을 찾아서 재정리한다.

 

1. 이름공간(네임스페이스)이란?

- 이름공간이란 변수가 소속되어 있는 영역이다.

- 보통 변수에 따라 구분된다.

- 이름공간에서는 변수와 객체의 상대관계는 사전으로 보존되어 있다. (여기서 '사전'은 locals()함수로 확인 가능하다.)

- 모듈을 가진 이름공간을 global이라고 말하고, 어디에서든 globals()로 확인 가능하다.

- global은 같은 모듈내라면 어디에서든 접근할 수 있다.

예를 들어,

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 0x7fa8e63dc488>, 's': 'apple', 'l': 0}
in function b: {'t': 'banana', 'm': 1}
in global from b: {'__name__': '__main__', '__doc__': 'Automatically created module for IPython interactive environment', '__package__': None, '__loader__': None, '__spec__': None, '__builtin__': <module 'builtins' (built-in)>, '__builtins__': <module 'builtins' (built-in)>, '_ih': ['', "name = input('Who are you?')\nprint('Welcome', name)", 'astr = "123"\n\ntry:\n  print("Hello")\n  islnt = int(astr)\n  print("World")\nexcept:\n  islnt = "Integer로 변환할 수 없습니다."\n\nprint(\'Done\', islnt)', "def thing():\n  print('Hello')\n  print('Fun')\n\nthing()\nprint('Zip')\nthing", "def thing():\n  print('Hello')\n  print('Fun')\n\nthing()\nprint('Zip')\nthing()", "while True:\n    line = input('> ')\n    if line[0] == '#' :\n        continue\n    if line == 'done' :\n        break\n    print(line)\nprint('Done!')", 'a = 2 \n\ndef test():\n  a = 3\n  print("inside test:", a)\n\ntest()', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest\nprint("after test:, a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest\nprint("after test":, a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest\nprint("after test", a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest()\nprint("after test", a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest()\nprint("after test", a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', 'a = 2 \n\ndef test()\n  global a\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', 'a = 2 \n\ndef test():\n  global a\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', "def a():\n  # 함수 a 내부의 이름 공간\n  l = 0\n  s = 'apple'\n\n  def b():\n    # 함수 b내부의 이름 공간\n    m = 1\n    t = 'banana'\n    print('in function b:', locals())\n    print('in global from b:', globals())\n\n  print('in function a:',locals())\n  b()\n\n# global의 이름공간\nn = 2\nu = 'cake'\na()\nprint('in global:',globals())"], '_oh': {3: <function thing at 0x7fa8ee0cbc80>}, '_dh': ['/content'], '_sh': <module 'IPython.core.shadowns' from '/usr/local/lib/python3.6/dist-packages/IPython/core/shadowns.py'>, 'In': ['', "name = input('Who are you?')\nprint('Welcome', name)", 'astr = "123"\n\ntry:\n  print("Hello")\n  islnt = int(astr)\n  print("World")\nexcept:\n  islnt = "Integer로 변환할 수 없습니다."\n\nprint(\'Done\', islnt)', "def thing():\n  print('Hello')\n  print('Fun')\n\nthing()\nprint('Zip')\nthing", "def thing():\n  print('Hello')\n  print('Fun')\n\nthing()\nprint('Zip')\nthing()", "while True:\n    line = input('> ')\n    if line[0] == '#' :\n        continue\n    if line == 'done' :\n        break\n    print(line)\nprint('Done!')", 'a = 2 \n\ndef test():\n  a = 3\n  print("inside test:", a)\n\ntest()', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest\nprint("after test:, a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest\nprint("after test":, a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest\nprint("after test", a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest()\nprint("after test", a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest()\nprint("after test", a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', 'a = 2 \n\ndef test()\n  global a\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', 'a = 2 \n\ndef test():\n  global a\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', "def a():\n  # 함수 a 내부의 이름 공간\n  l = 0\n  s = 'apple'\n\n  def b():\n    # 함수 b내부의 이름 공간\n    m = 1\n    t = 'banana'\n    print('in function b:', locals())\n    print('in global from b:', globals())\n\n  print('in function a:',locals())\n  b()\n\n# global의 이름공간\nn = 2\nu = 'cake'\na()\nprint('in global:',globals())"], 'Out': {3: <function thing at 0x7fa8ee0cbc80>}, 'get_ipython': <bound method InteractiveShell.get_ipython of <google.colab._shell.Shell object at 0x7fa8f38cf550>>, 'exit': <IPython.core.autocall.ZMQExitAutocall object at 0x7fa8f0e944e0>, 'quit': <IPython.core.autocall.ZMQExitAutocall object at 0x7fa8f0e944e0>, '_': <function thing at 0x7fa8ee0cbc80>, '__': '', '___': '', '_i': 'a = 2 \n\ndef test():\n  global a\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', '_ii': 'a = 2 \n\ndef test()\n  global a\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', '_iii': 'a = 2 \n\ndef test():\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', '_i1': "name = input('Who are you?')\nprint('Welcome', name)", 'name': 'kim', '_i2': 'astr = "123"\n\ntry:\n  print("Hello")\n  islnt = int(astr)\n  print("World")\nexcept:\n  islnt = "Integer로 변환할 수 없습니다."\n\nprint(\'Done\', islnt)', 'astr': '123', 'islnt': 123, '_i3': "def thing():\n  print('Hello')\n  print('Fun')\n\nthing()\nprint('Zip')\nthing", 'thing': <function thing at 0x7fa8ee0cbea0>, '_3': <function thing at 0x7fa8ee0cbc80>, '_i4': "def thing():\n  print('Hello')\n  print('Fun')\n\nthing()\nprint('Zip')\nthing()", '_i5': "while True:\n    line = input('> ')\n    if line[0] == '#' :\n        continue\n    if line == 'done' :\n        break\n    print(line)\nprint('Done!')", 'line': 'done', '_i6': 'a = 2 \n\ndef test():\n  a = 3\n  print("inside test:", a)\n\ntest()', 'a': <function a at 0x7fa8e63dc048>, 'test': <function test at 0x7fa8edff28c8>, '_i7': 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest\nprint("after test:, a)', '_i8': 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest\nprint("after test":, a)', '_i9': 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest\nprint("after test", a)', '_i10': 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest()\nprint("after test", a)', '_i11': 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest()\nprint("after test", a)', '_i12': 'a = 2 \n\ndef test():\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', '_i13': 'a = 2 \n\ndef test()\n  global a\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', '_i14': 'a = 2 \n\ndef test():\n  global a\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', '_i15': "def a():\n  # 함수 a 내부의 이름 공간\n  l = 0\n  s = 'apple'\n\n  def b():\n    # 함수 b내부의 이름 공간\n    m = 1\n    t = 'banana'\n    print('in function b:', locals())\n    print('in global from b:', globals())\n\n  print('in function a:',locals())\n  b()\n\n# global의 이름공간\nn = 2\nu = 'cake'\na()\nprint('in global:',globals())", 'n': 2, 'u': 'cake'}
in global: {'__name__': '__main__', '__doc__': 'Automatically created module for IPython interactive environment', '__package__': None, '__loader__': None, '__spec__': None, '__builtin__': <module 'builtins' (built-in)>, '__builtins__': <module 'builtins' (built-in)>, '_ih': ['', "name = input('Who are you?')\nprint('Welcome', name)", 'astr = "123"\n\ntry:\n  print("Hello")\n  islnt = int(astr)\n  print("World")\nexcept:\n  islnt = "Integer로 변환할 수 없습니다."\n\nprint(\'Done\', islnt)', "def thing():\n  print('Hello')\n  print('Fun')\n\nthing()\nprint('Zip')\nthing", "def thing():\n  print('Hello')\n  print('Fun')\n\nthing()\nprint('Zip')\nthing()", "while True:\n    line = input('> ')\n    if line[0] == '#' :\n        continue\n    if line == 'done' :\n        break\n    print(line)\nprint('Done!')", 'a = 2 \n\ndef test():\n  a = 3\n  print("inside test:", a)\n\ntest()', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest\nprint("after test:, a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest\nprint("after test":, a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest\nprint("after test", a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest()\nprint("after test", a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest()\nprint("after test", a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', 'a = 2 \n\ndef test()\n  global a\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', 'a = 2 \n\ndef test():\n  global a\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', "def a():\n  # 함수 a 내부의 이름 공간\n  l = 0\n  s = 'apple'\n\n  def b():\n    # 함수 b내부의 이름 공간\n    m = 1\n    t = 'banana'\n    print('in function b:', locals())\n    print('in global from b:', globals())\n\n  print('in function a:',locals())\n  b()\n\n# global의 이름공간\nn = 2\nu = 'cake'\na()\nprint('in global:',globals())"], '_oh': {3: <function thing at 0x7fa8ee0cbc80>}, '_dh': ['/content'], '_sh': <module 'IPython.core.shadowns' from '/usr/local/lib/python3.6/dist-packages/IPython/core/shadowns.py'>, 'In': ['', "name = input('Who are you?')\nprint('Welcome', name)", 'astr = "123"\n\ntry:\n  print("Hello")\n  islnt = int(astr)\n  print("World")\nexcept:\n  islnt = "Integer로 변환할 수 없습니다."\n\nprint(\'Done\', islnt)', "def thing():\n  print('Hello')\n  print('Fun')\n\nthing()\nprint('Zip')\nthing", "def thing():\n  print('Hello')\n  print('Fun')\n\nthing()\nprint('Zip')\nthing()", "while True:\n    line = input('> ')\n    if line[0] == '#' :\n        continue\n    if line == 'done' :\n        break\n    print(line)\nprint('Done!')", 'a = 2 \n\ndef test():\n  a = 3\n  print("inside test:", a)\n\ntest()', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest\nprint("after test:, a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest\nprint("after test":, a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest\nprint("after test", a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest()\nprint("after test", a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest()\nprint("after test", a)', 'a = 2 \n\ndef test():\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', 'a = 2 \n\ndef test()\n  global a\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', 'a = 2 \n\ndef test():\n  global a\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', "def a():\n  # 함수 a 내부의 이름 공간\n  l = 0\n  s = 'apple'\n\n  def b():\n    # 함수 b내부의 이름 공간\n    m = 1\n    t = 'banana'\n    print('in function b:', locals())\n    print('in global from b:', globals())\n\n  print('in function a:',locals())\n  b()\n\n# global의 이름공간\nn = 2\nu = 'cake'\na()\nprint('in global:',globals())"], 'Out': {3: <function thing at 0x7fa8ee0cbc80>}, 'get_ipython': <bound method InteractiveShell.get_ipython of <google.colab._shell.Shell object at 0x7fa8f38cf550>>, 'exit': <IPython.core.autocall.ZMQExitAutocall object at 0x7fa8f0e944e0>, 'quit': <IPython.core.autocall.ZMQExitAutocall object at 0x7fa8f0e944e0>, '_': <function thing at 0x7fa8ee0cbc80>, '__': '', '___': '', '_i': 'a = 2 \n\ndef test():\n  global a\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', '_ii': 'a = 2 \n\ndef test()\n  global a\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', '_iii': 'a = 2 \n\ndef test():\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', '_i1': "name = input('Who are you?')\nprint('Welcome', name)", 'name': 'kim', '_i2': 'astr = "123"\n\ntry:\n  print("Hello")\n  islnt = int(astr)\n  print("World")\nexcept:\n  islnt = "Integer로 변환할 수 없습니다."\n\nprint(\'Done\', islnt)', 'astr': '123', 'islnt': 123, '_i3': "def thing():\n  print('Hello')\n  print('Fun')\n\nthing()\nprint('Zip')\nthing", 'thing': <function thing at 0x7fa8ee0cbea0>, '_3': <function thing at 0x7fa8ee0cbc80>, '_i4': "def thing():\n  print('Hello')\n  print('Fun')\n\nthing()\nprint('Zip')\nthing()", '_i5': "while True:\n    line = input('> ')\n    if line[0] == '#' :\n        continue\n    if line == 'done' :\n        break\n    print(line)\nprint('Done!')", 'line': 'done', '_i6': 'a = 2 \n\ndef test():\n  a = 3\n  print("inside test:", a)\n\ntest()', 'a': <function a at 0x7fa8e63dc048>, 'test': <function test at 0x7fa8edff28c8>, '_i7': 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest\nprint("after test:, a)', '_i8': 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest\nprint("after test":, a)', '_i9': 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest\nprint("after test", a)', '_i10': 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest()\nprint("after test", a)', '_i11': 'a = 2 \n\ndef test():\n  print("inside test", a)\n\ntest()\nprint("after test", a)', '_i12': 'a = 2 \n\ndef test():\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', '_i13': 'a = 2 \n\ndef test()\n  global a\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', '_i14': 'a = 2 \n\ndef test():\n  global a\n  print("inside test", a)\n  a = 3\n\ntest()\nprint("after test", a)', '_i15': "def a():\n  # 함수 a 내부의 이름 공간\n  l = 0\n  s = 'apple'\n\n  def b():\n    # 함수 b내부의 이름 공간\n    m = 1\n    t = 'banana'\n    print('in function b:', locals())\n    print('in global from b:', globals())\n\n  print('in function a:',locals())\n  b()\n\n# global의 이름공간\nn = 2\nu = 'cake'\na()\nprint('in global:',globals())", 'n': 2, 'u': 'cake'}
"""

 와 같이 작성한다면, 함수 a의 내부, 함수 b 내부와 global로 전부 3 개의 이름 공간이 생성된다.

- 함수 a 내부에서는{'b': <function a.<locals>.b at 0x7fa8e63dc488>, 's': 'apple', 'l': 0}

- 함수 b 내부에서는 {'t': 'banana', 'm': 1}

- 함수 b 내부에서 본 global은 {a: 함수 객체a, n: 2, u: 'cake', 등}

- global에서는 {a : 함수객체 a, n: 2, u : 'cake', 등}

 

2. Scope란?

- Scope란 어떠한 이름공간으로 부터 (직접) 접근가능한 이름 공간의 범위이다.

- 직접 접근이란 a.b와 같이 '.'를 사용하여 상관관계를 지정할 필요 없이, 단순히 'b'와 같이 작성하는 것으로 접근하는 방법이다.

- Scope는 '시야'와 같은 것으로 어떤 이름공간이 생겼을 때, 그곳에서 볼 수 있는 이름공간은 어디까지인가에 대한 이야기이다.

- 기본적으로 자신보다 바깥쪽의 변수가 Scope에 포함된다.

- Scope에는 몇 가지 종류가 있다.

 

1) Scope의 우선순위

- 변수를 평가할 때, 그것을 참고하는 객체를 찾기 위해서 그 변수를 가진 이름공간의 검색하는 것을 시작한다.

- 아무것도 지정되지 않은 경우 아래와 같은 순서대로 변수를 검색한다.

① local(자신의 함수내)

② nonlocal(local도 global도 아닌 중간 영역)의 중, 보다 local에 가까운 내부쪽부터 먼저 검색된다.

③ global(자신의 모듈)

④ built-ins(len(), print(), str()등 이미 존재하는 함수), 더 정확히 말하자면 글로벌 변수인 __builtins__의 속성을 검색한다.

print(spam)가 어떠한 이름공간으로 부터 본 scope는 아래와 같다.

- builtins는 import builtins로 명시적으로 import 가능하다.

- builtins는 global보다 우선 순위가 낮다.

import builtins
builtins.spam = "hello"

print(spam)
#=>hello

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

 

2) 명시적인 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
"""

참고자료

https://www.atmarkit.co.jp/ait/articles/1612/09/news030.html

https://qiita.com/Liesegang/items/085f53a13ac140b84202

728x90