콘텐츠로 건너뛰기

함수를 매개변수로 하는 함수, map/filter 내장함수 및 람다함수

1. 함수를 매개변수로 하는 함수

def call_10_times(func):
    for i in range(10):
        func()

def func():
    print("안녕")

call_10_times(func)

def func()print_hello()로 변경하는게 네이밍 규칙에 따르면 올바른 방법이다.

def call_10_times(func):
    for i in range(10):
        func()

def print_hello():
    print("안녕")

call_10_times(print_hello)

윗 코드를 보면 funcion print_hello()에서 괄호를 빼고 call_10_times(print_hello)라고 호출했다. 만일 괄호를 넣어서 호출하면 어떨까? 당연히 에러가 난다.

안녕

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[25], line 8
      5 def print_hello():
      6     print("안녕")
----> 8 call_10_times(print_hello())

Cell In[25], line 3, in call_10_times(func)
      1 def call_10_times(func):
      2     for i in range(10):
----> 3         func()

TypeError: 'NoneType' object is not callable

또한 ‘안녕’이라는 글자 호출 후 에러를 낸다. 이유는 def print_hello()가 먼저 한 번 호출되어서 그 결과를 찍었기 때문이다.

이러한 개념은 콜백함수와 연결된다. 콜백은 내가 함수를 연결해 놓을 테니, 나중에 호출하면 실행해줘라는 뜻이다. 그래서 cv2.setMouseCallback(window, on_mouse)와 같이 콜백함수를 연결한 모습을 종종 보았던 것이다.

2. map, filter 함수

콜백함수는 나중에 한 번 더 다루기로 하고, 이제는 파이썬 내장함수인 map, filter 함수에 대해서 알아보겠다. 일단, 아래의 포스팅을 한 번 읽어보자.

윗 포스팅에서 제곱 구하기 함수를 정의한 바 있다. 이건 어떤 함수를 매핑(mapping)한 것과 같은 의미이다. 그래서 이런 것을 구할 때 명시적으로 매핑한다는 것을 강조하면서 표준화된 방법으로 찾아보자라고 해서 나온것이 map 내장 함수이다.

일단 개념만 가지고 짜면 아래처럼 된다.

def my_power(list_x):
    return [i*i for i in range list_x]    

list_a=[1,2,3,4,5,6]
map(my_power, list_a)

--------------------------------
<map object at 0x7f85106c9780>

얼추 맞는것 같다. list()가 필요한것 같다.

def my_power(list_x):
    return [i*i for i in list_x]
    
list_a=[1,2,3,4,5,6]
print(list(map(my_power, list_a)))


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[10], line 5
      2     return [i*i for i in list_x]
      4 list_a=[1,2,3,4,5,6]
----> 5 print(list(map(my_power, list_a)))

Cell In[10], line 2, in my_power(list_x)
      1 def my_power(list_x):
----> 2     return [i*i for i in list_x]

TypeError: 'int' object is not iterable

음… map에 대해서 공부를 해야 겠다.

class map(
    func: (_T1@__new__) -> _S@map, #func를 인자로 받고
    iterable: Iterable[_T1@__new__], #이터러블이 여기 들어와야 된다.
    /
)
map(func, *iterables) --> map object #반환은 map object

Make an iterator that computes the function using arguments from
each of the iterables. Stops when the shortest iterable is exhausted.
#"각 반복 가능 객체(iterables)로부터 인자들을 가져와 함수를 계산하는 이터레이터를 만듭니다. 인자로 전달된 객체 중 가장 짧은 객체가 소진되면 동작을 멈춥니다."

map 함수는 이터러블을 직접 하나씩 풀어서 계산하므로 리스트 컴프리핸션이 필요 없었다. 그럼 코드를 다시 짜 보자.

def my_power(x): # 리스트가 아니라 숫자 하나(x)를 받음
    return x * x

list_a = [1, 2, 3, 4, 5, 6]

# list_a의 각 요소를 my_power에 하나씩 적용하고 리스트로 변환
result = list(map(my_power, list_a))
print(result) # [1, 4, 9, 16, 25, 36]

함수의 정의가 C/C++ 처럼 명료하다. 또한 함수의 사상(mapping)이라는 의미를 가진, map 함수의 기능도 이해가 되었다.단, 마지막에 list()로 한 번 더 처리해 주어야 map 반환값이 리스트로 변경되는 것에 주의한다.

map 함수를 이해했다면 filter 함수도 쉽게 이해할 수 있다.

result에서 10 이하의 값만 뽑아보자.

def my_power(x): # 리스트가 아니라 숫자 하나(x)를 받음
    return x * x

def under_10(x):
    return([x if x < 10])

list_a = [1, 2, 3, 4, 5, 6]

# list_a의 각 요소를 my_power에 하나씩 적용하고 리스트로 변환
result = list(map(my_power, list_a))
filtered_result = list(filter(under_10, result))

print(result)
print(filtered_result)

---------------------------------------------------------
Cell In[13], line 5
    return([x if x < 10])
            ^
SyntaxError: expected 'else' after 'if' expression

쉽지 않다. 리스트 컴프리핸션까지 했는데… else는 왜 넣으란 거지?

filter 함수 사용법을 읽어 보니 이넘도 각각의 요소에 대해서 조건을 테스트 후, true면 그 요소를 filtered object로 저장하는 함수다. 요렇게 짜면 된다.

def my_power(x): # 리스트가 아니라 숫자 하나(x)를 받음
    return x * x

def under_10(x):
    return(x < 10)

list_a = [1, 2, 3, 4, 5, 6]

# list_a의 각 요소를 my_power에 하나씩 적용, 리스트로 변환
result = list(map(my_power, list_a))
# result의 각 요소를 테스트해서 true인 것 만 리스트로 변환
filtered_result = list(filter(under_10, result))

print(result)
print(filtered_result)

여기까지 했으면, map과 filter 함수에 대해서는 감이 왔을 것 같다.

3. 람다함수(lambda)

이제는 람다함수에 대해서 알아보자. 위에서 def로 2 개의 함수를 정의했다. 이것을 이렇게 나타내 보겠다.

my_power = lambda x: x*x
under_10 = lambda x: x<10

list_a = [1, 2, 3, 4, 5, 6]

# list_a의 각 요소를 my_power에 하나씩 적용, 리스트로 변환
result = list(map(my_power, list_a))
# result의 각 요소를 테스트해서 true인 것 만 리스트로 변환
filtered_result = list(filter(under_10, result))

print(result)
print(filtered_result)

윗 코드는 동작하는 코드다. 그리고 위에서 함수 정의부를 각 라인으로 이동시키면 아랫처럼 쓸 수 있다.

list_a = [1, 2, 3, 4, 5, 6]

# list_a의 각 요소를 my_power에 하나씩 적용, 리스트로 변환
result = list(map(lambda x: x*x, list_a))
# result의 각 요소를 테스트해서 true인 것 만 리스트로 변환
filtered_result = list(filter(lambda x: x<10, result))

print(result)
print(filtered_result)

이렇게 하면 함수의 원형을 찾으로 위로 올라가서 확인할 필요가 없으며 함수명에 대해서 고심할 필요가 없게 된다. 이제 lambda라는 코드를 보면 함수의 원형이 머릿속에 그려지는가?

lambda x : # 함수의 매개변수 x
: x*x # 연산를 return
: x<10 # 연산을 return


참조: 물론, map과 filter를 안쓰고도 훌륭한 코드를 만들 수 있다.

list_a = [1, 2, 3, 4, 5, 6] #펑션에서 호출하는게 아니므로 리스트 컴프리핸션 위에 위치

filtered_result = [i*i for i in list_a if i*i < 10]

print(filtered_result)

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다