-
[lambda x: i * x for i in range(4)]Python 2022. 11. 7. 15:24
def create_multipliers(x): return [lambda x : i * x for i in range(4)] for multiplier in create_multipliers(): print(multiplier(2))출력 값이 어떻게 나올까?
0,2,4,6이라 생각했지만 6,6,6,6이 나왔다.
한참을 생각했고 도저히 이해가 되지 않았다.
찾아보다 python closure 때문이라는 것을 찾고 개념을 보았다. 배경지식부터 [lambda x: i * x for i in range(4)] 의 출력 값의 이유를 알아보자
중첩 함수
def first(): def hello_world(): print("hello world!") hello_world() first()hello_world라는 함수를 first 함수로 감싸서 hello_world를 호출하고 있다. 이것이 중첩 함수이다.
나는 중첩 함수이면 다 클로저라고 알았지만 아니었다.
1급 객체 (first class object)
처음 들어보는 개념이다.
일반적으로 다른 개체에 통용 가능한 동작이 지원되는 개체라고 한다.
통용 가능한 동작이란 크게 3가지이다.
- 함수의 인자로 전달
- 함수의 반환 값이 됨
- 수정되고 할당되는 것들을 전제로 한다.
무슨 소린지 감이 안 온다.
우리가 흔히 사용하던 자료형들 int, str, list 등 위의 3가지 동작이 가능하다. 1급 객체이다
파이썬에서는 함수도 1급 객체이다.
def add(a, b): return a + b def execute(func, *args): return func(*args) func = add print(execute(func, 3, 5))- func라는 함수를 함수의 인자로 전달했다.
- 함수 내부에서 전달받은 func함수를 문제없이 사용한다.
- add 함수를 func라는 새 이름에 할당했다.
이 특성이 있어야 closure가 성립될 수 있다.
nonlocal
파이썬에서 중첩 함수가 가능한 것을 확인했다.
z = 3 def outer(x): y = 2 def inner(): x = 1 return x return inner() print(outer(10))위의 예시대로 한다면 인자를 10으로 줬을 때 반환되는 값은 몇일까?
결국 scope에 관한 이야기이다. inner함수의 입장에서 보자.
- inner 함수 안에 있는 영역은 local scope라고 불린다. 안의 모든 개체들은 inner의 제어 아래에 있다
- outer안에 있고, inner 밖에 있는 영역은 nonlocal scope라고 불린다.
- outer 함수 밖의 영역은 global scope이다.
이런 scope의 구분은 자신의 영역에 자유롭고 다른 영역의 변수나 객체에 대해서는 제한적인 제어를 가지게 된다.
def first(x): def hello_world(): print(x) hello_world() first("헬로 월드")문제없이 헬로 월드가 출력된다.
다른 영역 즉 외부 스코프에 대해 "읽기"가 문제없이 가능한 것을 확인했다.
def first(x): def hello_world(): x = x + "!!" print(x) hello_world() first("헬로 월드") # UnboundLocalError: local variable 'x' referenced before assignment뭐가 문제일까? local 영역에서 다른 영역에 대한 값을 할당하거나 수정 즉 "쓰는 것"은 불가능하다.
왜 이렇게 설계가 되어있을까? 코드 영역의 책임과 권한을 명확히 나누는 것이 좋기 때문이다.
Closure
자신을 둘러싼 scope의 상태 값을 기억하는 함수이다.
다음의 세 가지 조건을 만족해야 한다.
- 해당 함수는 어떤 함수 내의 중첩된 함수 이어야 한다.
- 해당 함수는 자신을 둘러싼(enclose) 함수 내의 상태 값을 반드시 참조해야 한다.
- 해당 함수를 둘러싼 함수는 이 함수를 반환해야 한다.
클로저의 특징은 무엇일까?
- 클로저는 자신을 둘러싼 함수 스코프의 상태 값을 참조하는데 이 값은 메모리에서 사라져도 값이 유지가 된다.
- 클로저의 내부 변수가 아닌 감싸고 있는 함수의 변수에 접근하는 것을 지원한다.
someting.__closure__[0].cell_contents클로저 사용 시 장점은 무엇일까?
- 관리와 책임을 명확히 할 수 있다.
- 변수가 섞여 발생하는 충돌을 방지할 수 있다.
- 임의대로 내부구조를 조정할 수 있다.
자 그럼 마지막으로 해결해야 하는 [lambda x: i * x for i in range(4)]의 이유를 알아보자
결과적으로 이유는 파이썬의 late binding closure 때문이다.
실제로 위의 함수를 풀어보면
def create_multipliers(x): multipliers = [] for i in range(4): def multiplier(x): return i * x multipliers.append(multiplier) return multipliers위의 함수와 같다. 즉 클로저 안에서 사용되는 변수는 안에서 호출될 때 정해진다. 그러므로 for loop가 끝나고 마지막인 3이 i값이 된다.
'Python' 카테고리의 다른 글
Python AES256 암호화, 복호화 (0) 2022.12.22 파이썬 오버로딩과 오버라이딩 (2) 2022.11.10 Python 패키징의 역사 (0) 2022.10.12 Python Celery (0) 2022.10.06 Python getter setter property (0) 2022.09.28