-
Notifications
You must be signed in to change notification settings - Fork 1
2.12. 내용정리: 12일차
- 파이썬은 객체지향 패러다임만을 제공하는 것이 아니라 다양한 프로그래밍 패러다임을 제공한다.
- 파이썬에서는 수많은 패러다임 중 함수형 패러다임 역시 제공한다.
- 함수형 프로그래밍(functional programming) 은 자료 처리를 수학적 함수의 계산으로 취급하고 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임의 하나이다.
- 객체지향 프로그래밍은 객체를 중심적으로 사고하는 기법이라면, 함수형 프로그래밍은 거의 모든 것을 순수 함수로 나누어 문제를 해결하는 기법 이다.
- 작은 문제를 해결하기 위한 함수를 작성하여 가독성을 높이고 유지 보수를 용이하게 해준다.
- 파이썬에는 여러 가지 형태의 함수들이 존재한다.
- 앞서 파이썬에는 함수형 패러다임을 제공한다고 서술했었다.
- 함수형 패러다임에서 사용되는 몇 가지 함수 패턴들이 존재한다.
- 람다 함수는 이전에도 배웠던 개념이다.
- 간단한 함수를 한 줄만으로 만들게 해준다.
- 주로 함수를 사용할 수 없는 경우나 간단한 함수를 인자값으로 넘길 때 사용된다.
f = lambda x: x + 1
f(10)재귀 함수란, 정의한 함수에 같은 함수를 호출하여 반복하는 결과를 만들어내는 함수를 의미한다.
아래는 팩토리얼(factorial) 연산을 수행하는 함수 factorial 을 정의한 것이다.
def factorial(n):
# 'n'이 1보다 클 경우에는 n과 factorial 함수를 다시 호출한 결과값을 곱셈한다.
if n > 1:
return n * factorial(n - 1)
# 'n'이 1과 같거나 작다면 1을 반환시킨다.
else:
return 1
x = factorial(5)
print(x)위의 코드가 수행되는 과정은 다음과 같다.
- 함수
factorial이 호출된다. - 인자값은
5이므로,n은5가 된다. -
5는1보다 크기 때문에5 * factorial(5 - 1)이return키워드에 의해 반환된다. - 반환됨과 동시에
factorial(5 - 1)이 호출되므로,factorial(5 - 1)을 수행한다. -
n은4이므로, 다시4 * factorial(4 - 1)이 수행된다. - 위와 같은 과정이 계속 반복이 되다보면 결국
n은1이 된다. -
n이1이므로,1이 반환된다. - 최종적으로
5 * 4 * 3 * 2 * 1을 반환하게 된다. - 따라서
x는120이 된다.
- 퍼스트 클래스 함수란, 프로그래밍 언어가 함수를 일급 객체(first-class object 혹은 일등 시민; first-class citizen 라고도 함) 으로 취급하는 것을 의미한다.
- 함수 자체를 인자(argument)로써 다른 함수에 전달하거나 다른 함수의 결과값으로 반환할 수도 있다.
- 혹은 함수를 변수에 할당하거나 자료 구조안에 저장할 수 있는 함수를 의미한다.
- 즉, 변수에 담을 수 있고, 함수의 인자로 전달하고 함수의 반환값(return value)으로 전달할 수 있는 함수를 의미한다.
- 일급 객체란, 다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체를 가리킨다.
- 보통 함수에 인자로 넘기기, 수정하기, 변수에 대입하기와 같은 연산을 지원할 때 일급 객체라고 한다.
- 변수나 자료 구조 안에 담을 수 있다.
- 파라미터로 전달 할 수 있다.
- 반환값으로 사용할 수 있다.
- 할당에 사용된 이름과 무관하게 고유한 구별이 가능하다.
함수를 인자값으로 받는 함수 f 를 선언한다.
# 퍼스트 클래스 함수 'f' 선언
def f(x):
# 여기서 파라미터 'x'는 함수이다.
# 함수를 호출할 때에는 괄호를 사용한다.
print("함수 'f' 호출")
x()그 다음, 함수 'test' 호출 이라는 메시지를 호출하는 함수 test 를 선언한다.
def test():
print("함수 'test' 호출")함수 f 에 인자값으로 함수 test 를 넣어주고 호출한다.
f(test)- 중첩 함수란, 말 그대로 함수 내에 또 다른 함수를 의미한다.
- 함수 내부에 선언된 함수이므로, 내부 함수(Inner function) 라고도 한다.
아래는 outer 라는 함수 안에 inner 라는 내부 함수를 선언하는 예시이다.
def outer():
print("외부 함수 영역")
def inner():
print("내부 함수 영역")
# 내부 함수 'inner' 호출
inner()
# 함수 'outer' 호출
outer()- 이전에 배웠던
global이라는 키워드는 함수 외부에 선언되어있는 변수(즉, 전역 변수)를 사용하기 위한 키워드이다. -
nonlocal키워드는global키워드와는 다르게, 중첩 함수의 관계에서, 내부 함수가 외부 함수의 지역 변수의 값을 다시 할당하려고 할 때 쓰이는 키워드이다.
아래의 코드를 실행하면 UnboundLocalError 라는 에러가 발생한다.
def outer():
a = 10
def inner():
a += 10
print('a:', a)
inner()
# 함수 'outer'를 호출하면 아래와 같은 에러를 발생시킨다.
# UnboundLocalError: local variable 'a'
outer()- 에러가 발생하는 이유는 할당하기 전에 지역 변수
a가 이미 참조되었기 때문이다. - 즉, 변수
a가 외부 함수의 지역 변수로써의a인지, 내부 함수의 지역 변수로써의a인지를 구별하지 못하고 있는 것이다.- 이전에 공부했었던 이름 공간이라는 개념을 생각해보면 된다.
- 이러한 문제를 해결하기 위해서 변수
a를 아래와 같이nonlocal로 선언하면 된다.
def outer():
print("외부 함수 영역")
a = 10
# 내부 함수 'inner' 호출
def inner():
print("내부 함수 영역")
# nonlocal 키워드를 사용하여 함수 'outer' 영역에 선언된 변수 'a'를 사용하겠다는 의미이다.
nonlocal a
a += 10
print('a:', a)
# 내부 함수 'inner' 호출
inner()
# 함수 'outer' 호출
outer()- 클로저 함수란, 어떤 함수를 함수 자신이 가지고 있는 환경과 함께 저장하는 함수이다.
- 또한 함수가 가진 프리 변수(free variable) 를 클로저 함수가 만들어지는 당시의 값과 참조된 값들을 맵핑(mapping)해주는 역할을 한다.
- 파이썬에서 프리 변수란, 코드 블럭안에서 사용은 되었지만, 그 코드 블럭안에서 정의되지 않은 변수를 의미한다.
- 클로저 함수는 일반 함수와는 다르게, 자신의 영역 밖에서 호출된 함수의 변수값과 참조된 값들을 복사하고 저장한 뒤, 이 값들에 접근할 수 있게 도와준다.
- 즉, 간단히 말해서 클로저 함수란, 자신이 가지고 있는 환경에 맞춰진 형태로 반환해주는 함수이다.
아래는 클로저 함수 outer 와 내부 함수인 inner 를 실행시킨 결과값을 반환하는 예시이다.
# 클로저 함수 'outer' 정의
def outer():
# 함수 'outer' 영역의 변수 'msg'
msg = 'Hi'
# 내부 함수 'inner' 정의
def inner():
# 내부 함수 'inner' 역시 함수 'outer' 영역에 있다.
# 재선언을 하는 것이 아니기 때문에 함수 'outer' 영역의 변수 'msg'임을 알 수 있다.
# 따라서 지역 변수 'msg'를 참조할 수 있다. (프리 변수)
print(msg)
# 함수 'inner'를 호출하면서 실행 결과를 반환시킨다.
# 반환되는 값이 없으므로, 결과값은 'None'이 반환된다.
return inner()
# 클로저 함수 'outer' 호출
outer()다음과 같이 함수를 반환시키는 함수를 만들어서 응용할 수도 있다.
# 클로저 함수 'f' 정의
def f(x):
# 내부 함수 'g' 정의
def g(y):
# 클로저 함수 'f'의 파라미터 'x'의 값과 내부 함수 'g'의 파라미터 'y'의 값을 더해준다.
return x + y
# 내부 함수 'g'를 반환한다.
return g
# 함수 'f'는 함수를 반환시키는 함수이므로, 아래와 같이 호출할 수 있다.
# 아래와 같은 방식을 마치 사슬처럼 엮여있는 모양이라고 해서 체이닝(chaining) 기법이라고 한다.
f(1)(2)위와 같은 방식을 마치 사슬처럼 엮여있는 모양이라고 해서 체이닝(chaining) 기법이라고 한다.
- 데코레이터란, 함수와 메서드를 장식(decorate)하는 문법적인 요소이다.
- 사용할 때에는
@기호를 붙여서 사용한다.
이해하기 쉽게 정의하자면 다음과 같다.
- 이전에
lambda함수같은 경우, 함수의 간단한 표현 방식이라고 서술하였다. - 데코레이터는 함수를 인자로 받는 함수, 즉 퍼스트 클래스 함수를 문법적인 요소로 간편하게 표현한 것이다.
- 함수를 인자로 받는 함수를 선언한다.
- 함수 안에 또다른 함수(내부 함수)를 정의한다.
- 새롭게 정의한 내부 함수를 반환한다.
- 이렇게 정의한 함수를
@기호를 붙여@함수명으로 새롭게 정의한 함수 윗줄에 추가한다.
먼저, 아래의 예시처럼 함수를 인자로 받는 클로저 함수 f 를 정의한다.
def f(x):
print("함수 'f' 호출")
# 내부 함수 'g'를 정의한다.
def g(y):
print("함수 'g' 호출")
# 함수를 인자로 받았기 때문에 'function' 타입이 출력된다.
print(x)
print(type(x))
# 인자값으로 받은 함수 'x'에 함수 'g'의 인자값 'y'를 넘긴다.
return x(y)
# 내부 함수 'g'를 반환시킨다.
return g그 다음 새롭게 정의할 함수 윗줄에 @ 기호를 붙여 다음과 같이 정의한다.
@f
def f2(x):
print("함수 'f2' 호출")
return x + 1그 다음 아래의 코드를 실행시켜본다.
y = f2(10)
print(y)위의 코드를 실행하면 결과는 다음과 같이 출력된다.
함수 'f' 호출
함수 'g' 호출
<function f2 at 0x.....>
<class 'function'>
함수 'f2' 호출
11
과정은 다음과 같다.
- 함수
f에 인자값으로x가 넘어가며,x는 함수이다. - 내부 함수
g에 인자값으로 받은 함수x가 넘어간다. - 내부 함수
g에서 인자값으로 넘긴 함수x에 내부 함수g의 인자값y를 넘긴다. - 내부 함수
g는 최종적으로 인자값으로 넘긴 함수x를 실행시킨 결과값을 반환시킨다. - 결과적으로, 함수
f는 위의 과정을 거쳐 만들어진 내부 함수g를 반환한다. - 함수
f가 호출된다. - 함수
f의 인자값x로f2가 넘어간다. - 내부 함수
g에서 인자값으로 받은 함수f2에 인자값y를 넘기면서 함수f2를 호출시킨 후, 그 결과값을 반환되도록 만들어진다. - 이렇게 만들어진 내부 함수
g를 반환한다. - 내부 함수
g가 호출된다. - 최종적으로, 내부 함수
g는 함수f2를 호출한다.
과정을 전부 풀어서 보면 매우 복잡해 보이지만, 알고 보면 사실 굉장히 단순하다.
위에서 살펴본 퍼스트 클래스 함수와 클로저 함수 등을 활용한 다양한 기법들이 존재하며, 파이썬에서 이를 지원하는 대표적인 세 가지 함수가 존재한다.
-
map함수는 순환 가능한(이터러블; iterable) 객체에 있는 모든 요소(element)에 인자값으로 넘겨받은 함수를 적용하여 그 결과를 반환한다.- 순환 가능한 객체, 즉 이터러블(iterable) 객체는 컨테이너 타입 중 인덱싱과 슬라이싱이 가능한 객체를 의미한다.
- 이때 함수는 여러 인자값을 받을 수 있어야 하고, 모든 이터러블 객체의 요소에 동시에 적용되도록 해야 한다.
- 즉, 정리하자면
map함수는 어떤 순환 가능한 객체(대표적으로 리스트와 같은 컨테이너 타입)에 함수를 적용시킨 결과가 나오도록 해주는 함수이다. - 사용하는 방법은
map(순환 가능한 객체의 요소 하나를 인자로 받는 함수, 순환 가능한 객체)이다.
아래는 map 함수를 사용해 순환 가능한 객체인 리스트 l 에 있는 요소들을 모두 1 씩 증가시키는 예시이다.
def f(x):
return x + 1
l = [1, 2, 3, 4, 5]
y = map(f, l)
# 출력하면 'map' 객체가 출력된다.
print(z)map 함수를 통해 나온 결과인 map 타입의 객체는 자체적으로 사용이 불가능하기 때문에 타입을 변환시켜서 사용해야 한다.
z = list(y)
# 출력하면 모든 요소가 하나씩 더해진 '[2, 4, 6, 8, 10]'이 출력된다.
print(z)위의 예시를 lambda 를 활용하면 훨씬 더 간단히 표현할 수 있다.
l = [1, 2, 3, 4, 5]
y = list(map(lambda x: x + 1, l))
print(y)-
filter함수는 이터러블 객체의 각 요소에 대해 함수의 결과값이True를 반환하는 요소만을 추려내는 함수이다. - 즉, '필터'라는 이름에서 알 수 있듯, 어떤 조건(함수)에 만족하는 경우에 해당하는 요소들만을 추출해주는 함수이다.
- 사용하는 방법은
map함수와 동일하게filter(함수, 순환 가능한 객체)와 같이 사용한다.- 여기서 인자값으로 받는 함수의 결과값은 참과 거짓을 판단하는 함수가 들어간다.
아래는 filter 함수를 활용해서 리스트 l 안에 있는 요소들 중에서 짝수에 해당하는 요소들만 가져오는 예제이다.
def f(x):
return x % 2 == 0
l = [1, 2, 3, 4, 5]
y = filter(f, l)
# 출력하면 'filter' 객체가 나온다.
print(y)
# 따라서 'map' 함수처럼 타입을 변환시켜서 사용해야 한다.
z = list(y)
# 짝수에 해당하는 값인 '[2, 4]'가 출력된다.
print(z)마찬가지로, lambda 키워드를 통해 좀 더 축약하여 표현할 수 있다.
l = [1, 2, 3, 4, 5]
y = list(filter(lambda x: x % 2 == 0, l))
print(y)- 파이썬이 3.x 버전으로 업데이트가 진행되면서
reduce함수가 파이썬의 기본적인 내장 함수에서 빠졌다고 한다. - 대신 파이썬에 내장된
functools이라는 모듈(라이브러리)를import해서 사용할 수 있다.-
reduce함수를 사용하기 위해서는from functools import reduce를 통해 사용할 수 있다.
-
-
reduce함수는 순환 가능한 객체의 각 요소를 왼쪽부터 오른쪽 방향으로 함수를 적용시키며 하나의 값으로 합쳐진 결과를 반환시켜준다. - 사용하는 방법은
reduce(함수, 순환 가능한 객체, 초기값<생략 가능>)이다.
아래는 reduce 함수를 활용해서 1 부터 100 까지 모두 더해 결과를 얻는 예시이다.
# reduce 함수를 사용하기 위해 아래의 코드를 추가한다.
from functools import reduce
def f(x, y):
return x + y
l = [i for i in range(1, 101)]
# 함수의 결과값이 나오므로, 타입을 변환시킬 필요는 없다.
y = reduce(f, l)
print(y)마찬가지로, lambda 를 통해 축약된 표현이 가능하다.
from functools import reduce
l = [i for i in range(1, 101)]
y = reduce(lambda x, y: x + y, l)
print(y)초기값을 지정해주면 순환 가능한 객체안에 요소가 아무것도 없는 경우, 그 초기값을 반환시켜준다.
from functools import reduce
# 요소가 하나도 없는 빈 리스트이다.
l = []
# 초기값을 1로 지정한다.
y = reduce(lambda x, y: x + y, l, 1)
# 인자값으로 받은 리스트가 비어있으므로, y의 값은 초기값으로 지정한 1이 된다.
print(y)위의 예시에서도 알 수 있듯, reduce(lambda x, y: x + y, [1, 2, 3, 4, 5]) 라고 한다면, ((((1+2)+3)+4)+5) 의 값을 반환하는 원리이다.
위에서 살펴본 개념들을 토대로 장점들을 정리해보자면 다음과 같이 정리할 수 있다.
- 프로그래밍 코드를 수학처럼 수식화시켜서 표현하는데 좋다.
- 표현이 간결해지므로 코드가 짧아진다.
- 따라서 가독성이 좋아지고 유지 보수하기가 보다 용이해진다.
- 작은 문제들(위의 예제들에서도 살펴보았듯, 단순 반복하는 작업을 통해 결과를 내놓는 문제들)을 해결하기에 적합하다.
- 위의 예제들에서도 살펴보았듯, 불필요한 반복문들이 많이 생략된다.
- 자료 구조(컨테이너 타입)를 제자리에서 수정하여 해결한다.
- 함수 자체가 독립적이므로 프로그램을 동작시키는데 안전성을 보장받을 수 있다.
- 이는 쓰레드(Thread)와 같이 동시성 프로그래밍이나 멀티 프로그래밍 환경에서 빛을 발한다.
- (이 개념에 대해 잘 모르겠지만, 한번에 여러 동작을 수행해야 하는 환경에서 유리하다고 이해했다.)