클로저 사용하기

Jeongyun Heo·2021년 1월 15일
0

Python

목록 보기
33/36
post-thumbnail

파이썬 코딩 도장 - 33.1 변수의 사용 범위 알아보기
https://dojang.io/mod/page/view.php?id=2364

클로저 사용하기

이번에는 변수의 사용 범위함수를 클로저 형태로 만드는 방법을 알아보자.

변수의 사용 범위 알아보기

파이썬 스크립트에서 변수를 만들면 다음과 같이 함수 안에서도 사용할 수 있다.

x = 10


def foo():
    print(x)


foo()
print(x)

-------------------
10
10

함수를 포함하여 스크립트 전체에서 접근할 수 있는 변수를 전역 변수(global variable)라고 부른다.

전역 변수에 접근할 수 있는 범위를 전역 범위(global scope)라고 한다.

이번에는 변수 x를 함수 foo 안에서 만들어보자.

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


foo()
print(x)        # 에러. foo의 지역 변수는 출력할 수 없음.
---------------
10
Traceback (most recent call last):
  File "main.py", line 7, in <module>
    print(x)    # 에러. foo의 지역 변수는 출력할 수 없음.
NameError: name 'x' is not defined

실행해보면 x가 정의되지 않았다는 에러가 발생한다.

왜냐하면 변수 x는 함수 foo 안에서 만들었기 때문에 foo의 지역 변수(local variable)이다.

지역 변수는 변수를 만든 함수 안에서만 접근할 수 있고, 함수 바깥에서는 접근할 수 없다.

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

함수 안에서 함수 만들기

파이썬 코딩 도장 - 33.2 함수 안에서 함수 만들기
https://dojang.io/mod/page/view.php?id=2365

이번에는 함수 안에서 함수를 만드는 방법을 알아보자.

다음과 같이 def로 함수를 만들고 그 안에서 다시 def로 함수를 만들면 된다.

def 함수이름1():
    코드
    def 함수이름2():
        코드

간단하게 함수 안에서 문자열을 출력하는 함수를 만들고 호출해보자.

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

    def print_message():
        print(hello)

    print_message()   # 바깥쪽 함수 안에서 안쪽 함수를 호출


print_hello()  # 바깥에서 함수를 실행해본다.
-------------------------------
Hello, world!

함수 print_hello 안에 다시 함수 print_message를 만들었다.

그리고 함수 print_hello 안에서 print_message()처럼 함수를 호출했다.

두 함수가 실제로 동작하는지 확인하기 위해 바깥에서 함수를 실행해본다.

함수는 print_hello > print_message 순으로 실행된다.

지역 변수의 범위

print_hello 함수와 print_message 함수에서 지역 변수의 범위를 살펴보자.

안쪽 함수 print_message에서는 바깥쪽 함수 print_hello의 지역 변수 hello를 사용할 수 있다.

def print_hello():           # 바깥쪽 함수
    hello = 'Hello, world!'  # 바깥쪽 함수의 지역 변수

    def print_message():     # 안쪽 함수
        print(hello)         # 바깥쪽 함수의 지역 변수 사용 가능

    print_message()

즉, 바깥쪽 함수의 지역 변수는 그 안에 속한 모든 함수에서 접근할 수 있다.

지역 변수 변경하기

지금까지 바깥쪽 함수의 지역 변수를 안쪽 함수에서 사용해봤다.

바깥쪽 함수의 지역 변수를 안쪽 함수에서 변경하면 어떻게 될까?

다음과 같이 안쪽 함수 B에서 바깥쪽 함수 A의 지역 변수 x를 변경해보자.

def A():
    x = 10        # A의 지역 변수 x

    def B():
        x = 20    # x에 20 할당

    B()           
    print(x)      # A의 지역 변수 x 출력


A()
-------------------
10

20이 나올 줄 알았는데 10이 나왔다....🙀

보기에는 바깥쪽 함수 A의 지역 변수 x를 변경하는 거 같지만,
실제로는 안쪽 함수 B에서 이름이 같은 지역 변수 x를 새로 만들게 된다.

즉, 파이썬에서는 함수에서 변수를 만들면 항상 현재 함수의 지역 변수가 된다.

def A():
    x = 10       # A의 지역 변수 x

    def B():
        x = 20   # B의 지역 변수 x를 새로 만듦

현재 함수의 바깥쪽에 있는 지역 변수의 값을 변경하려면 nonlocal 키워드를 사용해야 한다.

다음과 같이 함수 안에서 nonlocal에 지역 변수의 이름을 지정해준다.

def A():
    x = 10            # A의 지역 변수 x

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

    B()
    print(x)          # A의 지역 변수 x 출력


A()
-------------------------------------------------------------
20

이제 함수 B에서 함수 A의 지역 변수 x를 변경할 수 있다.

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

nonlocal이 변수를 찾는 순서

nonlocal은 현재 함수의 바깥쪽에 있는 지역 변수를 찾을 때 가장 가까운 함수부터 먼저 찾는다.

이번에는 함수의 단계를 A, B, C로 만들어 보았다.

def A():
    x = 10
    y = 100

    def B():
        x = 20

        def C():
            nonlocal x
            nonlocal y
            x = x + 30
            y = y + 300
            print(x)
            print(y)

        C()

    B()


A()
------------------------------
50
400

함수 C에서 nonlocal x를 사용하면 바깥쪽에 있는 함수 B의 지역 변수 x = 20을 사용하게 된다.

따라서 x = x + 30은 50이 나온다.

그리고 함수 C에서 nonlocal y를 사용하면 바깥쪽에 있는 함수의 지역 변수 y를 사용해야 하는데 함수 B에는 y가 없다.

이때는 한 단계 더 바깥으로 나가서 함수 A의 지역 변수 y를 사용하게 된다.

즉, 가까운 함수부터 지역 변수를 찾고, 지역 변수가 없으면 계속 바깥쪽으로 나가서 찾는다.

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

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

global로 전역 변수 사용하기

함수가 몇 단계든 상관없이 global 키워드를 사용하면 무조건 전역 변수를 사용하게 된다.

x = 1     # 전역 변수


def A():
    x = 10

    def B():
        x = 20

        def C():
            global x     # 전역 변수 사용
            x = x + 30
            print(x)

        C()

    B()


A()

함수 C에서 global x를 사용하면 전역 변수 x = 1을 사용하게 된다.
따라서 x = x + 30은 31이 나온다.

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

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

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

클로저 사용하기

파이썬 코딩 도장 - 33.3 클로저 사용하기
https://dojang.io/mod/page/view.php?id=2366

이제 함수를 클로저 형태로 만드는 방법을 알아보자.

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

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

👇  먼저 calc에 지역 변수 a와 b를 만들고 3과 5를 저장했다.

👇  그 다음에 함수 mul_add에서 a와 b를 사용하여 a * x + b를 계산한 뒤 반환한다.

def calc():
    a = 3
    b = 5

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

👇  함수 mul_add를 만든 뒤에는 이 함수를 바로 호출하지 않고 return으로 함수 자체를 반환한다.

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

    return mul_add     # mul_add 함수를 반환. 괄호 붙이면 안 됨.

이제 클로저를 사용해보자.

👇  다음과 같이 함수 calc를 호출한 뒤 반환값을 c에 저장한다.

👇   calc에서 mul_add를 반환했으므로 c에는 함수 mul_add가 들어간다.

👇   그리고 c에 숫자를 넣어서 호출해보면 a * x + b 계산식에 따라 값이 출력된다.

c = calc()  # c(변수)에 저장된 함수: 클로저
print(c(1), c(2), c(3), c(4), c(5))
------------------------------------
8 11 14 17 20

잘 보면 함수 calc가 끝났는데도 c는 calc의 지역 변수 a, b를 사용해서 계산을 하고 있다.

이렇게 함수를 둘러싼 환경(지역 변수, 코드 등)을 계속 유지하다가, 함수를 호출할 때 다시 꺼내서 사용하는 함수클로저(closure)라고 한다.

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

이처럼 클로저를 사용하면 프로그램의 흐름을 변수에 저장할 수 있다.

즉, 클로저는 지역 변수와 코드를 묶어서 사용하고 싶을 때 활용한다.

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

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

지금까지 클로저의 지역 변수를 가져오기만 했는데, 클로저의 지역 변수를 변경하고 싶다면 nonlocal을 사용하면 된다.

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

def calc():
    a = 3
    b = 5
    total = 0        # 지역 변수 total

    def mul_add(x):
        nonlocal total      # nonlocal 사용
        total = total + a * x + b     # a * x + b 결괏값 누적, total += a * x + b 써도 됨
        print(total)

    return mul_add


c = calc()
c(1)
c(2)
c(3)
-------------------------------------
8
19
33

지금까지 전역 변수, 지역 변수, 변수의 범위, 클로저에 대해 알아보았다.

0개의 댓글