학교 다닐 때 행렬로 연립방정식 풀었던 기억이 날 듯 합니다. 선형대수(Linear Algebra)는 통계, 기계학습, 공학, 영상/이미지 처리 등 여러 분야에서 활용이 됩니다. 선형대수를 전부 다루려면 너무나 방대하므로, 이번 포스팅에서는 Python의 NumPy에 있는 선형대수(Linear Algebra) 함수들 중에서 자주 사용하는 함수에 대해서만 선별적으로 소개하겠습니다. 그리고 선형대수의 이론적인 부분은 별도로 참고할 수 있는 링크를 달도록 하겠습니다. 


  • 단위행렬 (Unit matrix): np.eye(n)
  • 대각행렬 (Diagonal matrix): np.diag(x)
  • 내적 (Dot product, Inner product): np.dot(a, b)
  • 대각합 (Trace): np.trace(x)
  • 행렬식 (Matrix Determinant): np.linalg.det(x)
  • 역행렬 (Inverse of a matrix): np.linalg.inv(x)
  • 고유값 (Eigenvalue), 고유벡터 (Eigenvector): w, v = np.linalg.eig(x)
  • 특이값 분해 (Singular Value Decomposition): u, s, vh = np.linalg.svd(A)
  • 연립방정식 해 풀기 (Solve a linear matrix equation): np.linalg.solve(a, b)
  • 최소자승 해 풀기 (Compute the Least-squares solution): m, c = np.linalg.lstsq(A, y, rcond=None)[0]




 1. 단위행렬 혹은 항등행렬 (Unit matrix, Identity matrix): np.eye(n)


단위행렬은 대각원소가 1이고, 나머지는 모두 0인 n차 정방행렬을 말하며, numpy의 eye() 함수를 사용해서 만들 수 있습니다. 


* 참고 링크 : https://rfriend.tistory.com/141



import numpy as np


unit_mat_4 = np.eye(4)


print(unit_mat_4)

[[1. 0. 0. 0.]
 [0. 1. 0. 0.]
 [0. 0. 1. 0.]
 [0. 0. 0. 1.]]

 




  1. 대각행렬 (Diagonal matrix): np.diag(x)


대각행렬은 대각성분 이외의 모든 성분이 모두 '0'인 n차 정방행렬을 말합니다. 아래 예시의 행렬에서 빨간색으로 표시한 원소를 '0'으로 바꾼 행렬이 대각행렬입니다. 


* 참고 링크 : http://rfriend.tistory.com/141


 

In [1]: import numpy as np


In [2]: x = np.arange(9).reshape(3, 3)


In [3]: print(x)

[[0 1 2]

 [3 4 5]

 [6 7 8]]


In [4]: np.diag(x)

Out[4]: array([0, 4, 8])


In [5]: np.diag(np.diag(x))

Out[5]:

array([[0, 0, 0],

        [0, 4, 0],

        [0, 0, 8]])





  2. 내적 (Dot product, Inner product): np.dot(a, b), a.dot(b)


matrix dot product, inner product, scalar product, projection product

Python에서 '*' 를 사용한 두 행렬 간 곱은 원소 간 곱(element-wise product)을 반환하며, 선형대수에서 말하는 행렬 간 내적 곱을 위해서는 np.dot() 함수를 이용해야 합니다. 


원소 간 곱 (element-wise product)

: a*b

내적 (dot product, inner product)

: np.dot(a, b)

 

In [6]: a = np.arange(4).reshape(2, 2)


In [7]: print(a)

[[0 1]

 [2 3]]


In [8]: a*a

Out[8]:

array([[0, 1],

        [4, 9]])


In [6]: a = np.arange(4).reshape(2, 2)


In [7]: print(a)

[[0 1]

 [2 3]]


In [9]: np.dot(a, a)

Out[9]:

array([[ 2, 3],

        [ 6, 11]])



np.dot(a, b) NumPy 함수와 a.dot(b)의 배열 메소드의 결과는 동일합니다. 



In [10]: a.dot(a)

Out[10]:

array([[ 2, 3],

        [ 6, 11]])

 




  3. 대각합 (Trace): np.trace(x)


정방행렬의 대각에 위치한 원소를 전부 더해줍니다. 

아래의 2차 정방행렬 예의 대각합은 0+5+10+15 = 30 이 됩니다. (파란색으로 표시함)



In [12]: b = np.arange(16).reshape(4, 4)


In [13]: print(b)

[[ 0 1 2 3]

 [ 4 5 6 7]

 [ 8 9 10 11]

 [12 13 14 15]]


In [14]: np.trace(b)

Out[14]: 30

 



3차원 행렬에 대해서도 대각합을 구할 수 있습니다. 2차원은 대각선 부분의 원소 값을 전부 더하면 되지만 3차원 행렬에서는 대각(diagonal)이 어떻게 되나 좀 헷갈릴 수 있겠습니다. 아래의 3차원 행렬의 대각합을 구하는 예를 살펴보면, [0+12+24, 1+13+25, 2+14+26] = [36, 39, 42] 가 됩니다. 



In [15]: c = np.arange(27).reshape(3, 3, 3)


In [16]: print(c)

[[[ 0 1 2]

  [ 3 4 5]

  [ 6 7 8]]


 [[ 9 10 11]

  [12 13 14]

  [15 16 17]]


 [[18 19 20]

  [21 22 23]

  [24 25 26]]]


In [17]: np.trace(c)

Out[17]: array([36, 39, 42])

 




  4. 행렬식 (Matrix Determinant): np.linalg.det(x)


역행렬이 존재하는지 여부를 확인하는 방법으로 행렬식(determinant, 줄여서 det)이라는 지표를 사용합니다. 이 행렬식이 '0'이 아니면 역행렬이 존재하고, 이 행렬식이 '0'이면 역행렬이 존재하지 않습니다. 


* 참고 링크 : http://rfriend.tistory.com/142


아래의 예에서 array([[1, 2], [3, 4]]) 의 행렬식이 '-2.0'으로서, '0'이 아니므로 역행렬이 존재한다고 판단할 수 있습니다. 



In [18]: d = np.array([[1, 2], [3, 4]])


In [19]: np.linalg.det(a)

Out[19]: -2.0

 




  5. 역행렬 (Inverse of a matrix): np.linalg.inv(x)


역행렬은 n차정방행렬 Amn과의 곱이 항등행렬 또는 단위행렬 In이 되는 n차정방행렬을 말합니다. A*B 와 B*A 모두 순서에 상관없이 곱했을 때 단위행렬이 나오는 n차정방행렬이 있다면 역행렬이 존재하는 것입니다.

역행렬은 가우스 소거법(Gauss-Jordan elimination method), 혹은 여인수(cofactor method)로 풀 수 있습니다. 




In [20]: a = np.array(range(4)).reshape(2, 2)


In [21]: print(a)

[[0 1]

 [2 3]]


In [22]: a_inv = np.linalg.inv(a)


In [23]: a_inv

Out[23]:

array([[-1.5, 0.5],

        [ 1. , 0. ]])




위의 예제에서 np.linalg.inv() 함수를 사용하여 푼 역행렬이 제대로 푼 것인지 확인을 해보겠습니다. 역행렬의 정의에 따라서 원래의 행렬에 역행렬을 곱하면, 즉, a.dot(a_inv) 또는 np.dot(a, a_inv) 를 하면 단위행렬(unit matrix)가 되는지 확인해보겠습니다. 



In [24]: a.dot(a_inv)

Out[24]:

array([[1., 0.],

         [0., 1.]])

 




  6. 고유값 (Eigenvalue), 고유벡터 (Eigenvector): w, v = np.linalg.eig(x)


정방행렬 A에 대하여 Ax = λx  (상수 λ) 가 성립하는 0이 아닌 벡터 x가 존재할 때 상수 λ 를 행렬 A의 고유값 (eigenvalue), x 를 이에 대응하는 고유벡터 (eigenvector) 라고 합니다. 


np.linalg.eig() 함수는 고유값(eigenvalue) w, 고유벡터(eigenvector) v 의 두 개의 객체를 반환합니다. 


In [25]: e = np.array([[4, 2],[3, 5]])


In [26]: print(e)

[[4 2]

[3 5]]


In [27]: w, v = np.linalg.eig(e)


#  w: the eigenvalues lambda

In [28]: print(w)

[2. 7.]


# v: the corresponding eigenvectors, one eigenvector per column

In [29]: print(v)

[[-0.70710678 -0.5547002 ]

[ 0.70710678 -0.83205029]]

 



고유벡터는 배열 인덱싱하는 방법을 사용해서 각 고유값에 대응하는 고유벡터를 선택할 수 있습니다. 



# eigenvector of eigenvalue lambda 2

In [30]: print(v[:, 0]

[-0.70710678 0.70710678]


# eigenvector of eigenvalue labmda 7

In [31]: print(v[:, 1]

[-0.5547002 -0.83205029]

 




  7. 특이값 분해 (Singular Value Decomposition): u, s, vh = np.linalg.svd(A)


특이값 분해는 고유값 분해(eigen decomposition)처럼 행렬을 대각화하는 한 방법으로서, 정방행렬뿐만 아니라 모든 m x n 행렬에 대해 적용 가능합니다. 특이값 분해는 차원축소, 데이터 압축 등에 사용할 수 있습니다. 이론적인 부분은 설명하자면 너무 길기 때문에 이 포스팅에서는 설명하지 않겠으며, 아래의 링크를 참고하시기 바랍니다. 


* 참고 링크 : http://rfriend.tistory.com/185


아래의 np.linalg.svd(A) 예제는 위의 참고 링크에서 사용했던 예제와 동일한 것을 사용하였습니다. 


In [32]: A = np.array([[3,6], [2,3], [0,0], [0,0]])


In [33]: print(A)

[[3 6]

 [2 3]

 [0 0]

 [0 0]]


In [34]: u, s, vh = np.linalg.svd(A)


In [35]: print(u)

[[-0.8816746 -0.47185793 0. 0. ]

 [-0.47185793 0.8816746 0. 0. ]

 [ 0. 0. 1. 0. ]

 [ 0. 0. 0. 1. ]]


In [36]: print(s)

[7.60555128 0.39444872]


In [37]: print(vh)

[[-0.47185793 -0.8816746 ]

 [ 0.8816746 -0.47185793]]

 




  8. 연립방정식 해 풀기 (Solve a linear matrix equation): np.linalg.solve(a, b)


아래의 두 개 연립방정식의 해(x0, x1)를 np.linalg.solve(a, b) 함수를 사용하여 풀어보겠습니다. 



위의 연립방정식을 어떻게 행렬로 입력하고, np.linalg.solve(a, b)에 입력하는지 유심히 살펴보시기 바랍니다. 



In [38]: a = np.array([[4, 3], [3, 2]])


In [39]: b = np.array([23, 16])


In [40]: x = np.linalg.solve(a, b)


In [41]: print(x)

[2. 5.]

 



NumPy 가 제대로 x0, x1의 해를 풀었는지 확인해보겠습니다. x0=2, x1=5 가 해 맞네요!



In [42]: np.allclose(np.dot(a, x), b)

Out[42]: True

 




  9. 최소자승 해 풀기 (Compute the Least-squares solution)

     : m, c = np.linalg.lstsq(A, y, rcond=None)[0]


회귀모형 적합할 때 최소자승법(Least-squares method)으로 잔차 제곱합을 최소화하는 회귀계수를 추정합니다. 


* 참고 링크 : https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.lstsq.html



아래의 예에서는 회귀계수 m, y절편 c를 최소자승법을 사용해서 구해보겠습니다. 



In [43]: x = np.array([0, 1, 2, 3])


In [44]: y = np.array([-1, 0.2, 0.9, 2.1])


In [45]: A = np.vstack([x, np.ones(len(x))]).T


In [46]: A

Out[46]:

array([[0., 1.],

        [1., 1.],

        [2., 1.],

        [3., 1.]])


In [47]: m, c = np.linalg.lstsq(A, y, rcond=None)[0]


In [48]: print(m, c)

0.9999999999999999 -0.9499999999999997

 



아래의 그래프에서 점은 원래의 데이터이며, 빨간색 선은 최소자승법으로 추정한 회귀식의 적합선이 되겠습니다. 



In [49]: import matplotlib.pyplot as plt

    ...: plt.plot(x, y, 'o', label='Original data', markersize=10)

    ...: plt.plot(x, m*x + c, 'r', label='Fitted line')

    ...: plt.legend()

    ...: plt.show()




많은 도움이 되었기를 바랍니다. 


Posted by R Friend R_Friend

댓글을 달아 주세요

이번 포스팅에서는 함수나 클래스의 구현을 미룰 때 쓰는 pass statement 에 대해서 알아보겠습니다. 


Python은 함수나 클래스를 정의할 때 { } 를 사용하지 않고 들여쓰기(indentation)로 함수나 클래스가 실행할 코드 블록을 정의하는데요, 만약 코드 블록 부분에 실행해야 할 코드가 없다면 def function_name:  이후의 줄에 아무것도 없게 되어 Python은 'SyntaxError: unexpected EOF while parsing' 에러를 발생시킵니다. 



In [1]: def null_func():

   ...:

   ...:

File "<ipython-input-1-85c822900a5a>", line 2

^

SyntaxError: unexpected EOF while parsing


 




이처럼 함수나 클래스 이름 정의 후에 ':' 다음 줄에 아무것도 실행시키지 않으려면 'pass' 문을 명시적으로 표기해주어야만 SyntaxError 가 발생하지 않습니다. 





다음은 아무것도 실행할 것이 없는 null_func() 라는 이름의 함수에 pass 문을 사용한 예입니다. 



In [2]: def null_func():

   ...:       pass

 




다음은 분류 모델의 BaseClassifier() 클래스를 정의할 때 fit() 함수에 pass 문을 사용한 예입니다. 



In [3]: from sklearn.base import BaseEstimator

   ...:   class BaseClassifier(BaseEstimator):

   ...:       def fit(self, X, y=None):

   ...:           pass

   ...:       def predict(self, X):

   ...:           return np.zeros((len(X), 1), dtype=bool)

 



'빈 구현'을 만드는 pass 문의 사용 용도 만큼이나 이번 포스팅은 별 내용이 없네요. ^^; 





이전에 for loop 포스팅을 했을 때 pass, continue, break 문을 비교해서 설명했던 적이 있는데요, 이번에 함수 파트에서 pass 문이 다시 나온만큼 복습하는 차원에서 pass와 continue문을 간단한 예를 들어서 한번 더 비교해서 설명하겠습니다. 



  pass : 아무것도 실행하지 않고 다음 행으로 넘어감



In [4]: for i in [1, 2, 3, 4, 5, 6]:

   ...:       if i == 4:

   ...:           pass

   ...:           print("This pass block will be printed before 4")

   ...:       print("The number is ", i)

   ...:   print("The end")

   ...:


The number is 1

The number is 2

The number is 3

This pass block will be printed before 4

The number is 4

The number is 5

The number is 6

The end

 




  continue : 다음 순번의 loop로 되돌아가서 loop문을 실행함



In [5]: for i in [1, 2, 3, 4, 5, 6]:

   ...:       if i == 4:

   ...:           continue

   ...:           print("This continue block and No. 4 will not be printed")

   ...:       print("The number is ", i)

   ...:   print("The end")


The number is 1

The number is 2

The number is 3

The number is 5

The number is 6

The end

 



많은 도움이 되었기를 바랍니다. 


Posted by R Friend R_Friend

댓글을 달아 주세요

  1. 김진양 2018.07.26 23:42  댓글주소  수정/삭제  댓글쓰기

    안녕하세요, 글쓴이님. 혹시 하나 질문드려도되나요?
    파이썬 관련 질문이 아니고 R 질문인데 너무 막막해서 최신 글에 댓글을 다는 실례를 범합니다....
    본문과 상관없는 질문을 달기엔 너무 죄송해서 질문글을 단 R프랜드님의 본문 링크를 여기다 걸게요!
    다시 한번 실례가 되었다면 너무 죄송하다는 말씀을 전해드립니다.
    R 분석 결과 외부 파일로 저장하기 : capture.output()

    출처: http://rfriend.tistory.com/18 [R, Python 분석과 프로그래밍 (by R Friend)]
    입니다.

  2. R Friend R_Friend 2018.07.27 01:03 신고  댓글주소  수정/삭제  댓글쓰기

    처음 질문 달으셨던 18번 포스팅에 답글 달아놓았습니다. 잘 해결되면 좋겠네요.

    • 김진양 2018.07.30 08:46  댓글주소  수정/삭제

      안녕하세요 알프랜드님, 친절한 답변 감사드립니다! 말씀해주신대로 수식을 적었는데 새 Sheet 생성 문제 관련해서 오류가 발생했습니다. 열심히 구글링을 해보았지만, 소양이 부족해 해결하지 못했네요.
      염치없지만 한번 더 도움을 요청할 수 있을까요???? 18번 포스팅에 댓글을 달아놓았습니다!

    • R Friend R_Friend 2018.07.30 17:34 신고  댓글주소  수정/삭제

      안녕하세요 김진양님. 18번 포스팅에 수정한 코드 답글 달았습니다.

이번 포스팅에서는 함수 안에 isinstance() 함수를 사용하여 매개변수의 형식(Type)을 확인하고, 함수에서 필요로 하는 데이터 형식이 아니면 에러 메시지를 반환하여 함수 사용자로 하여금 에러를 수정하는데 참고할 수 있는 정보를 제공할 수 있도록 하는 방법에 대해서 소개하겠습니다. 


  • 데이터 형식 확인 함수: isinstance(my_number, int), isinstance(my_string, str)
  • raise TypeError("message")
  • raise ValueError("message")




  (1) 정수형이 아니면 TypeError, 양수가 아니면 ValueError 반환하기


아래의 [1] 번 함수는 숫자를 input으로 넣으면 짝수(even number) 인지 아니면 홀수(odd number) 인지를 판단해주는 예제입니다. 


- if not isinstance(number, int) 조건문 함수를 사용하여 정수인지 여부를 확인하고, 

- if number <= 0 조건문으로 양수가 아닌지를 확인하여 

정수가 아니면 TypeError, 양수가 아니면 ValueError 를 반환하도록 하였습니다. 



In [1]: def even_odd_num_checker(number):

   ...:

   ...:     # TypeError, ValueError handling

   ...:     if not isinstance(number, int):

   ...:         raise TypeError("'number' is not an integer.")

   ...:     if number <= 0:

   ...:         raise ValueError("'number' must be positive.")

   ...:

   ...:     if number % 2 == 0:

   ...:         print("This number is EVEN")

   ...:     else:

   ...:         print("This number is ODD")


In [2]: even_odd_num_checker(10)

This number is EVEN


In [3]: even_odd_num_checker(9)

This number is ODD

 



아래의 [4]번에서는 '10'이라는 문자열을 입력했을 때 TypeError 가 발생한 예이며, [5]번은 매개변수 값으로 5.5 를 입력했을 때 정수(integer)가 아니기 때문에 역시 TypeEror가 발생한 예입니다. 그리고 [6]번 예는 -2 가 양수가 아니기 때문에 ValueError가 발생하였습니다.  위의 [1]번에서 정의한 함수대로 잘 작동하고 있음을 알 수 있습니다. 



In [4]: even_odd_num_checker('10') # TypeError

Traceback (most recent call last):


File "<ipython-input-4-858c9cb910a4>", line 1, in <module>

even_odd_num_checker('10') # TypeError


File "<ipython-input-1-b74b94983114>", line 5, in even_odd_num_checker

raise TypeError("'number' is not an integer.")


TypeError: 'number' is not an integer.



In [5]: even_odd_num_checker(5.5) # TypeError

Traceback (most recent call last):


File "<ipython-input-5-1125bee4ec3f>", line 1, in <module>

even_odd_num_checker(5.5) # ValueError


File "<ipython-input-1-b74b94983114>", line 5, in even_odd_num_checker

raise TypeError("'number' is not an integer.")


TypeError: 'number' is not an integer.

 


In [6]: even_odd_num_checker(-2) # ValueError

Traceback (most recent call last):


File "<ipython-input-6-8bb80e5d6ca4>", line 1, in <module>

even_odd_num_checker(-2) # ValueError


File "<ipython-input-1-b74b94983114>", line 7, in even_odd_num_checker

raise ValueError("'number' must be positive.")


ValueError: 'number' must be positive.





  (2) 문자열이 아니면 TypeError 반환하기


다음으로 [8]번에서 문자열을 매개변수 값으로 입력하면 문자열의 길이를 반환해주는 함수를 정의해보겠습니다. 이때 매개변수 값이 문자열(string)이 아니면 TypeError 를 반환하도록 하였습니다. 


[9]번에서 string_length() 함수에 "hello world" 값을 넣으니 길이가 11이라고 정상적으로 작동함을 알 수 있습니다. 



In [7]: import sys


In [8]: def string_length(string_input):

   ...:

   ...: # Python version check

   ...:     if sys.version_info[0] == 3:

   ...:         string_arg = str # Python 3x version

   ...:     else:

   ...:         string_arg = basestring # Python 2x version

   ...:

   ...: # TypeError handling

   ...:     if not isinstance(string_input, string_arg):

   ...:         raise TypeError("'string' is not a string")

   ...:

   ...:     string_length = len(string_input)

   ...:     print("The length of the string is {0}".format(string_length))


In [9]: string_length("hello world")

The length of the string is 11

 



[10]번에서 string_length() 함수에 숫자 10을 넣으면 문자열이 아닌 정수형이므로 TypeError 메시지를 반환합니다. 



In [10]: string_length(10) # TypeError

Traceback (most recent call last):


File "<ipython-input-10-997e19d6acd3>", line 1, in <module>

string_length(10) # TypeError


File "<ipython-input-8-8363558b8e40>", line 11, in string_length

raise TypeError("'string' is not a string")


TypeError: 'string' is not a string

 


많은 도움이 되었기를 바랍니다. 

Posted by R Friend R_Friend

댓글을 달아 주세요

이번 포스팅에서는 Python의 함수 중에서도 '함수 안의 함수 (Function in Function)' 로서 


- (1) 중첩 함수 (Nested Function): 함수 안에 정의된 함수

- (2) 재귀 함수 (Recursive Function): 함수 안에 자기 자신을 호출하는 함수


에 대해서 알아보겠습니다. 


[ Python 함수 안의 함수 (Function in Function) ]




  (1) 중첩 함수 (Nested Function): 함수 안에 정의된 함수


def 로 시작하는 함수 안에 또 다른 하나의 def 로 시작하는 함수를 정의할 수 있는데요, 이때 함수 안에 정의된 또 다른 함수를 중첩 함수 (Nested Function) 이라고 합니다. 


중첩 함수는 자기가 속한 원래 함수의 매개변수를 받아서 사용할 수 있습니다. 아래의 예시는 outer() 함수의 num 매개변수를 중첩함수인 inner() 함수의 매개변수 input 으로 사용해서 최종 결과값인 result2 를 반환하도록 하는 함수입니다. 



In [1]: def outer(num):

   ...:

   ...:     def inner(n): # nested function

   ...:         result1 = n + 1

   ...:         return result1

   ...:

   ...:     result2 = inner(num)**2

   ...:     return result2;


In [2]: outer(9) # (9+1)**2

   ...:

Out[2]: 100

 




중첩 함수 (nested function)는 자신이 속한 원래 함수 안에서만 역할을 하며, 원래 함수의 밖에서는 인식이 안됩니다.  아래의 [3] 처럼 중첩함수인 inner() 함수를 호출하려고 하면 'NameError: name 'inner' is not defined' 라는 에러메시지가 뜹니다. 



In [3]: inner(10) # NameError

Traceback (most recent call last):


File "<ipython-input-3-4050b378a5f5>", line 1, in <module>

inner(10) # NameError


NameError: name 'inner' is not defined

 




중첩함수를 사용하면 복잡한 수식도 여러개의 함수를 하나의 함수로 묶어서 정의할 수 있습니다. 아래의 예시는 모집단의 표준편차(population standard deviation) 사용자 정의 함수를 중첩함수를 사용해서 정의한 것입니다. 





In [4]: import math


In [5]: def stddev(*args):

   ...:

   ...:     def mean_func():  # nested function

   ...:         mean = sum(args)/len(args)

   ...:         return mean

   ...:

   ...:     def variance(mean):  # nested function

   ...:         squared_deviation = 0

   ...:         for arg in args:

   ...:             squared_deviation += (arg - mean)**2

   ...:             var = squared_deviation / len(args)

   ...:         return var

   ...:

   ...:     v = variance(mean_func())

   ...:     return math.sqrt(v)


In [6]: stddev(1.0, 2.0, 3.0, 4.0, 5.0)

Out[6]: 1.4142135623730951



# standard deviation using numpy std()

In [7]: import numpy as np


In [8]: np.std([1.0, 2.0, 3.0, 4.0, 5.0])

Out[8]: 1.4142135623730951

 




  (2) 재귀 함수 (Recursive Function): 함수 안에 자기 자신을 호출하는 함수


재귀 함수(Recursive Function)는 함수 안에서 자기 자신을 호출하는 함수를 말합니다. 




함수가 호출자이자 동시에 피호출자가 되어서 반복에 반복에 반복을 계속하는 함수입니다. 



왼쪽의 사진을 보면 재귀함수가 자기 자신을 계속해서 반복하면서 호출을 하는 것이 무엇을 의미하는지 이해가 될 것 같습니다. 


(*출처: http://www.itcuties.com/java/recursion-and-iteration/)












재귀함수(recursive function)와 중첩함수(nested function)를 같이 이용해서 Factorial 계산하는 함수를 정의해보겠습니다. 



In [9]: def factorial(number):

   ...:

   ...: # error handling

   ...:     if not isinstance(number, int):

   ...:         raise TypeError("Oops. 'number' must be an integer.")

   ...:     if number < 0:

   ...:         raise ValueError("Oops. 'number' must be zero or positive.")

   ...:

   ...:     def inner_factorial(number): # nested function

   ...:         if number == 0:

   ...:             return 1

   ...:         elif number > 0:

   ...:             return inner_factorial(number-1)*number # recursive function

   ...:

   ...:     return inner_factorial(number)


In [10]: factorial(5)

Out[10]: 120

 



위의 factorial(5) 함수를 실행하면 아래에 각 절차를 풀어서 설명해 놓은 것과 같이 자기 자신의 factorial() 함수를 계속 반복해서 호출하면서 값을 계산해서 최종값을 반환하게 됩니다. 




재귀함수는 편리한 점도 있지만 호출 비용이 크므로 성능이 중요한 경우에는 재귀함수 대신 반복문을 사용하는 것이 좋겠습니다.  그리고 재귀함수를 짤 때는 반드시 종료 조건을 정의해 주어야 무한반복의 함정에 빠지지 않습니다. (파이썬이 지정해 놓은 재귀 함수 반복 호출 최대 회수를 초과하면 'RuntimeError: maximum recursion depth exceeded while calling a Python object' 에러가 발생합니다)


많은 도움이 되었기를 바랍니다. 

Posted by R Friend R_Friend

댓글을 달아 주세요

이번 포스팅에서는 변수의 유효범위(scope of variables)에 대해서 알아보겠습니다. 


파이썬은 변수의 유효범위에 따라 


(1) 프로그램 전체에서 유효한 전역변수(Global Variable)

(2) 함수의 코드블록 안에서만 유효한 지역변수(Local Variable)


의 두가지 종류가 있습니다. 


변수의 유효범위에 대해서 정확하게 이해하지 못하면 Name Error, Unbound Local Error 등과 같은 의도치 않은 에러를 유발할 수 있으므로 주의가 필요합니다. 


[ Python Variable Scope : Global Variable vs. Local Variable ]




간단한 예를 들어서 설명해보겠습니다. 


  전역변수(Global Variable) vs. 지역변수(Local Variable)


아래의 [1]번 행에서 var_scope() 함수를 정의하면서 s = "Python is easy" 라는 지역변수(Local variable)을 정의하였습니다. 이 지역변수는 var_scope() 함수가 호출될 경우에만 함수가 실행이 되면서 메모리에 생성이 되었다가 함수 실행이 끝나면 메모리에서도 사라져버립니다. 


반면에 [2]번 행에서 s = "Python is not easy" 라고 할당한 문자열은 전역변수(Global variable)로서 바로 메모리에 저장이 되며, 프로그램을 전체에서 유효하며, 프로그램이 살아있는 한 계속 같이 살아있으면서 이용이 가능합니다. [4]번 행에서 print(s)를 했을 때 전역변수인 s = "Python is not easy"가 출력이 되었으며, var_scope() 함수 안에서만 유효한 지역변수인 s="Python is easy"는 출력이 안되었습니다. 



In [1]: def var_scope():

   ...:     s = "Python is easy" # Local variable

   ...:     print(s)


In [2]: s = "Python is not easy" # Global variable


In [3]: var_scope() # Local variable

Python is easy


In [4]: print(s) # Global variable

Python is not easy

 




  지역변수가 할당되기 전에 호출하려 할 때 발생하는 Unbound Local Error


아래의 [5]번 행에서 정의한 var_scope() 함수의 코드블록을 보면, 지역변수 's'가 할당이 아직 안된 상태에서 빨간색의 print(s) 를 실행하도록 했더니 "Unbound Local Error: local variable: local variable 's' referenced before assignment' 라는 에러 메시지가 떴습니다. 



In [5]: def var_scope():

   ...:     print(s) # Unbound Local Error

   ...:     s = "Python is easy" # local variable

   ...:     print(s); # local variable


In [6]: s = "Python is not easy"


In [7]: var_scope()

Traceback (most recent call last):


File "<ipython-input-7-51074fe12940>", line 1, in <module>

var_scope()


File "<ipython-input-5-5b8d90c83375>", line 2, in var_scope

print(s) # UnboundLocalError


UnboundLocalError: local variable 's' referenced before assignment

 




  함수 안에서 전역변수를 사용할 수 있게 해주는 global keyword


위의 UnblundLocalError 를 해결하려면 아래의 [8]번행에서 함수를 정의할 때 밑줄 그은 'global s' 처럼 global keyword 를 사용해서 전역변수를 사용하겠다고 선언을 해주면 됩니다. 그러면 [9]번 행에서 전역변수로 할당한 s = "Python is not easy"를 var_scope() 함수의 코드블록 안에서 지역변수가 선언되기 전에도 전역변수를 가져다가 함수 호출 시 이용할 수 있습니다. 



In [8]: def var_scope():

   ...:     global s # global variable

   ...:     print(s)

   ...:     s = "Python is easy" # local variable

   ...:     print(s);


In [9]: s = "Python is not easy"


In [10]: var_scope()

Python is not easy     <--- global variable 's'

Python is easy           <--- local variable 's'

 




  지역변수를 함수 밖에서 호출할 때 발생하는 Name Error


함수의 코드블록 안에서 정의된 지역변수는 함수의 밖에서 사용할 수 없습니다. 만약 함수 밖에서 함수안의 지역변수를 호출하려고 하면 "Name Error: name 's2' is not defined' 라는 에러 메시지가 발생합니다. 프로그래밍을 처음하는 분의 경우 지역변수, 전역변수의 변수의 유효범위 개념을 모를 경우 이게 왜 에러가 발생하는가 하고 의아해 할 수 있습니다. 



In [11]: def var_scope2():

    ...:     s2 = "I love Python"

    ...:     print(s2);


In [12]: print(s2) # NameError: name 's2' is not defined

Traceback (most recent call last):


File "<ipython-input-12-ccb37084217a>", line 1, in <module>

print(s2) # NameError: name 's2' is not defined


NameError: name 's2' is not defined

 


많은 도움이 되었기를 바랍니다. 


Posted by R Friend R_Friend

댓글을 달아 주세요

이번 포스팅에서는 lambda 를 사용한 이름이 없는 익명 함수(the anonymous functions), 한 줄로 간단하게 다음 함수 안에 넣어서 사용할 수 있는 인 라인 함수 (inline functions)에 대해서 알아보겠습니다. 


R 사용하다가 Python 넘어올 때 처음으로 lambda 익명함수를 봤을 때 '이게 뭔가?' 했었는데요, 아래의 예제들을 같이 살펴보시면 어렵지 않게 이해할 수 있을 겁니다. 





(1) 익명 함수 (the anonymous functions) lambda 


이전 포스팅에서 소개했던 def function_name(argments): expression return result; 형태의 함수 정의와는 다르게, 함수의 이름 부여 없이도 lambda 를 사용하면 함수를 정의할 수 있습니다. 



[ lambda 함수 문법 ]

lambda arg1, agr2, ... arg n: expression

 


아래는 input으로 받은 숫자를 제곱해주는 함수를 def 와 lambda 로 각각 정의해 본 예제입니다. 


lambda 익명함수 정의는 한줄 짜리 간단한 함수를 작성해서 편하게 쓴다든지, 메모리를 아끼고 가독성을 높이는데 유용하게 쓸 수 있습니다. (lambda는 이름 없는 익명함수이기 때문에 한번 실행되고 다음줄로 넘어가면 heap 메모리 영역에서 증발되므로 메모리를 아낄 수 있습니다.)


def 를 사용한 이름을 가진 함수 정의

lambda 를 사용한 이름 없는(익명) 함수 정의

 

In [1]: def my_pow(x):

   ...:     result = x**2

   ...:     return result;


In [2]: my_pow(3)

Out[2]: 9


[3]: my_pow2 = lambda x: x**2


In [4]: my_pow2(3)

Out[4]: 9




다음으로 List, Tuple, String과 같은 순서열 데이터에 대해 lambda 함수와 함께 map(), filter(), reduce(), apply() 함수의 안에 익명 함수로 INLINE으로 사용하는 예제, 리스트 축약(list comprehension) 도 더불어 소개하겠습니다.  



 (2) map(lambda arg:expression, list) : lambda 함수를 리스트 원소에 적용하기


리스트의 여러개의 각 원소에 lambda 함수를 적용하고 싶으면 map() 함수를 lambda 와 같이 사용하면 됩니다.  (리스트에 문자열이나 튜플과 같은 순서열 자료형이 들어가도 동일함)


만약 리스트에 lambda 함수를 그냥 적용하면 아래처럼 TypeError: unsupported operand type(s) for ** or pow(): 'list' and 'int' 처럼 TypeError 가 납니다. 



In [5]: my_list = [1, 2, 3, 4]


In [6]: my_pow2(my_list) # TypeError

Traceback (most recent call last):


File "<ipython-input-6-0c500bc490e4>", line 1, in <module>

my_pow2(my_list) # TypeError


File "<ipython-input-3-6e25a06562c4>", line 1, in <lambda>

my_pow2 = lambda x: x**2


TypeError: unsupported operand type(s) for ** or pow(): 'list' and 'int'

 



아래는 숫자를 제곱해주는 lambda 함수를 map() 함수를 사용하여 리스트의 각 원소에 적용하고, 이 결과값을 리스트(list)로 반환하도록 한 예제입니다. Python3 에서는 map(lambda ) 를 실행하면 리스트가 아니라 map 객체로 반환이 되며, 리스트로 반환을 받고 싶으면 list() 로 명시적으로 변환을 해주어야 합니다. 



In [5]: my_list = [1, 2, 3, 4]


In [7]: list(map(lambda i: i**2, my_list))

Out[7]: [1, 4, 9, 16]

 




  (3) filter(lambda arg: expression, list) : lambda 함수로 리스트의 원소 필터링 하기


다음 예제는 filter(lambda ) 를 사용하여 여러개의 숫자를 원소로 가지는 리스트에서 '짝수(even number)'만을 선별(filter) 하여 리스트로 반환하는 lambda 익명함수 예제입니다. 



In [8]: num_list = range(10)


In [9]: even_list = list(filter(lambda x: (x%2 == 0), num_list))


In [10]: even_list

Out[10]: [0, 2, 4, 6, 8]

 



위의 예제를 리스트 축약(List Comprehension) 으로도 똑같은 결과 값을 구현할 수 있습니다. 메모리 절약 측면에서는 lambda 익명함수가 더 낫고, 코드 가독성 면에서는 리스트 축약이 좀더 나아보입니다. 



In [11]: new_list_2 = [x for x in num_list if x%2 == 0] # list comprehension


In [12]: new_list_2

Out[12]: [0, 2, 4, 6, 8]

 




  (4) reduce(lambda arg: expression, list): 리스트의 원소에 누적으로 함수 적용


reduce 는 lambda 함수를 리스트의 각 원소에 누적으로 계속 적용할 때 사용합니니다. 아래 예제는 reduce(lambda ) 를 사용하여 숫자 1 ~ 4까지 계속 곱하여 1*2*3*4 의 값을 구한 것입니다(4! 을 구한 것임). 



In [13]: from functools import reduce # python3


In [14]: my_list = [1, 2, 3, 4]


In [15]: reduce(lambda x, y: x*y, my_list)

 




  (5) apply(lambda x: pd.Series({'key': function(x)}) 

     : lambda 함수 적용한 칼럼들로 Pandas DataFrame 만들기


마지막으로, 아래는 DataFrame에 카테고리 변수를 기준으로 groupby() 를 하여 apply(lambda ) 함수로 DataFrame 내 각 칼럼별 요약 집계를 한 결과를 가지고 새로운 DataFrame을 만들어보는 예제입니다. 



데이터 전처리, 집계할 때 알아두면 유용합니다. 



In [16]: import pandas as pd


In [17]: aa = pd.DataFrame({'id': ['a', 'a', 'a', 'b', 'b'],

                                     'val': [1, 2, 3, 4, 5]})

In [18]: aa

Out[18]:

  id  val

0  a    1

1  a    2

2  a    3

3  b    4

4  b    5


In [19]: df = aa.groupby('id').apply(lambda x:

                 pd.Series({'clst_obs_cnt': len(x)

                               , 'clst_val_sum': sum(x.val)

                               , 'id_list': x.id.tolist()})).reset_index()


In [20]: df

Out[20]:

  id  clst_obs_cnt  clst_val_sum    id_list

0  a             3             6        [a, a, a]

1  b             2             9        [b, b]




많은 도움이 되었기를 바랍니다. 

Posted by R Friend R_Friend

댓글을 달아 주세요

이번 포스팅에서는 함수의 호출자(caller)에게 코드를 실행시킨 후의 값(result)을 반환해주는 return 문에 대해서 3가지 유형으로 나누어서 소개하겠습니다. 


(1) return + result => 호출자에게 result 반환

(2) no return & no result => code_block 실행 후 종료 (None 반환)

(3) return (no result) => 함수 즉시 종료




첫번째의 return + result 문은 함수를 사용할 때의 일반적인 형태로서 이미 친숙할 것이라고 예상합니다. 두번째와 세번째는 좀 낯설 수도 있는데요, 이번 포스팅에서 예를 들어가면서 자세히 설명해보겠습니다. 



  (1) return + result => 호출자에게 result 값 반환


아래에 my_sum(3, 5) 함수를 호출하여 sum_all = 3 + 5 = 8 의 result 값을 sum_result 에 반환(return)한 예시입니다. 



In [1]: def my_sum(num1, num2):

   ...:     sum_all = num1 + num2

   ...:     return sum_all;


In [2]: sum_result = my_sum(3, 5)


In [3]: sum_result

Out[3]: 8

 




  (2) no return & no result => code_block 실행 후 종료 (None 반환)


다음으로 함수의 마지막 부분에 return 문을 사용하지 않을 경우, 함수 안의 code_block 만 실행할 뿐 호출자에게는 None을 반환합니다 (즉, 아무값도 반환하지 않음). 


이때 함수를 실행한 후의 결과값을 저장한 객체는 "None Type"이 됩니다. 



In [4]: def my_sum2(num1, num2):

   ...:     sum_all = num1 + num2;


In [5]: sum_result2 = my_sum2(3, 5)


In [6]: sum_result2


In [7]: type(sum_result2) # None

Out[7]: NoneType

 



아래의 예제와 같이 하나의 함수 안에 2개의 return 문을 사용할 수도 있습니다. 하지만 이럴 경우 의도치 않게 실수로 버그를 만들 수도 있으므로 가급적 하나의 return 문을 사용하는 것이 좋습니다. 아래의 예제에서는 argument 로 '0' 값을 받을 경우 return 문이 실행되지 않기 때문에 (의도치않게) None 을 반환하고 있습니다. 



In [8]: def my_abs(num):

   ...:     if num > 0:

   ...:         return num

   ...:     elif num < 0:

   ...:         return (-1)*num;


In [9]: abs_result = my_abs(-1)


In [10]: abs_result

Out[10]: 1


In [11]: abs_result = my_abs(0)


In [12]: abs_result   # no result


In [13]: type(abs_result)   # None

Out[13]: NoneType

 




  (3) return only (no result) => 함수 즉시 종료


마지막으로 return 문만 있고 뒤에 result 가 없는 경우입니다. 이럴 경우에 return 문은 함수 호출자에게 값을 반환(return)하는 의미로 쓰인 것이 아니고 함수를 종료(close)시키는 의미로 쓰였다고 보면 됩니다.  


아래의 예에서는 code block에 i를 loop 돌면서 프린트하는 실행문이 들어있으며, 만약 i 가 1부터 하나씩 증가하다가 3이 되면 return 문을 맞닥드려서 함수를 종료시켜버리는 함수입니다.  따라서 my_print(10) 을 실행시켰을 때 1, 2, 3 까지만 프린트가 실행이 되고 거기서 중단이 되었기 때문에 나머지 4 ~ 10까지는 프린트가 안되었습니다. 



In [14]: def my_print(num):

    ...:     for i in range(1, num+1):

    ...:         print(i)

    ...:         if i == 3:

    ...:             return;


In [15]: my_print(3)

1

2

3


In [16]: my_print(10)

1

2

3

 


많은 도움이 되었기를 바랍니다. 


Posted by R Friend R_Friend

댓글을 달아 주세요

지난번 포스팅에서는 함수의 매개변수 중에서 매개변수의 수가 함수 정의 시에 정한 개수로 고정되어 있는 위치 매개변수, 키워드 매개변수, 기본값 매개변수에 대해서 소개하였습니다. 


이번 포스팅에서는 매개변수의 수가 상황에 따라서 변화하는 가변 매개변수 (Arbitrary Arguments, Variable Length Arguments)에 대해서 알아보겠습니다. 


가변 매개변수는 

  - (1) *args_tuple : Positional 매개변수 (Tuple)

  - (2) **args_dict : 키워드 매개변수 (Dictionary)

의 두가지가 있습니다. 




함수 중에 보면 매개변수로 * (Asterisk), ** (Two Asterisks) 가 들어있는 경우 처음보는 분이라면 이게 무슨 뜻인가하고 궁금하였을 것입니다. 이번 포스팅을 보고 나면 이해가 될 것이예요. 



먼저 지난번 포스팅에서 소개했던 위치 매개변수를 복습해보겠습니다. line 2 에서 'print_list' 함수를 정의할 때 매개변수 'text_list' 1개만을 정의했으며, line 3에서 print_list() 함수를 호출할 때 mylist 1개를 매개변수 위치에 입력해주었더니 정상적으로 잘 출력을 해주었습니다. 



In [1]: mylist = ['Korea', 'Sweden', 'Mexico', 'Germany']


In [2]: def print_list(text_list):

    ...: for text in text_list:

    ...: print text


In [3]: print_list(mylist)

Korea

Sweden

Mexico

Germany

 



하지만, 아래의 line 4 에서 처럼 print_list() 함수의 매개변수에 ('Korea', 'Sweden', 'Mexico', 'Germany') 의 4개의 값을 입력하자 'TypeError: print_list() takes exactly 1 arguments(4 given)' 이라는 에러 메시지가 떴습니다. 이처럼 매개변수 값으로 여러개의 값을 이용하고자 할 때 쓰는 것이 가변 매개변수(arbitrary arguments, variable length arguments) 입니다. 



# TypeError: print_list() takes exactly 1 argument (4 given)

In [4]: print_list('Korea', 'Sweden', 'Mexico', 'Germany')

Traceback (most recent call last):


File "<ipython-input-4-5358764707f0>", line 1, in <module>

print_list('Korea', 'Sweden', 'Mexico', 'Germany')


TypeError: print_list() takes exactly 1 argument (4 given)

 


위의 line 4 처럼 했을 때 TypeError 가 났던 것을 Positional 가변 매개변수를 사용하여 정상적으로 작동하도록 고쳐보면 아래와 같습니다. 



  (1) *args_tuple : Positional 매개변수 (Tuple 형태)


괄호 안의 매개변수 입력란에 '* (Asterisk)' 로 시작하는 매개변수 이름을 넣어줍니다. 이 가변 매개변수는 Tuple 형태입니다. 



In [5]: def print_list_2(*text_list):

    ...: for text in text_list:

    ...: print text


In [6]: print_list_2('Korea', 'Sweden', 'Mexico', 'Germany') # more arguments

Korea

Sweden

Mexico

Germany

 



==============================================================


다음으로 사전형(Dictionary Type)을 매개변수 값으로 받아서 프린트를 해주는 함수를 정의해보겠습니다. 



In [7]: worldcup2018 = dict({'A': 'Russia, Saudi, Egypt, Uruguay',

   ...: 'F': 'Korea, Sweden, Mexico, Germany'})


In [8]: def print_group(group):

   ...: for i in group.keys():

   ...: print ("{0} : {1}".format(i, group[i]))


In [9]: print_group(worldcup2018)

A : Russia, Saudi, Egypt, Uruguay

F : Korea, Sweden, Mexico, Germany

 



위의 line 8 에서 정의한 함수에 아래의 line 10 처럼 여러 개의 사전형 값을 매개변수 값으로 입력하면 'TypeError: print_group() got an unexpected keyword argument 'A'' 라는 에러 메시지가 뜹니다. 



# TypeError: print_group() got an unexpected keyword argument 'A'

In [10]: print_group(A='Russia, Saudi, Egypt, Uruguay',

    ...: F='Korea, Sweden, Mexico, Germany')

Traceback (most recent call last):


File "<ipython-input-10-bae24f97b031>", line 2, in <module>

F='Korea, Sweden, Mexico, Germany')


TypeError: print_group() got an unexpected keyword argument 'A'

 



위의 line 10 처럼 매개변수 값으로 다수 개의 사전형 값을 사용하고 싶을 때 **agrs_dict 의 가변형 매개변수를 사용하면 됩니다. 


  (2) **agrs_dict : 키워드 매개변수 (Dictionary 형태)


사전형(Dictionary) 의 Key 값이 키워드 매개변수의 키워드(Keyword) 가 됩니다. 파이썬 라이브러리의 함수들을 보다 보면 **kwagrs 라고 표현된 경우가 있는데요, 이때 kw 가 KeyWord 의 K(ey)W(ord) 의 kw 를 따온 거예요. 



In [11]: def print_group_2(**group):

    ...: for i in group.keys():

    ...: print ("{0} : {1}".format(i, group[i]))


In [12]: print_group_2(A='Russia, Saudi, Egypt, Uruguay',

    ...: F='Korea, Sweden, Mexico, Germany')

A : Russia, Saudi, Egypt, Uruguay

F : Korea, Sweden, Mexico, Germany

 




  (3) 순서 : *args_tuple 먼저, **args_dict 나중에


*args_tuple 과 **args_dict 두 가지 유형의 가변 매개변수를 모두 사용하여 함수를 정의할 수 있습니다. 단, 이때 순서가 중요합니다. *args_tuple 을 먼저 정의하고, **args_dict 를 나중에 이어서 정의해야 합니다. 



#%% arguments sequence matters

In [13]: def print_all(*tup, **dic):

    ...: print(tup)

    ...: print(dic)


In [14]: print_all(1, 2, 3,

    ...: A='Russia, Saudi',

    ...: F='Korea, Sweden')

(1, 2, 3)

{'A': 'Russia, Saudi', 'F': 'Korea, Sweden'}

 



만약 아래의 line 15 처럼 **args_dict 먼저 정의하고, *args_tuple 을 그 뒤에 이어서 정의하게 되면 'SyntaxError: invalid syntax' 라는 에러 메시지가 뜹니다. 



# SyntaxError: invalid syntax

In [15]: def print_all(**dic, *tup):

    ...: print(tup)

    ...: print(dic)

    ...:

    ...:

File "<ipython-input-15-3e33a9bb0436>", line 1

def print_all(**dic, *tup):

^

SyntaxError: invalid syntax

 



많은 도움이 되었기를 바랍니다. 


Posted by R Friend R_Friend

댓글을 달아 주세요

지난번 포스팅에서는 파이썬의 사용자 정의 함수를 사용하면 좋은 점, 함수를 정의하고 호출하는 기본 문법과 예제를 다루었습니다. 


이번 포스팅에서는 파이썬 함수의 입력(input) 값을 받아서 함수가 정의된 작업을 할 수 있도록 해주는 매개변수(Arguments)에 대해서 알아보겠습니다. 


함수를 호출하는데 사용하는 파이썬의 매개변수에는 아래의 4가지 유형이 있습니다. 

  • 위치 매개변수 (Positional Arguments)
  • 키워드 매개변수 (Keyword Arguments)
  • 기본값 매개변수 (Default Arguments)
  • 가변 매개변수 (Arbitrary Arguments)




이중에서 위치, 키워드, 기본값 매개변수까지만 이번 포스팅에서 소개하고, 가변 매개변수는 다음번 포스팅에서 소개하겠습니다. 



  위치 매개변수 (Positional Arguments)


위치 매개변수(Positional Arguments)는 함수 내 매개변수의 위치별 순서와 데이터 타입에 맞게 정확하게 입력을 해주어야만 하는 매개변수를 말합니다.  아래에 '2개'의 숫자를 매개변수로 받아서 평균을 계산해주는 my_avg() 라는 함수가 있다고 했을 때, 입력 매개변수로 '1개'의 숫자만을 입력받게 되었을 때 'TypeError: my_avg() takes exactly 2 arguments (1 given)' 이라는 에러 메시시가 떴습니다. 



In [1]: def my_avg(x1, x2):

   ...: avg_val = (x1 + x2)/ 2

   ...: return avg_val


In [2]: my_avg(2, 4)

Out[2]: 3


In [3]: my_avg(2) # TypeError

Traceback (most recent call last):


File "<ipython-input-3-69b7a0a22718>", line 1, in <module>

my_avg(2)


TypeError: my_avg() takes exactly 2 arguments (1 given)

 




  키워드 매개변수 (Keyword Arguments)


키워드 매개변수는 함수 호출과 관련이 있는데요, 키워드 매개변수를 이용하여 프로그래머는 함수를 호출할 때 매개변수의 이름(parameter name)으로 매개변수를 지정합니다. 


키워드 매개변수를 이용하면 함수를 정의할 때 썼던 매개변수의 입력 순서를 바꾸어서 함수를 호출할 수도 있으며, 코드 가독성도 높아지는 좋은 점이 있습니다. 


아래에 회사명(company)과 임직원 이름(name)을 출력하는 함수로 예를 들어보았습니다. 두번째 호출 예시에서 매개변수 순서를 바꾸어서 입력했음에도 불구하고 출력은 함수에서 정의했던 순서대로 출력되었음을 알 수 있습니다. 



# Keyword Arguments : The caller identifies the arguments by the parameter name

In [4]: def print_me(company, name):

   ...: print("Company : ", company)

   ...: print("Name : ", name)


In [5]: print_me(company="ABCD Co.", name="Mr.Jack")

('Company : ', 'ABCD Co.')

('Name : ', 'Mr.Jack')


# The order of parameters does not matter

In [6]: print_me(name="Mr.Jack", company="ABC Co.")

('Company : ', 'ABC Co.')

('Name : ', 'Mr.Jack')

 



만약 키워드 매개변수를 이용하지 않는 다면 입력 순서와 데이터 타입을 꼭 함수에서 정의한 순서와 맞추어 주어야 합니다. 아래의 2번째 예에서 보면 함수를 호출할 때 매개변수의 입력 순서가 바뀌면 함수를 정의할 때와의 의도와는 다르게 엉뚱한 순서로 값이 출력되었습니다. 



In [7]: print_me("ABCD Co.", "Mr.Jack")

('Company : ', 'ABCD Co.')

('Name : ', 'Mr.Jack')


# Without parameter name, the order matters

In [8]: print_me("Mr.Jack", "ABCD Co.")

('Company : ', 'Mr.Jack')

('Name : ', 'ABCD Co.')

 




  기본값 매개변수 (Default Arguments)


기본값 매개변수는 함수를 호출할 때 매개변수 입력이 없을 경우에 함수를 정의할 때 입력한 기본값(default value)을 사용할 수 있게 해줍니다.  함수를 정의할 때 기본값 설정이 안되어 있는 상태에서 함수 호출 시 매개변수 값 입력을 빼먹으면 TypeError가 나는데요, 기본값 매개변수를 사용하면 TypeError 없이 사용할 수 있게 됩니다. 대신, 기본값 설정을 잘 지정해주어야 겠지요. 


아래의 예에서는 name 매개변수에 "Who?"라는 기본값(default value)을 지정해서 함수를 print_me2 라는 함수를 정의를 했구요, 함수 호출 시 name 매개변수 값을 입력하지 않았더니 => 기본값이 "Who?"가 출력되었네요. 



# Default Argument Value

In [9]: def print_me2(company, name="Who?"):

   ...: print("Company : ", company)

   ...: print("Name : ", name)


In [10]: print_me2(company="ABC Co.") # No TypeError

('Company : ', 'ABC Co.')

('Name : ', 'Who?')

 



단, 기본값 매개변수는 매개변수의 첫번째에는 사용할 수 없습니다. 만약 첫번째 매개변수에 기본값을 지정하려고 하면 아래처럼 'SyntaxError: non-default argument follows default argument)' 라는 에러가 발생합니다. 



# SyntaxError: non-default argument follows default argument

In [11]: def print_me2(company="ABCD Co.", name):

    ...: print("Company : ", company)

    ...: print("Name : ", name)

    ...:

    ...:

File "<ipython-input-11-c9e7590bf632>", line 1

def print_me2(company="ABCD Co.", name):

SyntaxError: non-default argument follows default argument

 



마지막으로, 매개변수는 숫자로 시작할 수 없습니다. 아래처럼 '3_company' 처럼 숫자 '3'으로 매개변수 이름을 시작했더니 'SyntaxError: invalid syntax'라는 에러가 떴습니다. 



In [12]: def print_me3(3_company, name):

    ...: print("Company : ", company)

    ...: print("Name : ", name)

    ...:

    ...:

File "<ipython-input-12-46388f3578d8>", line 1

def print_me3(3_company, name):

^

SyntaxError: invalid syntax

 



다음번 포스팅에서는 가변 매개변수(arbitrary arguments)에 알아보겠습니다. 


많은 도움이 되었기를 바랍니다. 


Posted by R Friend R_Friend

댓글을 달아 주세요

함수(function)는 input을 받아서 특정 과업(들)을 수행하여 output을 반환하도록 짜여진, 잘 조직되고 재사용이 가능한 코드 블록을 말합니다.

 

그동안 파이썬의 내장형 함수(Built-in function), 여러 라이브러리의 함수에 대해서 소개를 했었는데요, 이번 포스팅부터는 사용자가 직접 함수를 정의해서 사용할 수 있는 '사용자 정의 함수 (User-Defined Function, UDF)' 에 대해서 소개하겠습니다.

 

[ 사용자 정의 함수의 유용성 ]

* 사용자 정의 함수를 재사용(reusable)할 수 있습니다.
* 코드를 구조화, 모듈화(modularization) 함으로써 관리하기 쉽습니다.
* 사용자 정의 함수를 사용하여 코드를 간결하게 하고 가독성을 높일 수 있습니다.
* 사용자 정의 함수를 분산해서 개별적으로 작성할 수 있으므로 애플리케이션 개발 속도를 높일 수 있습니다.


사용자 정의 함수는 (1) 사용자 정의 함수의 정의 (Definition), (2) 호출 (Call), (3) 반환 (Return) 의 절차를 따라서 이용할 수 있습니다.

 

 

사용자 정의함수를 정의(define) 할 때는

  • 첫 줄에는 def 로 시작하며
  • 사용자 정의 함수 이름을 쓰고,
  • 괄호 안에는 매개변수 목록을 쓰며,
  • 콜론(:)을 써줍니다.
  • 다음줄 부터는 들여쓰기(indentation)을 꼭 해주어야 하며,
  • 따옴표 세개("""함수 설명""")로 함수에 대한 부가 설명(Docstring)을 넣어주고 (optional)
  • 실행하고자 함는 작업에 대해서 코드블록을 작성합니다.
  • 마지막에 return 뒤에 반환하고자 하는 결과값을 써주면 됩니다.
  • 반환할 값이 없으면 return 은 생략 가능합니다.

 

(1) 숫자를 input으로 받아서 평균을 반환하는 간단한 사용자 정의 함수를 정의(define)해보겠습니다.

 

 

def my_avg(x1, x2):

    avg_val = (float(x1) + float(x2))/2

   

    return avg_val

 

 

 

(2) 위에서 만든 my_avg() 라는 사용자 정의 함수를 호출(call)하여 (2, 3), (2, 4) 의 두 쌍의 숫자들의 평균을 반환(return) 해보겠습니다.

 

 

In [2]: avg_val = my_avg(2, 3)


In [3]: avg_val

Out[3]: 2.5

 

In [4]: my_avg(2, 4)

Out[4]: 3.0

 

 

 

큰 따옴표 세개(""" Docstring """)로 사용자 정의 함수를 설명하는 Docstring을 추가하고, ?함수이름으로 Docstring을 불러와서 참고해보겠습니다.

 

 

def my_avg(x1, x2):

    """

    This function calculates average value of two numbers

    x1: first input number

    x2: second input number

    """

    avg_val = (float(x1) + float(x2))/2

 

    return avg_val


 

In [6]: ?my_avg()

Signature: my_avg(x1, x2)

Docstring:

This function calculates average value of two numbers

x1: first input number

x2: second input number

File: c:\users\admin\<ipython-input-5-d14cc1c6028b>

Type: function

 

 

 

사용자 정의 함수의 코드 블록에 if ~ else 조건문을 추가하여 좀더 복잡한 작업을 할 수도 있습니다. 위의 두 숫자를 input으로 받아서 평균을 반환하는 사용자 정의 함수에다가 '정수(integer) 혹은 부동소수형(float)'이 아니면 "This is not a number" 라는 메시지를 반환하도록 하는 조건문 코드를 추가해보겠습니다.

 

사용자정의함수와 조건문을 같이 쓰므로 콜론(:)으로 코드블록이 시작됨을 알려주고 들여쓰기(indentation)에 주의를 기울여야 합니다.

 

 

def my_avg(x1, x2):

    """

    This function calculates average value of two numbers

    x1: first input number

    x2: second input number

    """

    

    if isinstance(x1, (int, float)) and isinstance(x2, (int, float)):

        avg_val = (float(x1) + float(x2))/2

        return avg_val

    else:

        print("This is not a number")

      


In [8]: my_avg(2, 5)

Out[8]: 3.5


In [9]: my_avg('2', 5)

This is not a number

 

 

많은 도움이 되었기를 바랍니다.

 

Posted by R Friend R_Friend

댓글을 달아 주세요