초보 개발자

6강 객체처럼 다뤄지는 함수 그리고 람다 본문

Python/윤성우 열혈 파이썬

6강 객체처럼 다뤄지는 함수 그리고 람다

taehyeki 2021. 12. 16. 12:12

[파이썬에서는 함수도 객체]

파이썬은 모든 것을 객체로 처리한다.

x = 3.0
type(x)
<class 'float'>
x.is_integer()
True

우리가 x=3.0이라는 식을 만들면 3.0이라는 값이 메모리 공간에 저장이 되고, x라는 이름이 거기에 붙는다 라고 설명을 했다. 

이걸 조금 더 확장해서 보면 파이썬에서는 실수, 정수 등 저장할 수 있는 Class를 미리 만들어서 가지고 있다.

그래서 우리가 실수(3.0)을 딱 쓰면 실수를 담기 위해 설치해 놓은 클래스가 있는데 (<class 'float'>) 그 클래스를 기반으로 객체(상자)를하나 만든다 그리고 그 객체 안에 3.0이라는 값을 담는 것이다. 그리고 x는 바로 그 객체에 붙는 것이다.

 

위의 예에서 변수 x를 대상으로 is_interger메소드를 호출하고 있는데 이는 x에 담긴 실수 3.0이 객체라는 증거가 된다.

함수 역시 객체이다. 다음 예를 살펴보자 파이썬은 역시 함수를 담기 위한 class를 미리 만들어 두었다. <class 'function'>

def fun1(n): # 매개변수가 있고 값을 반환하는 함수
	return n

def fun2(): # 매개변수가 없고 값의 반환도 없는 함수
	print('hello')
    
type(fun1) # fun1은 function 클래스의 객체임을 확인한다.
<class 'function'>
type(fun2) # fun2은 function 클래스의 객체임을 확인한다.
<class 'function'>

우리가 함수를 정의하면 function클래스의 객체를 생성하고 정의한 함수를 그 안에다 채워 넣는다. 따라서 그 객체가 함수처럼 기능을 하게 되는 것이다. 그리고 그 함수의 이름 func1이 객체에 붙일 변수 이름이다.(포스트잇) 따라서 fun1은 변수이름이다라고 해도 틀리지가 않는다. ( 어딘가에 존재하는 함수객체를 참조하는 역할이기 때문 )

 

def say1():
	print('hello')

def say2():
	print('hi~')

def caller(fct):
	fct() #fct를 통해 전달된 함수를 호출

caller(say1) # say1 함수를 caller에 전달
hello
caller(say2) # say2 함수를 caller에 전달
h1~

caller 함수에 매개변수로 say1을 전달하는데 이게 가능한 이유가 뭐냐면 say1은 함수에 붙어있는 이름이기 때문에 가능한 것이다. 

 

1. caller(say1)이 호출이 되면 say1이 참조하고 있는 함수객체(A라고 칭함)A 에 fct라는 이름으로 또 한번 참조가 되어지는 상황이다. (레퍼런스 카운트 2)

 

2. 현재 A를 참조하는 변수 fct와 say1라는 두가지가 존재하는 상태이며

 

3. fct를 호출을 하니 A가 실행이 되어진다.( 이게 가능한 이유는 같은 것을 참조하고 있기 때문에 )

 

매개변수에 함수를 어떻게 전달을 하느냐?? -------> 함수도 사실은 객체이기 때문에 가능한 것이다.

 

그래서 함수안에 함수를 만들어서 이를 반환할 수 있다.

def fct_fac(n):
	def exp(x): # 함수 내에서 정의된, x의 n제곱을 반환하는 함수
    	return x ** n
    return exp  # 위에서 정의한 함수 exp를 반환한다.
    
    
f2 = fct_fac(2) # f2는 제곱을 계산해서 반환하는 함수를 참조한다.
f3 = fct_fac(3) # f3는 세제곱을 계산해서 반환하는 함수를 참조한다.
f2(4) # 4의 제곱은?
16
f3(4) # 4의 세제곱은?
62

함수안에 어떻게 함수를 만들 수 있는지 의아해 할 수 있지만 함수 안에 x=3,y=1 이런식으로 변수를 만들어 쓰는 것은 어색하지 않을 것이다. 하지만 이 둘은 차이가 없다. 앞서 배운 것처럼 3이라는 숫자를 가지고 객체를 만들고 x라는 이름을 붙이는 것이나 함수 역시도 객체이며 변수명으로 참조하는 것이라고 생각하면 쉽게 받아들일 수 있을 것이다.

 

이 함수는 함수를 만들어서 반환한다. 

예를 들어 2를 전달하면서 fct_fac 함수를 호출하면 내부적으로 다음과 같은 형태의 함수가 만들어 진다.

def exp(x):
	return x ** 2

그리고 다음 문장에 의해서 이렇게 만들어진 함수가 반환된다.

return exp

3역시 다음 형태의 함수가 만들어지고 반환이 된다.

def exp(x):
	return x ** 3

이렇듯 함수를 만들어서 반환할 수 있는 이유는 함수도 객체이기 때문이다.

 

 

[람다]

 이름이 없는 함수이다.

ref = lambda s: print(s) # 람바 기반의 함수 정의
ref('oh~')
oh~

s는 매개변수이다. print(s)는 함수 몸통이다. 근데 이름이 없다. 따라서 ref라는 이름을 람다함수 객체에 붙여 주는 것이다. 만약 ref가 없다면 우리가 만든 람다에는 접근할 수가 없다.

 

다시 말해서

lambda args: expression

위에서 매개변수 선언을 args에 둔다. 그리고 함수의 몸체(내용)을 expression에 두면 이름 없는 함수가 만들어져 반환이 된다. 물론 이름이 없는 함수가 만들어져서 반환되는 것이기 때문에 위에서 보인 것과 같이 이를 변수에 저장해야 한다.

ref = lambda s: print(s)

이 람다 함수의 매개변수는 s이다.

s를 가지고 실행할 수 있는 몸체는 print(s)이다.

이렇게 해서 생성된 함수를 ref에 저장한다.

 

f1 = lambda n1, n2 : n1 + n2
f1(3,4)
7

위의 예에서 매개변수 선언은 다음과 같다. 이렇듯 매개변수가 둘 이상인 경우 이를 콤마로 구분해야 한다.

그리고 함수 몸체에서 어떤 결과가 만들어 진다면 return을 쓰지 않아도 알아서 return이 된다. ( 실제로 return을 넣으면 오류로 이루어 진다 )

 

f2 = lambda s: len(s)
f2('simple')
6

연산의 결과가 함수 호출의 결과로 남는 경우. 또는 함수(len()과 같은)가 어떠한 값을 반환하는 경우에 return이 있는 것으로 생각하면 된다. 즉 연산 혹은 함수 호출을 통해서 값이 남게 되면 그 값이 반환된다고 생각하자.

 

다음 예에서 보듯이 매개 변수가 없는 경우에는 그냥 그 자리를 비워 두면 된다.

f3 = lambda : print('yes~')
f3()
yes~

 

앞서 만들었던 fct_fac함수를 람다로 만들면 간편하게 만들 수 있다.

def fct_fac(n):
	retrun lambda x : x ** n

f2 = fct_fac(2)
f3 = fct_fac(3)
f2(4)
16
f3(4)
64