반응형

파이썬을 처음 배우다 보면 다음과 같은 의문이 생길 수 있다:

"서로 다른 파일에서 함수와 변수가 어떻게 서로를 찾아가는 걸까?"

 간단한 예제를 통해 이 개념을 명확하게 이해해보자.

 

아래와 같이 file_a.py file_b.py가 있다.

# file_a.py
some_variable = "Hello"

def some_function():
    print(some_variable)  # 이 some_variable은 어디서 오는가?
# file_b.py
from file_a import some_function

some_function()  # 실행하면 "Hello"가 출력됨

단순한 예제이지만, 파이썬의 중요한 동작원리가 숨어있다.

 

실행 순서 이해하기

python file_b.py 명령을 실행하면 다음과 같은 순서로 코드가 실행된다:

  1. file_b.py 실행 시작.
  2. from file_a import some_function 구문을 만남.
  3. 파이썬은 file_a.py를 찾아서 전체를 실행함.
    • some_variable = "Hello" 가 실행됨.
    • some_function() 함수가 정의됨.
  4. some_functionfile_b.py의 namespace로 가져옴.
    • file_a의 namespace에 있는 객체 중 오로지 some_function만 가져온 것.
    • 만약 모든 것을 가져오려면, from file_a import *를 사용해야 함.
  5. file_b.py에서 some_function() 호출.
    • "Hello"가 출력됨.

 

Namespace와 Scope

여기서 핵심적인 개념이 바로 namespacescope이다.

Namespace

정의: 코드가 실행되는 어떤 특정 시점에 사용 가능한 모든 이름과 그에 매핑된 객체들의 집합. 변수나 함수의 정의, import, 삭제 등의 작업이 발생할 때마다 namespace는 계속 변화한다.

# 시점 1: module이 처음 로드될 때
x = 1
# namespace: {'x': 1}

# 시점 2: 함수가 정의된 후
def add_number(y):
    return x + y
# namespace: {'x': 1, 'add_number': <function>}

# 시점 3: 새로운 변수가 추가된 후
z = 2
# namespace: {'x': 1, 'add_number': <function>, 'z': 2}

# 시점 4: 기존 변수가 삭제된 후
del x
# namespace: {'add_number': <function>, 'z': 2}
# 시점 1: 스크립트 시작
# namespace: {}

# 시점 2: math module의 import 후
import math
# namespace: {'math': <module>}

# 시점 3: math에서 특정 함수만 import 후
from math import sin
# namespace: {'math': <module>, 'sin': <function>}
  • 각 파이썬 module은 자신만의 namespace를 가진다.
    • file_a의 namespace는 some_variablesome_function의 집합으로 구성된다.
    • some_variablefile_a의 namespace에 존재하는 객체이다.
  • 한 module에서 다른 module의 객체를 직접 import해야, namespace에 해당 객체가 비로소 추가된다.
    • file_b에서 file_asome_variable을 import하므로, file_b의 namespace에 some_variable이 추가된다.

Scope

정의: "변수의 유효 범위" 혹은 "이름이 유효한 범위"를 의미한다. 즉, namespace의 유효 범위를 의미한다.

Scope는 코드 블록에서 변수가 접근 가능한 범위를 정의한다.

LEGB 규칙(Scope의 4단계)

  1. Local(L): 가장 안쪽의 함수나 클래스 메서드 내부
  2. Enclosing(E): 중첩 함수에서 바깥쪽 함수의 범위
  3. Global(G): module 수준의 전역 범위
  4. Built-in(B): 파이썬 내장 함수와 예약어가 있는 범위

Scope 검색 순서

파이썬에서 변수를 찾을 때는 다음 순서로 검색한다:

 

  1. local scope (함수 내부)
  2. enclosing scope (중첩 함수에서 바깥 함수 범위)
  3. global scope (Module 레벨)
  4. built-in scope (파이썬 내장 함수/변수)

따라서 some_functionsome_variable을 찾는 과정은 다음과 같다:

  1. local scope 검색: 함수 내부에 없음.
  2. enclosing scope 검색: 중첩 함수 구조가 아니므로 미해당.
  3. global scope 검색: file_a의 module 레벨에서 찾아냄.
  4. built-in scope 검색: global scope에서 찾았으므로 여기까지 오지 않음.

 

Module import의 특징

파이썬의 module 시스템에는 다음과 같은 중요한 특징이 있다:

  1. 한 번만 실행:
    • Module은 처음 import될 때만 실행된다.
    • 같은 module을 여러 번 import해도 한 번만 실행된다.
  2. 클로저(Closure):
    • 함수는 자신이 정의된 환경의 변수들을 기억한다.

 

(심화) 함수는 "자신이 정의된 환경"의 어디까지 기억하는가?

 

  • 함수는 자신의 enclosing scope의 변수들 중 자신이 사용하는 변수들만 기억한다.
  • global scope의 변수는 클로저에 저장되지 않고, 필요할 때 module에서 찾는다.
  • built-in scope의 함수/변수들도 저장되지 않고, 필요할 때 찾는다.
# global scope
global_var = "global"

def outer():
    outer_var = "outer"  # enclosing scope
    
    def inner():
        local_var = "local"  # local scope
        print(local_var)    # 1. 당연히 접근 가능
        print(outer_var)    # 2. outer 함수의 변수 기억
        print(global_var)   # 3. global 변수도 접근 가능
        print(len("test"))  # 4. built-in 함수도 사용 가능
    
    return inner  # inner 함수를 반환

# inner 함수를 할당
func = outer()

# 나중에 실행해도 outer_var를 기억
func()  # 모든 출력이 정상적으로 작동

 

 

1. 클로저의 범위

def outer():
    outer_var = "remembered"  # 이 변수는 클로저에 저장됨
    
    def inner():
        print(outer_var)
    
    return inner

func = outer()
print(func.__closure__)  # 클로저 정보 확인 가능

2. 실제로 기억하는 것

def outer():
    outer_var = "outer"
    other_var = "not used"  # inner에서 사용하지 않음
    
    def inner():
        print(outer_var)
    
    return inner

func = outer()
# outer_var만 클로저에 저장되고
# other_var는 저장되지 않음

3. 기억하지 않는 것

global_var = "global"

def simple_func():
    print(global_var)

# global_var는 클로저에 저장되지 않음
# 왜냐하면 module 레벨(global scope)의 변수이기 때문

 

따라서 앞선 예제의 some_functionsome_variable의 값을 기억하는게 아니라, some_variable이 global scope의 변수이므로 자신이 정의된 module인 file_a.py에서 찾아보는 것이다.

# file_a.py
some_variable = "Hello"

def some_function():
    print(some_variable)

# 클로저 확인
print(some_function.__closure__)  # None이 출력됨
# 클로저가 None이라는 것은 저장된 변수가 없다는 의미

 

 

결론

파이썬의 namespace와 scope는 변수와 함수의 가시성과 생명주기를 결정하는 중요한 개념이다. 이들을 제대로 이해하고 활용하면, 변수 이름 충돌을 방지하고, 코드의 module성을 높이는 데 유용하다.

반응형

'Python > 문법' 카테고리의 다른 글

[Python] Module과 Package  (0) 2025.01.18
[Python] import 문 사용 팁  (0) 2025.01.13
[Python] Unpacking의 다양한 예시  (2) 2024.12.27
[Python] Iterable과 Sequence 자료형  (0) 2024.12.27
[Python] 제너레이터(Generator)  (1) 2024.12.14

+ Recent posts