파이썬은 함수와 메소드를 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]: