profile image

L o a d i n g . . .

unit 32. 람다 표현식 사용하기

 

  • 람다 표현식으로 함수 만들기

람다 표현식은 함수를 간편하게 작성할 수 있어서 다른 함수의 인수로 넣을 때 주로 사용한다.

 

람다 표현식은 다음과 같이 lambda에 매개변수를 지정하고 :(콜론) 뒤에 반환값으로 사용할 식을 지정한다.

- lambda 매개변수들: 식

 

 

다음은 매개변수 x에 10을 더한 값을 반환하는 람다표현식이다.

>>> lambda x: x + 10
<function <lambda> at 0x02C27270>

실행을 해보면 함수 객체가 나오는데, 이 상태로는 함수를 호출할 수 없다.

왜냐하면 람다 표현식은 이름이 없는 함수(익명 함수)를 만들기 때문이다. 

따라서 lambda로 만든 익명 함수를 호출하려면 다음과 같이 람다 표현식을 변수에 할당해주면 된다.

>>> plus_ten = lambda x: x + 10
>>> plus_ten(1)
11

lambda x: x + 10은 매개변수 x 하나를 받고, x에 10을 더해서 반환한다. 즉, 매개변수, 연산자, 값 등을 조합한 식으로 반환값을 만드는 방식이다.

 

 

* 람다 표현식 자체를 할당하기

람다 표현식은 변수에 할당하지 않고 람다 표현식 자체를 바로 호출할 수 있다.

다음과 같이 람다 표현식을 ( )(괄호)로 묶은 뒤에 다시 ( )를 붙이고 인수를 넣어서 호출하면 된다.

 

- (lambda 매개변수들: 식)(인수들)

>>> (lambda x: x + 10)(1)
11

 

 

* 주의

주의할 점은 람다표현식 안에서는 새 변수를 만들 수 없다는 점이다.

따라서 변수가 필요한 코드일 경우에는 def로 함수를 작성하는 것이 좋다.

>>> (lambda x: y = 10; x + y)(1)
SyntaxError: invalid syntax

단, 람다 표현식 바깥에 있는 변수는 사용할 수 있다.

 

 

* 람다 표현식을 인수로 사용하기

람다 표현식을 사용하는 이유는 함수의 인수 부분에서 간단하게 함수를 만들기 위해서다.

 

이런 방식으로 사용하는 대표적인 예가 map이다.

람다 표현식으로 함수를 만들어서 map에 넣어보면 다음과 같다.

>>> list(map(lambda x: x + 10, [1, 2, 3]))
[11, 12, 13]

람다 표현식이 아닌 def 형식이었으면 더 길어졌을 코드가 람다 표현식 lambda x: x + 10 한줄로 해결된다.

참고) map을 사용할 때 자료형뿐만 아니라 함수를 직접 만들어서 넣어도 됨.

 

 

  • 람다 표현식과 map, filter, reduce 함수 활용하기

더보기

* 람다 표현식에 조건부 표현식 사용하기

람다 표현식에서 조건부 표현식을 사용하는 방법은 다음과 같다.

- lambda 매개변수들: 식1 if 조건식 else 식2

 

다음은 map을 사용하여 리스트 a에서 3의 배수를 문자열로 변환한다.

>>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> list(map(lambda x: str(x) if x % 3 == 0 else x, a))
[1, 2, '3', 4, 5, '6', 7, 8, '9', 10]

여기서는 요소가 3의 배수일 때는 str(x)로 요소를 문자열로 만들어서 반환했고, 3의 배수가 아닐 때는 x로 요소를 그대로 반환한다.

 

람다 표현식 안에서 조건부 표현식 if, else를 사용할 때는 :(콜론)을 붙이지 않는다. 즉, 일반적인 if, else와 문법이 다르다.

조건부 표현식은 식1 if 조건식 else 식2 형식으로 사용하며 식1은 조건식이 참일 때, 식2는 조건식이 거짓일 때 사용할 식이다.

특히 람다 표현식에서 if를 사용했다면 반드시 else를 사용해야 하며, 만약 if만 사용하면 문법 에러가 발생한다.

 

그리고 람다 표현식 안에서는 elif를 사용할 수 없다. 따라서 조건부 표현식은 식1 if 조건식1 else 식2 if 조건식2 else 식3 형식처럼 if를 연속으로 사용해야 한다.

예를 들어 리스트에서 1은 문자열로 변환하고, 2는 실수로 변환, 3 이상은 10을 더하는 식은 다음과 같이 만든다.

>>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> list(map(lambda x: str(x) if x == 1 else float(x) if x == 2 else x + 10, a))
['1', 2.0, 13, 14, 15, 16, 17, 18, 19, 20]

별로 복잡하지 않은 조건인데도 알아보기가 힘드므로, 이런 경우에는 람다 표현식을 사용하기 보다 def로 함수를 만들고 if, elif, else를 사용하는 것이 낫다.

 

* map에 객체를 여러 개 넣기

map은 리스트 등의 반복 가능한 객체를 여러 개 넣을 수 있다.

다음은 두 리스트의 요소를 곱해서 새 리스트를 만든다.

>>> a = [1, 2, 3, 4, 5]
>>> b = [2, 4, 6, 8, 10]
>>> list(map(lambda x, y: x * y, a, b))
[2, 8, 18, 32, 50]

람다 표현식의 매개변수 개수에 맞게 반복 가능한 객체도 콤마로 구분해서 넣어주면 됨.

 

* filter 사용하기

filter는 반복 가능한 객체에서 특정 조건에 맞는 요소만 가져오는데, 

filter에 지정한 함수의 반환값이 True일 때만 해당 요소를 가져온다.

- filter(함수, 반복가능한객체)

 

다음은 람다표현식을 filter에 넣은 것으로, 리스트에서 5보다 크면서 10보다 작은 숫자를 가져온다.

>>> a = [8, 3, 2, 10, 15, 7, 1, 9, 0, 11]
>>> list(filter(lambda x: x > 5 and x < 10, a))
[8, 7, 9]

 

 

* reduce 사용하기

reduce반복 가능한 객체의 각 요소를 지정된 함수로 처리한 뒤 이전 결과와 누적해서 반환하는 함수이다.(reduce는 파이썬 3부터 내장 함수가 아니라서, functools 모듈에서 reduce 함수를 가져와야 함.)

 

- from functools import reduce

- reduce(함수, 반복가능한객체)

 

다음은 람다 표현식을 reduce에 넣은 것으로, 리스트에 저장된 요소를 순서대로 더한 뒤 누적된 결과를 반환한다.

>>> a = [1, 2, 3, 4, 5]
>>> from functools import reduce
>>> reduce(lambda x, y: x + y, a)
15

 

 

 

 

unit 33. 클로저 사용하기

 

  • 변수의 사용 범위 알아보기

더보기

전역 변수란? 함수를 포함하여 스크립트 전체에서 접근할 수 있는 변수를 뜻한다.

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

 

다음은 전역 변수 x를 출력하는 코드이다.

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

전역변수 x foo 함수 내부에서도 그리고 함수 바깥에서도 접근할 수 있다.

 

그러나 반대로 변수 x를 함수 foo 내부에 만들면 에러가 발생한다.

def foo():
    x = 10      # foo의 지역 변수
    print(x)    # foo의 지역 변수 출력
 
foo()
print(x)        # 에러. foo의 지역 변수는 출력할 수 없음

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

지역 변수는 변수를 만든 함수 안에서만 접근할 수 있고, 함수 바깥에서는 접근할 수 없다. 특히 지역 변수를 접근할 수 있는 범위를 지역 범위(local scope)라고 한다.

 

 

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

다음은 함수 안에서 전역 변수의 값을 변경하고자 한다.

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

실행 결과는 다음과 같다.

20
10

그런데 함수 foo 안에서는 x의 값이 20으로 변경되었지만, 함수 바깥에서 print x의 값을 출력해보면 10이 나온다.

즉, 겉으로 보기에는 foo 안의 x는 전역 변수인 것 같지만 실제로는 foo의 지역 변수인 것이다. 전역 변수 x가 있고, foo에서 지역 변수 x를 새로 만든 것으로, 이 둘은 이름만 같을 뿐 서로 다른 변수이다.

 

따라서 함수 안에서 전역 변수의 값을 변경하려면 global 키워드를 사용해야한다. 

- global 전역변수

x = 10          # 전역 변수
def foo():
    global x    # 전역 변수 x를 사용하겠다고 설정
    x = 20      # x는 전역 변수
    print(x)    # 전역 변수 출력
 
foo()
print(x)        # 전역 변수 출력

이제 함수 안에서 x를 20으로 변경하면 함수 바깥에서 x를 출력했을 때 20이 나온다. 

 

  • 함수 안에서 함수 만들기

더보기

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

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

 

다음은 함수 안에서 문자열을 출력하는 함수이다.

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

함수 print_hello 안에서 다시 def로 함수 print_message를 만들었다. 그리고 print_hello 안에서 print_message()처럼 함수를 호출하였다. 하지만 아직 함수를 정의만 한 상태이므로 아무것도 출력되지 않는다.

두 함수가 실제로 동작하려면 바깥쪽에 있는 print_hello를 호출해주어야 한다.

즉, print_hello > print_message 순으로 실행된다.

 

 

* 지역 변수의 범위

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

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

 

* 지역 변수 변경하기

단, 바깥쪽 함수의 지역 변수를 안쪽 함수에서 변경할 땐 다르다.

다음은 안쪽 함수 B에서 바깥쪽 함수 A의 지역 변수 x를 변경한다.

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

실행을 해보면 20이 나와야 할 것 같은데 10이 나온다.

왜냐하면 겉으로 보기에는 바깥쪽 함수 A의 지역 변수 x를 변경하는 것 같지만, 실제로는 안쪽 함수 B에서 이름이 같은 지역 변수 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를 사용하게 된다.

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

 

 

* global로 전역변수 사용하기

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

 

그러나, 전역 변수는 코드가 복잡해졌을 때 변수의 값을 어디서 바꾸는지 알기가 힘들기에 전역 변수는 가급적이면 사용하지 않는 것이 낫다.

 

  • 클로저 사용하기

더보기

다음은 함수 바깥쪽에 있는 지역 변수 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

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

그리고 바깥에서 함수 calc를 호출한 뒤 반환값을 c에 저장한다. calc에서 mul_add를 반환했으므로 c에는 함수 mul_add가 들어가고, c에 숫자를 넣어서 호출해보면 a * x + b 계산식에 따라 값이 출력된다.

c = calc()
print(c(1), c(2), c(3), c(4), c(5))    # 8 11 14 17 20

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

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

 

클로저를 사용하면 프로그램의 흐름을 변수에 저장할 수 있다. 즉, 클로저는 지역 변수와 코드를 묶어서 사용하고 싶을 때 활용한다.

 

* 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))

람다를 사용하면 클로저를 좀 더 간단하게 만들 수 있다. return lambda x: a * x + b처럼 람다 표현식을 만든 뒤 람다 표현식 자체를 반환하면 된다.

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

 

 

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

클로저의 지역 변수를 변경하고 싶다면 nonlocal을 사용하면 된다. 

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)
c(2)
c(3)

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

 

 

복사했습니다!