Henu
개발냥발
Henu
전체 방문자
오늘
어제
  • 분류 전체보기 (411)
    • DevOps (52)
      • Kubernetes (19)
      • Docker (14)
      • AWS (3)
      • Nginx (4)
      • Linux (4)
      • ArgoCD (1)
      • CN (2)
      • NATS (0)
      • Git (5)
    • Back-End (30)
      • Django (18)
      • Spring (5)
      • JPA (1)
      • MSA (5)
    • CS (87)
      • SystemSoftware (20)
      • OS (25)
      • Computer Architecture (16)
      • Network (23)
      • Database (2)
    • Lang (21)
      • Java (9)
      • Python (4)
      • C# (8)
    • Life (12)
    • 블록체인 (2)
    • Algorithm (204)
      • BOJ (160)
      • 프로그래머스 (19)
      • LeetCode (4)
      • SWEA (1)
      • 알고리즘 문제 해결 전략 (8)
      • DS, algorithms (7)
      • Checkio (5)
    • IT (2)

블로그 메뉴

  • GitHub
  • 글쓰기
  • 관리자

공지사항

  • Free!

인기 글

태그

  • Network
  • BFS
  • 백트래킹
  • boj
  • docker
  • Kubernetes
  • DFS
  • django
  • 프로그래머스
  • 다이나믹 프로그래밍

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Henu

개발냥발

Lang/Python

[Python] Closure (클로저)

2021. 12. 2. 17:33

클로저를 이해하기 위해서는 일급 함수, 일급 객체에 대한 이해가 필요하다.

만약 일급함수, 일급객체를 모른다면 아래 포스팅이 도움이 될 수 있다.

 

[Python] First-Class Function (일급 함수)

프로그래밍 언어가 함수를 일급 객체로 취급하는 경우 일급 함수를 지원한다고 한다. Python 은 일급 함수를 지원한다. (Java의 함수는 1급 객체가 아니다. Kotlin의 함수는 1급 객체라고 한다.) 일급

hyeo-noo.tistory.com

 

클로저란?

클로저는 외부 함수에 접근할 수 있는 내부 함수 혹은 이러한 원리를 칭하는 용어이다.

외부 함수는 외부 함수 자신의 지역변수를 사용하는 내부함수가 소멸될때까지 소멸되지 않는다.

예를 들어 임의의 A함수 내부에 다른 B함수가 있다면 B함수는 A함수의 지역변수를 사용할 수 있지만 그 반대는 불가능하다.

 

 

1. 클로저 실행 순서

def add(x, y):	#1
    return x + y

def create_calc(func, x):	#2
    offset = 20	#4
    
    def calc(y):	#5
        y += offset	#7
        return func(x, y)	#8
        
    return calc	#6

newadder = create_calc(add, 15)	#3

# result = 55
print(newadder(20))	#9
  1. add 함수가 정의된다.
  2. create_calc 함수가 정의된다.
  3. newadder 변수에 create_calc 함수를 할당한다.
  4. create_calc 함수 내부의 지역변수 offset 변수가 정의된다.
  5. calc 함수를 정의한다.
  6. calc 를 리턴한다.
  7. calc가 받은 매개변수(지역변수가 됨)의 값을 offset만큼 더한다.
  8. func를 리턴한다.
  9. newadder(20)의 값을 print 한다.

 

#3, #7, #8에서 에서 아주 중요한 클로저의 특징이 드러난다.

함수는 실행이 될 때 stack메모리 상에 메모리를 할당 받고, 실행이 종료되면 메모리를 반환하는 구조이다.

따라서 다음과 같은 의문점이 생길 수 있다.

1. #3에서 create_calc함수가 할당됨과 동시에 create_calc함수를 사용했기 때문에, create_calc함수는 소멸되어야 하지 않는가?

2. create_calc함수가 소멸된다면 newadder를 실행할 때 calc 함수가 실행 될 것이고, calc함수에서 사용하는 offset과 func는 사용할 수 없을 것이다. 그렇다면 newadder는 어떻게 실행되는가?

 

파이썬은 일급 함수를 지원하는 언어이기 때문에 create_calc 함수를 '클로저'로 인식하고 특별한 동작을 할 수 있게 도와준다.

따라서 아래 클로저 메모리 구조를 살펴보고 어떻게 create_calc 함수 내부의 지역변수들이 살아있을 수 있는지 알아보자

 

2. 클로저 메모리 구조 파헤치기

def add(x, y):
    return x + y

def mul(x, y):
    return x * y

def create_calc(func, x):
    offset = 20
    def calc(y):
        y += offset
        return func(x, y)
    return calc

newadder = create_calc(add, 15)
newmuler = create_calc(mul, 10)

print("location of add() :", add)
print("location of mul() :", mul)
print("location of a create_calc :", create_calc)
print("location of newadder :", newadder)
print("location of newmuler :", newmuler)
print()
print(dir(create_calc))
print()
print(newadder.__closure__)
print()
print(dir(newadder.__closure__[0]))
print()
print(newadder.__closure__[0].cell_contents)
print()
print(newmuler.__closure__)
print()
print(newmuler.__closure__[0].cell_contents)
print()

 

result:

location of add() : <function add at 0x7fd4e80cef70>

location of mul() : <function mul at 0x7fd4d8208040>

location of a create_calc : <function create_calc at 0x7fd4d82080d0>

location of newadder : <function create_calc.<locals>.calc at 0x7fd4d8208160>
location of newmuler : <function create_calc.<locals>.calc at 0x7fd4d82081f0>

['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

newadder closure : (<cell at 0x7fd4d8206fd0: function object at 0x7fd4e80cef70>, <cell at 0x7fd4d8206fa0: int object at 0x7fd4e802eb90>, <cell at 0x7fd4d8206f70: int object at 0x7fd4e802eaf0>)

['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'cell_contents']

<function add at 0x7fd4e80cef70>

newmuler closure : (<cell at 0x7fd4d8206e50: function object at 0x7fd4d8208040>, <cell at 0x7fd4d8206df0: int object at 0x7fd4e802eb90>, <cell at 0x7fd4d8206dc0: int object at 0x7fd4e802ea50>)

<function mul at 0x7fd4d8208040>

newadder closure는 tuple 형식으로 되어있음을 확인할 수 있다.

 

newadder closure를 살펴보자

create_calc에는 지역변수라고 할 수 있는게 3가지가 있다.

  1. offset
  2. func
  3. y

 

따라서 closure tuple에는 3개의 변수가 담겨있는것을 newadder closure에서 확인할 수 있다.

 

[0] : <cell at 0x7fd4d8206fd0: function object at 0x7fd4e80cef70>

 

 

 - 메모리의 0x7fd4d8206fd0 부분에 위치한 클로저 cell은 0x7fd4e80cef70에 위치하는 function object를 참조한다. 

즉 add 함수를 참조한다는 뜻이다. 이는 add 함수의 location과 비교해 보면 알 수 있다.

 

[1] : <cell at 0x7fd4d8206fa0: int object at 0x7fd4e802eb90>

- offset 변수가 저장된 위치를 참조하는 cell이 지정되었다

 

[2] : <cell at 0x7fd4d8206f70: int object at 0x7fd4e802eaf0>

- y 변수가 저장된 위치를 참조하는 cell이 지정되었다.

 

 

그리고 closure의 dir()을 보면 'cell_contents'라는 명령어가 있다. 이 명령어는 클로저가 리턴하는 값을 리턴한다.

newadder의 0번째 cell의 cell_contents를 살펴보면 <function add at 0x7fd4e80cef70> 라고 적혀있다.

이는 add 함수를 의미하는 것으로, calc 함수 내부의 리턴값과 동일함을 확인할 수 있다.

 

클로저 cell이라는 개념을 통해서 외부 함수가 어떻게 내부함수가 종료될 때까지 자신의 지역변수값을 유지할 수 있는지 이해할 수 있었다.

'Lang > Python' 카테고리의 다른 글

[Python] Decorator (데코레이터)  (0) 2021.12.03
[Python] First-Class Function (일급 함수)  (2) 2021.12.02
Tkinter 로 계산기 만들기  (0) 2020.06.18
    'Lang/Python' 카테고리의 다른 글
    • [Python] Decorator (데코레이터)
    • [Python] First-Class Function (일급 함수)
    • Tkinter 로 계산기 만들기

    티스토리툴바