본문 바로가기
CS

[Python] 데코레이터는 언제 실행되나요? - import

by 왕밤빵도라에몽 2025. 4. 20.
728x90

최근 데코레이터는 임포트 시점에 정의된다고 배웠다.

def my_decorator(func):
    print("데코레이터 실행됨")  # <-- 임포트/정의 시점에 실행됨
    def wrapper(*args, **kwargs):
        print("함수 실행 전")
        result = func(*args, **kwargs)
        print("함수 실행 후")
        return result
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")
  • 이 코드를 모듈로 저장해놓고 다른 파일에서 import를 하면 say_hello()를 호출하지 않아도 my_decorator()는 실행되어서 "데코레이터 실행됨"이 먼저 출력된다.

왜 데코레이터는 함수 정의 시점에 실행될까?

1. 데코레이터는 그냥 함수를 다른 함수에 넣는 문법이다.

파이썬에서는 아래 두 줄을 완전히 동등하게 취급한다.

@my_decorator
def say_hi():
    print("hi")
def say_hi():
    print("hi")

say_hi = my_decorator(say_hi)

 

 

따라서 데코레이터는 함수를 인자로 받아서, 가공해서 다시 반환하는 함수라고 볼 수 있다.

 

2. 함수 정의 시점 == 코드가 실행되는 시점

파이썬은 코드를 위에서 아래로 스크립트처럼 실행한다.
def함수를 만다면 함수를 생성, @를 만나면 데코레이터를 호출해서 원래 함수를 감싼다.

def deco(func):
    print("deco 실행됨")
    return func

@deco
def f():
    print("f 실행")

# 출력:
# deco 실행됨

 

@deco는 f를 deco에 넣고 결과로 바꿔달라는 뜻이니까

함수 f()가 정의되는 그 시점에 deco(f)가 실행되어야 자연스럽다.

 

3. 파이썬 내부적으로는

@deco는 단순히 아래처럼 처리되는 문법이다.

f = deco(f)

 

파이썬 컴파일러가 내부적으로 치환해서 처리하는 문법 규칙일 뿐이다.

 

 

 

그럼 파이썬 내부적으로 데코레이터를 어떻게 파싱해내는 걸까? 

AST (Abstract Syntax Tree)

파이썬은 코드를 실행하기 전에 코드를 구조적으로 해석해서 트리 형태로 바꾼다.(컴파일러나 인터프리터의 역할)
이 구조를 AST(추상 구문 트리)라고 한다.
이 트리를 기반으로 실행, 변환, 검사를 진행한다.


파이썬의 코드 이해 과정 예시를 보면,

@my_decorator
def hello():
    print("hi")
  • 함수 이름은 hello이고
  • 함수 안에 print 어쩌구 있고
  • 근데 이 함수는 @my_decorator라는 데코레이터 함수에 감싸져야 함
  • 그래서 최종적으로는 hello = my_decorator(hello)형태로 반환됨

ast모듈을 써서 AST를 직접 확인할 수 있다.

import ast
import astpretty

code = """
@my_decorator
def hello():
    print("hi")
"""

tree = ast.parse(code)
astpretty.pprint(tree)
Module(
    body=[
        FunctionDef(
            name='hello',
            decorator_list=[
                Name(id='my_decorator', ctx=Load())
            ],
            ...
        )
    ]
)
  • Module: 하나의 파이썬 파일 전체
  • Module.body: 파이썬 소스코드의 최상위 문장들 소속
    • FunctionDef(함수 정의), ClassDef(클래스 정의), Assign(변수 할당), Import/ImportFrom(모듈 가져오기) ...

모든 코드 블록은 .body를 가진다. FunctionDef안에 코드가 포함되니 FunctionDef.body도 당연 존재

 

아무튼 여기서 핵심은 decorator_list이다.

파이썬은 이걸 보면서 hello라는 함수가 my_decorator에 감싸야함을 인식한다.  

 

 

 

그럼 AST는 파이썬 import에서 어느 부분에 속할까?

그 전에 python import에 대해서 이해하자

파이썬 import

파이썬에서 import는 반드시 한 번만 실행된다.

import mymodule

 

해당 코드는 단순히 다른 코드 불러오기가 아니라 실행 그 자체이다.

즉,  mymodule.py 파일의 모든 코드가 즉시 실행된다. (정확히는 Module.body 전체)

 

 

import의 핵심 흐름

  • 모듈 찾기
    • sys.modules에 해당 모듈이 이미 로드되어있는지 확인
    • 없으면 .py / .pyc / 패키지 디렉토리 검색
  • 파싱 + AST 생성
    • 찾은 .py 파일을 열어서 문자열로 읽고
    • 문자열을 파싱해서 AST로 만듦
  • 컴파일 (AST -> Bytecode)
    • AST 트리를 바이트코드로 컴파일
  • 실행
    • 컴파일된 바이트코드 객체 실행
  • 모듈 객체 등록 sys.module
    • 결과로 나온 모듈 객체를 sys.modules["mymodule"]에 저장
    • 호출한 쪽에는 그 모듈 바인딩

*결과로 나온 모듈 객체가 뭐임?

하나의 독립된 네임스페이스를 가진 module 객체

mymodule.py 안에 정의된 함수, 클래스, 변수, 상수, import 등 모든 이름 담고 있는 딕셔너리 기반의 공간!

print(mymodule)  # <module 'mymodule' from '...'>

 

정말로 객체 그 자체다. 코드가 이님.

그래서 mymodule.add, mymodule.Dog 이런 식으로 접근할 수 있는 것!

 

 

왜 import는 한 번만 실행될까?

파이썬은 sys.modules라는 캐시를 가지고 있어서

여러 파일이 동일 모듈을 import해도 최초 1번만 실행되고, 이후엔 캐시된 객체만 넘겨준다.

 

 

정리

import는 파싱(AST) -> 컴파일 -> 실행 -> 캐시등록 까지 한 세트 이다.

(참고로 모듈을 찾는 과정은 import를 위한 준비 과정이지 import 과정에 속하지는 않는다.)

 

 

이렇게 정리된 import 흐름에 따라 데코레이터 실행은 아래와 같이 정리할 수 있다.

AST -> 데코레이터 발견

compile -> 바이트 코드로 전환

exex -> 함수 정의 + 데코레이터 호출까지 실행

 

AST 상의 FunctionDef 노드가 실행될 때 (def가 실행될 때),

데코레이터도 호출되는 거니까(FunctionDef 속성에 데코레이터 리스트 있었으니까)...

- 데코레이터는 def가 실행될 때 호출된다.

- def는 모듈이 임포트 될 때 실행되고 

- 따라서 데코레이터도 임포트 시 실행된다고 하는 거임

 

아까 언급했던 "데코레이터가 함수 정의 시점에서 실행"된다는 것을 이제 완전히 이해할 수 있게 되었다~

 

728x90