흔한말 (collection)/Python

파이썬 lambda 함수가 도대체 뭐에요? 몰라도 되죠?

hnanmal 2022. 3. 31. 00:29
300x250

함수를 쓰는 이유는?

함수를 쓰는 이유는 여러 가지가 있는데 그중 가장 중요한 2가지를 꼽자면 코드의 재사용성과 프로그램의 추상화를 위해 특정 역할을 하는 문맥의 개체화 입니다.

말이 어려워서 벌써 짜증 나죠?

일단 재사용성에 대해 조금 쉽게 말하자면,

한번 사용한 코드를 유사한 상황에서 계속 반복해서 사용하기 위해 함수로 저장하고, 함수의 이름으로 계속 꺼내 쓰기 용이한 성질

을 말합니다.

여기에 대해서는 "함수의 탄생: 문맥(Context)에서 기능(function)으로"라는 포스팅에서 함수가 도입되는 배경을 이미 한번 설명을 드렸으니 그 포스트를 참고해 보세요.

https://hnanmal.tistory.com/entry/%ED%95%A8%EC%88%98%EC%9D%98-%ED%83%84%EC%83%9D-%EB%AC%B8%EB%A7%A5Context%EC%97%90%EC%84%9C-%EA%B8%B0%EB%8A%A5function%EC%9C%BC%EB%A1%9C

함수의 탄생: 문맥(Context)에서 기능(function)으로

프로그래밍은 보통 리스트(로 대변되는 반복가능한 타입)를 받아서 그중 일부를 추려내고, 모든 원소에 동일한 변환작업을 한 다음 그 변환된 결과에서 어떤 의미를 추출해 내는 작업의 연속인

hnanmal.tistory.com


재사용을 위해서가 아니라면: lambda

그런데 함수를 사용하는 이유는 꼭 이런 상황만 있는 것은 아닙니다.
코드의 추상화에 대해서 따로 글을 쓰겠지만, 추상화 수준이 높으면 높을수록 여러 가지 상황에서도 대응할 수 있는 체계를 만들기 쉽거든요.
그런데 이 추상화를 하기 위해서는 함수를 인자로 받는 함수인 고계 함수(higher-order functions)를 사용해야 하는 경우가 생깁니다.
(물론 람다 함수가 이것만을 목적으로 만들어지거나 활용되지는 않습니다.)

그럴 때마다 짧은 코드들도 매번 이름을 붙인 함수를 따로 작성한 뒤에 그 함수를 고계 함수의 인자로 넣어줘야 하는 불편함이 생기는데요.
지난 포스팅에서 배웠던 filter 함수를 이용해 예제를 만들어보겠습니다.

과테말라_커피가루 = [
    {'이름': '카페인', '용해성': '수용성', '로스팅후변화': False, '함량': 10.00},
    {'이름': '광물질', '용해성': '불용성', '로스팅후변화': False, '함량': 0.10},
    {'이름': '지방', '용해성': '지용성', '로스팅후변화': False, '함량': 1.00},
    {'이름': '탄닌산', '용해성': '수용성', '로스팅후변화': False, '함량': 7.00},
    {'이름': '클로로겐산', '용해성': '수용성', '로스팅후변화': False, '함량': 3.00},
    {'이름': '트리고넬린', '용해성': '수용성', '로스팅후변화': False, '함량': 2.50},
    {'이름': '탄산가스', '용해성': '불용성', '로스팅후변화': True, '함량': 0.01},
    {'이름': '당분', '용해성': '수용성', '로스팅후변화': False, '함량': 1.50},
    {'이름': '섬유소', '용해성': '불용성', '로스팅후변화': False, '함량': 0.70},
        ]

과테말라 커피가루 내부에 수많은 성분들이 있죠?
이 성분들의 이름 중에서 3글자가 넘지않는 성분들은 성분 목록표에서 제외하고 싶다고 해봅시다.

filter함수를 사용할 건데, 이 함수는 인자로 '조건 함수(필터링 여부를 판별해주는 함수)'와 대상 목록을 받는 함수입니다.
그러니 '조건 함수'를 미리 선언해야겠죠?

def 성분이름길이가3글자이상인가(성분):
    return len(성분['이름']) > 3

조건 함수를 선언했으니 이 조건함수를 filter함수의 재료로 넣어서 코드를 짜 보겠습니다.

결과 = filter(성분이름길이가3글자이상인가, 과테말라_커피가루)

list(결과)
[{'이름': '클로로겐산', '용해성': '수용성', '로스팅후변화': False, '함량': 3.0},
 {'이름': '트리고넬린', '용해성': '수용성', '로스팅후변화': False, '함량': 2.5},
 {'이름': '탄산가스', '용해성': '불용성', '로스팅후변화': True, '함량': 0.01}]

결과가 잘 나옵니다!!
그런데 한 가지 불만이 있다면, 조건 함수의 내용이 return 문 하나로 완결되는 간단한 함수인 데다가,
함수 이름을 짓다 보니 쓸데없이 코드보다 이름이 길어서 배보다 배꼽이 더 커지는 상황이 되었다는 것이죠.

핵심적인 논리 부분은 "len(성분['이름']) > 3" 이 부분인데, 거추장스럽게 함수 선언을 하지 않고,
저 수식이 나타내는 기능 자체를 값으로 만들어 filter 함수에 전달할 수 있는 방법은 없을까요?

이미 눈치채셨겠지만, 이런 상황에 대한 해결책이 바로 람다 함수입니다.
람다 함수를 사용해서 동일한 코드를 다시 써볼게요. 이번에는 함수 선언 따위는 없습니다!

결과 = filter(lambda a: len(a['이름']) > 3, 과테말라_커피가루)

list(결과)
[{'이름': '클로로겐산', '용해성': '수용성', '로스팅후변화': False, '함량': 3.0},
 {'이름': '트리고넬린', '용해성': '수용성', '로스팅후변화': False, '함량': 2.5},
 {'이름': '탄산가스', '용해성': '불용성', '로스팅후변화': True, '함량': 0.01}]

함수 선언 없이 딱 한 줄로 코드가 완결되었습니다!!!
함수 선언을 위한 이름 짓기 과정도 없고, 핵심적인 글자 수 판별 로직이 잘 드러나도록 코드가 작성되었습니다.

lambda 함수의 형식

※ x = 함수 인자
lambda x: x를 사용한 수식


람다 함수의 형식은 위와 같습니다.
풀어서 설명하면,

  1. 람다 함수를 사용하기 위해서는 "lambda"라는 키워드를 미리 적어주고,
  2. 그다음에 한 칸 띄운 뒤에 함수의 인자로 사용될 변수를 적습니다. (ex. x, y, a, b 등등)
  3. 마지막으로 함수가 반환할 값을 만들어내는 코드를 적어주면 됩니다. (함수의 인자가 포함되지 않아도 상관은 없음)


위의 세 조건을 만족하도록 코드를 적으면 됩니다.

참고사항

이렇게 고계 함수(map, filter, reduce가 대표적입니다)의 인자가 되는 용도 말고도,
closure 구현(인자의 개수가 여러 개인 함수의 상태를 저장하는 방법)과 같은 용도로도 사용하니 람다 함수의 사용법은 꼭 익혀두세요!

반응형