Python 클로저: JavaScript에서 온 경우
Source: Dev.to
위에 제공된 소스 링크 외에 번역할 텍스트를 알려주시면 한국어로 번역해 드리겠습니다.
Source: …
책으로 배우기 vs. 영상
“책 – 주제에 대한 깊이 있는 지식 습득
영상 – 특정 기술을 빠르게 익히기”
새로운 것을 배우고 싶을 때면 여전히 책을 집어 듭니다. 오늘날의 문서, Udemy 강좌, 그리고 기타 영상 자료들이 매우 귀중하지만, 텍스트(디지털이든 종이든) 한 장에 담긴 느낌이 더 영구적인 것 같습니다.
올해의 목표 중 하나는 Python 실력을 끌어올려 AI/ML 솔루션을 보다 편하게 만들 수 있게 하는 것입니다. 그 여정의 핵심 부분은 Python의 클로저(closures) 를 이해하는 것이었습니다.
*아래 대부분의 자료는 **Fluent Python (2nd Ed.)**와 **Python Cookbook (3rd Ed.)*에서 발췌한 것으로, 실용적이고 흥미로운 Python 안내서로 강력히 추천합니다.
클로저가 중요한 이유
- 데코레이터 – 파이썬 데코레이터는 클로저를 기반으로 합니다(자세한 내용은 나중에).
- 콜백 – 자바스크립트 콜백과 마찬가지로, 클로저는 상태를 “기억”하도록 합니다.
- 팩토리 함수 – 즉석에서 특수화된 함수를 생성합니다(예: 곱셈기).
- 데이터 캡슐화 – 클래스를 사용하지 않고 내부 상태를 숨깁니다.
Flask에서 @app.route()를 작성한 적이 있다면, 이미 클로저를 사용한 것입니다—비록 인식하지 못했더라도.
클로저 정의
외부(상위) 함수의 스코프에 있는 변수를 기억하고 접근할 수 있는 중첩 함수로, 외부 함수가 실행을 마친 후에도 내부 함수가 상태를 유지할 수 있게 합니다.
기본 예제: 승수기
Python 구현 (mult_closure.py)
def make_multiplier_of(n):
"""Outer function that takes a factor `n` and returns a closure."""
def multiplier(x):
"""Inner function (closure) that uses the remembered factor `n`."""
return x * n
return multiplier
# Create two closures, each remembering a different `n` value
doubler = make_multiplier_of(2) # remembers n = 2
tripler = make_multiplier_of(3) # remembers n = 3
# Results – the scalar is remembered, so we only pass the value to be multiplied
print(f"8 times 2 is {doubler(8)}")
print(f"4 times 3 is {tripler(4)}")
Output
8 times 2 is 16
4 times 3 is 12
설명: 외부 함수가 중첩된 multiplier 함수를 반환하며, 이 함수는 n 값을 기억합니다.
호출 방식 시각화
# Saved to a variable
doubler = make_multiplier_of(2)
doubler(4)
# Executed in‑place
make_multiplier_of(2)(4)
Source:
JavaScript에서 동일한 아이디어
JavaScript 구현 (mult_closure.js)
function makeMultiplierOf(n) {
// Outer function that takes a factor `n` and returns a closure
function multiplier(x) {
// Inner function (closure) that uses the remembered factor `n`
return x * n;
}
return multiplier;
}
// Instances of our new closure
const doubler = makeMultiplierOf(2);
const tripler = makeMultiplierOf(3);
// Results – the closure remembers the initial value it was passed
console.log(`8 times 2 is ${doubler(8)}`);
console.log(`4 times 3 is ${tripler(4)}`);
호출 간 상태 유지
클로저는 호출 사이에 지속되는 가변 데이터를 보유할 수 있습니다. 아래 예시는 외부 함수의 스코프에 보관된 리스트에 메시지를 저장하는 로거입니다.
Python 로거 클로저 (logger_closure.py)
def create_logger(source):
"""Outer function that holds a list of logs for a given `source`."""
logs = []
def log_message(message=None):
"""
Inner function that appends a message (if provided) and always returns
the current list of logs.
"""
if message:
logs.append({"source": source, "message": message})
return logs
return log_message
# Two independent loggers
error_log = create_logger("error")
info_log = create_logger("info")
# Log some messages
info_log("Hello world")
error_log("File Not Found")
# Retrieve logs
print(error_log("Zero Division"))
print(info_log())
출력
[{'source': 'error', 'message': 'File Not Found'},
{'source': 'error', 'message': 'Zero Division'}]
[{'source': 'info', 'message': 'Hello world'}]
핵심 포인트: create_logger를 호출할 때마다 고유한 logs 리스트가 생성됩니다. error_log와 info_log는 동일한 클로저 구조를 공유하지만, 각각 별개의 상태를 유지합니다.
파이썬의 변수 스코핑 vs. 자바스크립트
- 자바스크립트 (ES6 이전) 은
var에 의존했으며, 함수 스코프를 신중히 다뤄야 했습니다. 현대 JS는let과const를 사용해 블록 스코프 변수를 생성합니다. - 파이썬 은
let/const와 직접 대응되는 것이 없습니다. 대신 LEGB 규칙(Local → Enclosing → Global → Built‑in)을 따라 이름을 해석합니다. 이 규칙을 이해하는 것이 클로저를 사용할 때 필수적입니다.
TL;DR
- 클로저는 중첩 함수가 자신을 둘러싼 스코프의 값을 기억하도록 합니다.
- 클로저는 데코레이터, 콜백, 팩토리 함수, 그리고 간단한 상태를 가진 객체의 핵심입니다.
- 파이썬의 렉시컬 스코핑(LEGB)은 규칙을 파악하면 클로저를 직관적으로 사용할 수 있게 해줍니다.
행복한 코딩 되세요! 🚀
Example 4 – legb_overview.py: LEGB 규칙의 시각적 표현
"""Global scope"""
x = "global"
def outer():
"""Enclosing scope"""
y = "enclosing"
def inner():
"""Local scope"""
z = "local" # Local scope
# Python searches: Local → Enclosing → Global → Built‑in
print(z) # Found in Local
print(y) # Not in Local, found in Enclosing
print(x) # Not in Local/Enclosing, found in Global
print(len) # Not in Local/Enclosing/Global, found in Built‑in
inner()
outer()
Output
local
enclosing
global
How the LEGB search works
| Scope | Description |
|---|---|
| Local | 인터프리터는 먼저 현재 함수의 로컬 네임스페이스를 확인합니다. |
| Enclosing | 함수가 중첩된 경우, 파이썬은 한 단계 위의 감싸는 함수 네임스페이스를 살펴봅니다(클로저에서 많이 사용됨). |
| Global | 다음으로 모듈 수준 네임스페이스(파일 상단에 정의되었거나 임포트된 변수)를 확인합니다. |
| Built‑in | 마지막으로 파이썬 내장 네임스페이스(예: len, print)를 확인합니다. |
Important: 파이썬은 일치하는 이름을 찾는 즉시 검색을 중단합니다. 일치 항목을 찾은 후에는 외부 스코프로 계속 탐색하지 않습니다.
nonlocal 키워드
예제 5 – make_avg_err.py: nonlocal 없이 클로저 만들기
"""Before"""
def make_avg():
count = 0
total = 0
def inner(new_val):
count += 1 # `nonlocal`은 (전역이 아닌) 상위 스코프에 있는 변수를 **재바인딩**해야 할 때 필요합니다. `int`, `float`, `str` 등과 같은 불변 객체에 적용됩니다.
가변 객체(예: 리스트)의 경우, 이름 자체를 재바인딩하지 않기 때문에
nonlocal없이도 객체의 내용을 수정할 수 있습니다(my_list.append(item)).
JavaScript 개발자를 위한 간단한 메모
JavaScript에서 온 경우, 같은 패턴을 파이썬에 그대로 적용하고 싶을 수 있습니다. 하지만 파이썬의 스코프 규칙—특히 클로저와 nonlocal 키워드와 관련된 부분—은 다르게 동작합니다. 이러한 미묘한 차이를 이해하면 은밀한 버그를 피하고 코드를 보다 “파이썬답게” 만들 수 있습니다.
다음은?
저는 현재 Python의 효율성에 기여하는 많은 “작은 것들”을 탐구하기 위해 AST File Parser를 만들고 있습니다. 목표는 이번 달 말까지 프로젝트를 마무리하고, 그 후에는 다음과 같은 다른 Python 주제로 파고들 예정입니다:
- 제너레이터
- 데코레이터
- 특수(매직) 메서드
이 개념들에 대해 논의하고 싶으시면 언제든지 연락 주세요!