TIL

TIL. 29 전역 변수, 지역변수, 클로저 알아보기

codermun 2020. 11. 6. 21:56
728x90
반응형

## 클로저를 알기 위해서는 먼저, 함수의 사용 범위를 먼저 알아보자.

## 전역 함수와 지역 함수

## 전역 변수 (global variable) == 위처럼 함수를 포함하여 스크립트 전체에서 접글 할 수 있는 변수를 전역 변수라 한다.

# 특히, 전역 번수에 접근 할 수 있는 범위를 전역 범위 (global scope)라고 한다.

x = 10 # 전역 변수
def foo():
    print(x) # 전역 변수 출력

foo()

print(x) # 전역 변수 출력
::
10

## foo 함수에 x라는 변수가 업지만, 전역 변수로 x가 있으므로 foo 함수에서도 x 변수를 사용할 수 있다.


## 지역 변수 (local variable) == 지역 변수 영역이 정해진 변수로 변수를 만든 함수 안에서만 접근이 가능하며

     함수 바깥에서는 접근 할 수없다.

# 특히, 지역 변수에 접근 할 수 있는 범위를 지역 범위 (local scope)라고 한다.

def foo():
    x = 10 # foo의 지역 변수
    print(x) # foo의 지역 변수 출력

foo()
::
10

print(x) # error . foo의 지역 변수는 출력할 수 없음

# 위의 print(x)는 error ==> 지역 변수로만 존재하는 x에 접근 할 수 없다.

## print 함수가 지역 범위에 없기 때문이다.


## 전역 변수 x 와 지역 변수 x는 같을까?

x = 10 # 전역 변수
def foo():
    x = 20 # x는 foo의 지역 변수 
    print(x) # foo의 지역 변수 출력

foo()
::
20

print(x) # 전역 변수 출력
::
10

## foo() 출력값은 지역 변수 x 값인 20을 출력하고, print(x)는 전역 변수 x값인 10을 출력한다.

## 여기서 전역 변수 x 와 지역 변수 x 는 "이름만 같을 뿐 서로 다른 변수로, 각 값은 서로 다른 메모리에 저장된다."


## 함수 안에서 전역 변수 변경하기

## 함수 안에서 전역 변수의 값을 변경할 수 있다.

# global 전역변수

x = 10 # 전역 변수
def foo():
    global x # 전역 변수 x를 사용하겠다고 설정
    x = 20 # x는 전역 변수 , x = 20이 다시 할당되는 걸로 이해
    print(x) # 전역 변수 출력

foo()
::
20
print(x) # 전역 변수 출력
::
20

## global x 라는 뜻은 여기서 부터 전역 변수 x를 사용하겠다는 뜻으로 해석해도 된다.

## 따라서, global x 를 작성한 이후의 변수 x는 전역 변수 x를 의미한다.


## 전역 변수가 없을때의 global

# 전역 변수 x가 없는 상태

def foo():
    global x # x를 전역 변수로 만듦
    x = 20 # x는 전역 변수
    print(x) # 전역 변수 출력

foo()
::
20
print(x) # 전역 변수 출력
::
20

## 전역 변수가 아무것도 없을때 global 를 사용할 경우 해당 변수는 전역 변수가 된다.

# x는 전역 변수가 된다.


## 함수 안에서 함수 만들기

## def 를 한 함수안에서 계속 사용하여 함수 안에 함수를 만들 수 있다.

# def 함수이름1():

# 코드

# def 함수이름2():

# 코드

def print_hello():
    hello = 'Hello, world!'
    def print_message():
        print(hello)
    print_message()

print_hello()
::

'Hello, world!'

## 함수 호출시 작동 순서를 본다면 // 1줄, 2줄, 5줄, 3줄, 4줄 로 표현 할 수 있다.

## 위 함수에서 보면 print_messge 함수 입장에서는 자신이 사용할 수 있는 변수 hello가 없다.

## 그런데도 error가 나지 않는 이유"바깥쪽 함수의 지역 변수는 그 안에 속한 모든 함수에서 접근할 수 있는 특징"때문이다.

 


## 바깥쪽 함수의 지연 변수를 안쪽 함수에서 변경하기

def A():
    x = 10 # A의 지역 변수 x
    def B():
        x = 20 # x에 20 할당
    B()
    print(x) # A의 지역 변수 x 출력

A()
::
10

## 위 함수에서 중요 포인트는 print가 어떤 함수에 위치해 있는가 이다.

## 얼핏 보기에는 B 함수의 지역 변수 20을 출력해야할 것 같지만,

      print 함수가 A 함수에 위치해 있기 때문에 A의 지역변수 10을 출력한다.

## 파이썬에서는 함수 에서 변수를 만들면 그 변수는 항상 해당 함수의 지역 변수가 된다.

def A():
    x = 10 # A의 지역 변수 x
    def B():
        x = 20 # x에 20 할당
        print(x)
    B()

A()
::
20

## 위와 같이 print 함수를 B 함수에 위치시키면 B의 지역변수인 20을 출력한다.


## 현재 함수에서 바깥쪽 함수의 지역 변수 변경하기

# nonlocal 지역변수

def A():
    x = 10 # A의 지역 변수 x
    def B():
        nonlocal x # 현재 함수의 바깥쪽에 있는 지역 변수 사용
        x = 20 # A의 지역 변수 x에 20 할당
    B()
    print(x) # A의 지역 변수 x 출력

A()
::
20

## nonlocal x 를 이용해 현재 함수인 B 함수에서 바깥쪽에 있는 (A함수)의 지역 변수를 변경 할 수 있다.

## global 과 비슷하게 nonlocal x라는 뜻은 여기서 부터 바깥쪽 함수의 지역 변수 x를 사용하겠다는 뜻으로 해석

## 따라서 nonlocal x 를 작성한 이후의 변수 x는 바깥쪽 함수의 지역 변수 x를 의미힌다.

## nonlocal은 현재 함수의 지역 변수가 아니라는 뜻이며 바깥쪽 함수의 지역 변수를 사용한다.


## nonlocal 의 변수를 찾는 순서

def A():
    x = 10
    y = 100
    def B():
        x = 20
        def C():
            nonlocal x # B 의 지역 함수 x = 20
            nonlocal y # A 의 지역 한수 y = 100
            x = x + 30
            y = y + 300
            print(x)
            print(y)
        C()
    B()

A()
::
50
400

## nonlocal 은 바깥쪽 지역함수를 찾을때 가장 가까운 함수에서의 지역 함수 값을 찾는다.

## 위 nonlocal y --> B의 지역 함수 y가 있었다면 B 함수의 지역 함수 y를 사용하였을것이다.

## 실무에서는 이렇게 여러 단계로 함수를 만들 일은 거의 없다.

## 그리고 함수마다 이름이 같은 변수를 사용하기 보다는 변수 이름을 다르게 짓는 것이 훨씬 좋다.


## global로 전역 변수 사용하기

x = 1
def A():
    x = 10
    def B():
        x = 20
        def C():
            global x
            x = x + 30
            print(x)
        C()
    B()
 
A()
::
31

## 함수가 몇개가있던 전역 변수 x =1 을 사용한다.

## 파이썬에서 global을 제공 "하지만" 함수에서 값을 주고받을 때는 매개변수와 반환값을 사용하는 것이 좋다.

## 특히 전역 변수는 코드가 복잡해졌을 때 변수의 값을 어디서 바꾸는지 파악하기 힘들다.

## 따라서 전역 변수는 가급적이면 사용하지 않는 것을 권장한다.

## 클로저 사용하기

## 함수를 클로저 형태로 만드는 방법

def calc():
	a = 3
	b = 5
	def mul_add(x):
		return a * x + b # 함수 바깥쪽에 있는 지역 변수 a, b를 사용하여 계산
	return mul_add # mul_add 함수를 반환

c = calc()

print(c(1), c(2), c(3), c(4), c(5))
::
8 11 14 17 20

# 함수 바깥쪽에 있는 지역 변수 a, b를 사용하여 a * x + b를 계산하는 함수 mul_add를 만든 뒤에 함수 mul_add 자체를 반환.

## 함수 mul_add를 만든 뒤, 이 함수를 바로 호출하지 않고 return으로 함수 자체를 반환

## (함수를 반환할 때는 함수 이름만 반환해야 하며 ( )(괄호)를 붙이면 안 됩니다).

c = calc()

print(c(1), c(2), c(3), c(4), c(5))
::
8 11 14 17 20

 

## 위와 같이 calc 함수를 호출한뒤 반환값을 c에 할당한다.

## calc에서 mul_add 를 반환하였으므로 c에는 mul_add가 들어가게 된다.

## 따라서 c(인수) 형태로 호출해보면 mul_add의 a*xb의 계산식을 사용할 수 있게된다.

## 또한, 자세히 보면 함수 c = calc() 에서 calc 함수는 1번 사용하고 끝났음에도 불구하고

## c 는 calc의 지역함수 a, b 를 계속 사용하는 것을 볼 수 있다.

## 이렇게 함수를 둘러싼 환경(지역 변수, 코드 등)을 계속 유지하다가,

## 함수를 호출할 때 다시 꺼내서 사용하는 함수를 클로저(closure)라고 한다.

## 여기서는 c에 저장된 함수가 클로저이다.

 


## 클로저를 사용하는 이유

# 1. 지역 변수와 코드를 묶어서 사용하고 싶을 때 활용한다

# 2. 또한, 클로저에 속한 지역 변수는 바깥에서 직접 접근할 수 없으므로 데이터를 숨기고 싶을 때 활용.

추가로 클로저를 사용하면 프로그램의 흐름을 변수에 저장 할 수 있다


## lambda로 클로저 만들기

def calc():
	a = 3
	b = 5
	return lambda x: a * x + b # 람다 표현식을 반환

c = calc()

print(c(1), c(2), c(3), c(4), c(5))
::
8 11 14 17 20

## 람다 표현식 자체를 반환한것으로 람다 표현식을 이용하면 좀더 간단하게 코드를 작성할 수 있다.

## 보통 클로저는 람다 표현식과 함께 사용하는 경우가 많아 둘을 혼동하기 쉬운데

## 람다는 이름이 없는 익명 함수를 뜻하고, 클로저는 함수를 둘러싼 환경을 유지했다가 나중에 다시 사용하는 함수를 뜻한다.


## 클로저의 지역 변수 변경하기

def calc():
	a = 3
	b = 5
	total = 0
	def mul_add(x):
		nonlocal total
		total = total + a * x + b
		print(total)
	return mul_add #함수를 바로 호출하지 않고 리턴으로 함수 자체를 반환함 () 사용하면 안됨.

c = calc()

c(1)
# 8
c(2)
# 19
c(5)
# 39

## 이전 식에서는 클로저의 지역변수를 가져오기만 하였다면, nonlocal을 통해 클로저의 지역 변수를 변경할 수 있다.

## 클로저(여기서는 calc)의 지역 변수 total = 0을 할당하였다.

## nonlocal total을 통해 바깥쪽 함수의 지역 변수 total을 사용하겠다는 의미로

## total = total + a*x +b로 변경이 가능하다.

## 다음은 a * x + b의 결과를 함수 calc의 지역 변수 total에 누적한다.

## total = total + a*x + b 에서 오른쪽의 total을 삭제하면 값이 누적되지 않는다.

# 클로저는 다소 어려운 개념이므로 지금 당장 완벽하게 이해하지 않아도, 나중에 파이썬에 익숙해지면 자연스럽게 익히게 된다고 한다.





 

 

728x90
반응형