파이썬은 함수와 메소드를 1급 객체(first-class object)로 다루며, 이는 C, C#, Java 등과 매우 다른 특징 중의 하나이다. 1급 객체는 예를 들어 함수의 인자로 사용될 수 있다. 파이썬을 제외한 기타 언어에서는 함수는 2급 객체로 구분되는 반면에 숫자, 문자열, 리스트, 튜클 등등만을 1급 객체로 다루며, 따라서 따라서 함수 자체를 다른 함수의 인자로 사용할 수 없다.
C 언어의 경우 함수 포인터, Java의 경우 클래스를 활용하여 동일한 기능을 구현할 수 있다. 하지만 좀 더 복잡하며, 파이썬은 매우 간단히 함수를 활용하는 방법을 제공한다.
함수를 인자로 받는 함수를 통틀어 고계함수(higher-order function)라 한다. 여기서는 함수를 다른 함수의 인자값 또는 리턴값으로 사용하는 방법을 배운다.
숫자들의 리스트를 입력받아 각 항목의 제곱으로 이루어진 리스트를 리턴하는 함수를 작성해보자.
In [1]:
def x2_list(xs):
L = []
for x in xs:
L.append(x**2)
return L
In [2]:
x2_list([0, 0.5, 1, 1.5, 2, 2.5])
Out[2]:
이제 숫자들의 리스트를 입력받아 각 항목의 세제곱으로 이루어진 리스트와 네제곱으로 이루어진 리스트를 리턴하는 함수들을 작성해보자.
In [3]:
def x3_list(xs):
L = []
for x in xs:
L.append(x**3)
return L
In [4]:
def x4_list(xs):
L = []
for x in xs:
L.append(x**4)
return L
이런 식으로 5승, 6승, 7승값으로 이루어진 리스트를 린터하는 함수들을 작성하려면 매번 전체코드를 복사해서 특정 부분을 수정해 주어야 한다. 그런데 이런 과정은 매우 비효율적이며 코드를 복잡하게 만든다.
코드를 작성할 때 가장 주의해야 하는 것중의 하나는 변하는 부분과 변하지 않는 부분을 구분해서 따로따로 처리하는 것이다. 앞서 다룬 세 개의 함수들의 경우 각각 4번째 줄에서 사용되는 지수값에 의해서만 구분된다. 이럴 경우 아래와 같이 변화되는 부분을 일반화시켜 인자로 빼서 활용할 수 있다.
In [5]:
def xn_list(n, xs):
L = []
for x in xs:
L.append(x**n)
return L
즉, 변하는 지수갑만 따로 빼내서 인자로 활용하면 들어오는 인자값에 따라 임의의 지수승값으로 이루어진 리스트를 리턴하는 함수를 만들게 되었다.
In [6]:
xn_list(5, [0, 0.5, 1, 1.5, 2, 2.5])
Out[6]:
In [7]:
xn_list(10, [0, 0.5, 1, 1.5, 2, 2.5])
Out[7]:
이제 xn_list 함수가 다음의 경우도 다룰 수 있는지 확인해보자.
In [8]:
from math import *
In [9]:
def sin_list(xs):
L = []
for x in xs:
L.append(sin(x))
return L
위 함수는 sin 함수값으로 이루어진 리스트를 리턴하는 함수이며, 불행히도 xn_list 함수를 이용하여 구현할 수 없다. 이유는 넷째 줄에서 변하는 부분이 x ** n의 꼴이 아니기 때문이다.
xn_list 함수를 수정해서 위 경우까지 다룰 수 있으려면 단순히 지수값만이 아니라 사용되는 함수 전체를 인자로 사용해야한다. 코드를 아래와 같이 수정하면 된다.
In [10]:
def fun_list(f, xs):
L = []
for x in xs:
L.append(f(x))
return L
이제 sin_list(xs)가 fun_list(sin, xs)와 동일함을 확인할 수 있다.
In [11]:
sin_list([0, 0.5, 1, 1.5, 2, 2.5])
Out[11]:
In [12]:
fun_list(sin, [0, 0.5, 1, 1.5, 2, 2.5])
Out[12]:
In [13]:
fun_list(sin, [0, 0.5, 1, 1.5, 2, 2.5]) == sin_list([0, 0.5, 1, 1.5, 2, 2.5])
Out[13]:
따라서 sin_list 함수를 다음과 같이 재정의 할 수 있다.
In [14]:
def sin_list_1(xs):
return fun_list(sin, xs)
앞서 다뤘던 x2_list, x3_list 함수들을 fun_list 함수를 이용하여 구현할 수 있다.
In [15]:
def x2_list_1(xs):
def x_2(x):
return x ** 2
return fun_list(x_2, xs)
In [16]:
x2_list_1([0, 0.5, 1, 1.5, 2, 2.5]) == x2_list([0, 0.5, 1, 1.5, 2, 2.5])
Out[16]:
In [17]:
def x3_list_1(xs):
def x_3(x):
return x ** 3
return fun_list(x_3, xs)
In [18]:
x3_list_1([0, 0.5, 1, 1.5, 2, 2.5]) == x3_list([0, 0.5, 1, 1.5, 2, 2.5])
Out[18]:
앞서 다뤘던 xn_list 함수 또한 fun_list 함수를 이용하여 구현할 수 있다.
In [19]:
def xn_list_1(n, xs):
def x_n(x):
return x ** n
return fun_list(x_n, xs)
In [20]:
xn_list_1(5, [0, 0.5, 1, 1.5, 2, 2.5]) == xn_list(5, [0, 0.5, 1, 1.5, 2, 2.5])
Out[20]:
lambda는 이름이 없는 함수를 정의할 때 사용함.
정의되는 함수의 이름이 없으므로, 함수를 호출하고자 할 때는 함수 정의 전체를 항상 사용해야 한다.
예제 1: 인자를 하나 받는 함수
lambda x : x ** 5
위 함수는 아래 함수와 동일한 역할을 수행함.
def arbitrary_name1(x):
return x**5
즉, arbitrary_name1(5)는 (lambda x : x**5)(5)와 동일한 값임.
예제 2: 인자를 두개 이상 받는 함수 경우는 사용되는 인자들을 콤마로 구분해서 나열하면 된다.
lambda x, y: (x + y) * 2
위 함수는 다음 함수와 동일하다.
def arbitrary_name2(x, y):
return (x + y) * 2
In [21]:
(lambda x: x*3)(5)
Out[21]:
위 함수는 아래 함수와 동일하다. 대신 사용법에 차이가 있음에 주의해야 한다.
In [22]:
def fx_3(x):
return x * 3
fx_3(5)
Out[22]:
In [23]:
(lambda x, y: (x + y) * 2)(3, 4)
Out[23]:
위 함수는 아래 함수와 동일하다.
In [24]:
def plus_2times(x, y):
return (x + y) * 2
plus_2times(3, 4)
Out[24]:
In [25]:
def x2_list_2(xs):
return fun_list(lambda x : x ** 2, xs)
In [26]:
x2_list_2([0, 0.5, 1, 1.5, 2, 2.5]) == x2_list_1([0, 0.5, 1, 1.5, 2, 2.5])
Out[26]:
In [27]:
def x3_list_2(xs):
return fun_list(lambda x : x ** 3, xs)
In [28]:
x3_list_2([0, 0.5, 1, 1.5, 2, 2.5]) == x3_list_1([0, 0.5, 1, 1.5, 2, 2.5])
Out[28]:
In [29]:
def xn_list_2(n, xs):
return fun_list(lambda x : x ** n, xs)
In [30]:
xn_list_2(5, [0, 0.5, 1, 1.5, 2, 2.5]) == xn_list_1(5, [0, 0.5, 1, 1.5, 2, 2.5])
Out[30]:
In [31]:
def fun_table(f):
for i in range(6):
x = i * 0.5
print("{} {}").format(x, f(x))
견본답안 활용 예제
In [32]:
# 제곱과 세제곱 함수
def square(x): return x ** 2
def cubic(x): return x ** 3
In [33]:
print("Square")
fun_table(square)
In [34]:
print("Cubic"); fun_table(cubic)
In [35]:
def linear_fun(a, b):
def linear(x):
return a * x + b
return linear
linear_fun(a, b)의 리턴값을 사용하는 방법은 함수를 호출하는 것과 동일하다.
In [36]:
g = linear_fun(2, 3)
g(5)
Out[36]:
위 코드에서 g(x) = 2*x + 3 함수를 의미하게 된다.
In [37]:
funs = [sin, cos]
함수들의 리스트 역시 자료형은 list이다.
In [38]:
type(funs)
Out[38]:
이제 Square 함수와 Cubic 함수의 테이블을 연속으로 보여줄 수 있다.
In [39]:
for f in funs:
print(str(f))
fun_table(f)
In [40]:
str(sin)
Out[40]:
함수 이름을 좀 더 예쁘게 보여주고 싶으면 위 코드를 좀 더 섬세하게 수정해야 한다.
In [41]:
for f in funs:
print("<" + str(f).split()[-1][:-1] + " 함수 테이블>")
fun_table(f)
print("\n")
위 코드에서 2번 줄이 조금 복잡해 보인다. 특히
str(f).split()[-1][:-1]
코드를 면밀히 살펴보아야 한다. 위 문장에서 변수 f는 sin 또는 cos 함수를 가리킨다.
예를 들어, str(sin)을 실행하였을 때 리턴되는 값을 확인하면 다음과 같다.
'<built-in function sin>'
따라서 위 문자열을 먼저 split 메소드로 쪼개어 리턴되는 리스트의 마지막 항목인 sin> 문자열을 선택한 후, 마지막 문자인 >을 제외한 나머지 문자열을 프린트 한다.
In [42]:
sorted([2, 1, 3])
Out[42]:
즉, 주어진 시퀀스 자료형을 크기 순서대로 오름차순으로 정렬한다. 그런데 내림차순으로 정렬하고자 하면 다음과 같이 사용해야 한다.
In [43]:
sorted([2, 1, 3], reverse=True)
Out[43]:
sorted 함수의 인자가 하나 더 늘어났다. 실제로 sorted 함수는 최대 네 개까지 인자를 받을 수 있다. help 명령어를 사용하면 아래와 같은 정보를 확인할 수 있다.
In [44]:
help(sorted)
위 정보는 sorted 함수는 네 개의 인자를 받는다. 하지만, 두 번째부터 네 번째 인자들은 이미 기본값이 정해져 있다. 그래서 그 인자들은 굳이 입력하지 않아도 파이썬 해석기 내부에서는 지정된 기본값으로 처리한다. 따라서 다음이 성립한다.
In [45]:
sorted([2, 1, 3]) == sorted([2, 1, 3], reverse=False)
Out[45]:
sorted 함수의 다른 두 개의 키워드 인자인 cmp와 key의 경우도 사용법은 동일하다. 두 키워드 인자의 역할은 다음과 같다.
cmp 키워드 인자: 두 개의 값을 어떻게 비교할지를 정해주는 함수가 입력되어야 한다. 기본값으로 None이라 함은 파이썬에서 기본적으로 정의된 방식으로 값을 비교한다는 의미이다.
key 키워드 인자: 문자열, 리스트, 튜플 등 시퀀스 자료형 값을 정렬할 때 무엇을 기준으로 정렬할지를 정하는 데에 사용한다. 기본값인 None을 사용하면 파이썬에서 정해진 순서대로 정렬한다.
주의: cmp 키워드 인자는 파이썬 3.x 버전에서는 사용되지 않는다. 따라서 파이썬 3.x 버전에서 sorted 함수의 인자로 세 개가 된다.
In [46]:
'A' < 'a'
Out[46]:
따라서 문자열을 정렬할 때 대문자가 시작하는 단어가 먼저 정렬된다.
In [47]:
sorted("This is a test string from Andrew".split())
Out[47]:
대, 소문자 구별없이 정렬하고자 한다면 key 인자 값을 수정해야 한다.
예를 들어, 문자열 클래스의 lower 메소드인 str.lower를 이용하여 문자열을 모두 소문자로 바꾼 뒤에 순서를 정하는 식을 활용할 수 있다.
In [48]:
sorted("This is a test string from Andrew".split(), key=str.lower)
Out[48]:
In [49]:
students_grades = [
('john', 'B', 12),
('jane', 'C', 10),
('dave', 'A', 15),
]
sorted 함수를 기본값으로 적용하면 이름 순서대로 정렬된다.
In [50]:
sorted(students_grades)
Out[50]:
이제 위 리스트를 학점 순으로 정렬하려면 다음과 같이 key 인자 값을 변경해야 한다.
주의: 'A' < 'B' < 'C'
In [51]:
sorted(students_grades, key=lambda student: student[1])
Out[51]:
위에서 key인자에 입력된 lambda student: student[1] 함수는 students_grades 리스트의 각 항목을 인자로 받았을 때 인덱스 번호 1에 해당하는 항목, 즉 두 번째 항목인 학점을 기준으로 정렬하라고 알려주는 역할을 수행한다.
등급을 오름차순으로 정렬하고자 한다면 reverse 인자값도 변경해야 한다.
In [52]:
sorted(students_grades, key=lambda student: student[1], reverse=True)
Out[52]:
이제 위 리스트를 점수 순으로 정렬하려면 다음과 같이 key 인자 값을 변경해야 한다.
In [53]:
sorted(students_grades, key=lambda student: student[2])
Out[53]:
In [54]:
def area(a, b):
return a * b
print("가로, 세로 길이가 각각 {}, {}인 직사각형의 넓이는 {}이다.".format(3, 3, area(3, 3)))
print("가로, 세로 길이가 각각 {}, {}인 직사각형의 넓이는 {}이다.".format(3, 5, area(3, 5)))
이제 세로의 길이를 3고정해서 사용하고자 한다면 다음과 같이 키워드 인자를 선언하면 된다. 그러면 인자를 하나만 입력할 경우 자동으로 세로의 길이는 3으로 삽입되어 계산된다. 반면에 세로의 길이를 변경하고자 한다면 해당 키워드 인자값을 변경하면 된다.
In [55]:
def area(a, breadth=3):
return a * breadth
print("가로, 세로 길이가 각각 {}, {}인 직사각형의 넓이는 {}이다.".format(3, 3, area(3)))
print("가로, 세로 길이가 각각 {}, {}인 직사각형의 넓이는 {}이다.".format(5, 3, area(5)))
print("가로, 세로 길이가 각각 {}, {}인 직사각형의 넓이는 {}이다.".format(5, 7, area(5, 7)))
이제 키워드가 두 개 이상일 경우 인자의 순서에 주의해야 한다.
아래 예제를 잘 살펴보면서 키워드 인자를 어떻게 활용하는지 다시 한 번 확인할 수 있다.
In [56]:
def strange_volume(x, y_axis = 3, z_axis=4):
return x * y_axis * (z_axis + 1)
print(strange_volume(3, y_axis=5, z_axis=7) == strange_volume(3, z_axis=7, y_axis=5))
print(strange_volume(3, 5, 7) == strange_volume(3, 7, 5))
In [57]:
def f():
print(y)
In [58]:
f()
위와 같은 경우를 대비해 에러처리를 해둘 필요도 있다.
In [59]:
def f():
try:
print(y)
except:
raise NameError("the varible y is not defined yet")
In [60]:
f()
다음은 함수 내부에서 정의되어 사용되는 변수, 즉, 지역변수에 대한 간단한 예제이다.
In [61]:
def f1():
x = '지역변수이다'
print("x는 {}.".format(x))
In [62]:
f1()
반면에 함수 외부에서 정의된 변수는 전역변수이다.
In [63]:
x = '전역변수이다'
In [64]:
def f2():
print("x는 {}.".format(x))
In [65]:
f2()
x가 전역변수로 이미 선언이 되었어도 f1 함수를 호출하면 그 함수 안에 선언된 x 값이 사용된다. 함수가 호출되었을 때 사용되는 변수에 할당된 값을 먼저 함수 내부에서 찾으며, 함수 내부에서 찾지 못할 경우 함수 밖에서 찾기 때문이다.
In [66]:
f1()
함수가 호출되었을 때 사용되는 변수에 할당된 값을 먼저 함수 내부에서 찾으며, 함수 내부에서 찾지 못할 경우 함수 밖에서 찾기 때문이다.
함수 f1에서 정의된 지역벽수 x는 함수 밖에서 정의된 전역변수 x와 이름만 같을 뿐 전혀 상관이 없으며 서로 알지 못한다. 지역변수와 전역변수의 이름을 가능하면 다르게 지정하는 것이 좋다.
또한 지역변수는 함수 밖으로는 절대로 알려지지 않음에 주의해야 한다.
In [67]:
def d_is_only_defined_here():
d = 10
print("d 값은 여기서만 {}으로 정의되었습니다".format(d))
In [68]:
d_is_only_defined_here()
위 함수 외부에서 d값을 물으면 모른다고 에러가 발생한다.
In [69]:
d
함수 내부에서 선언된 지역변수를 전역변수처럼 사용하려면 global 키워드를 사용한다.
In [70]:
def e_is_made_global():
global e
e = 10
print("e 값은 여기서도 보이며 현재 {} 값이 할당되어 있습니다".format(e))
In [71]:
e_is_made_global()
In [72]:
e
Out[72]: