함수

파이썬과 같은 프로그래밍 언어에서 함수란 수학에서의 함수 의미와 약간 다릅니다. 함수란 프로그램에서 언제든지 어디서든지 실행할 수 있는 문장들을 모아 놓은 집합입니다.

함수

수학 함수 표현

섭씨를 화씨로 변환하는 수학 함수는 다음과 같습니다.

\[F(C) = \frac{9}{5} C + 32\]

이것을 파이썬 함수로 표현하면 다음과 같습니다.

In [1]: def F(C):
   ...:   return 9 / 5 * C + 32
   ...: 

C를 입력받아 F(C)를 반환하는 프로그램입니다.

모든 파이썬 함수는 def로 시작하고 함수 이름 소괄호 ()를 하고 콜론 : 으로 머리부분(header)을 정의합니다. 소괄호 안에는 입력 매개변수를 쉼표를 이용하여 나열할 수 있습니다. 이러한 입력값을 인자(argument)라고도 합니다. 다음은 다음 줄에 들여쓰기를 하여 몸통부분(body)을 정의합니다. 몸통 부분에 return 문을 이용하여 반환하고자 하는 값을 적습니다. return 문을 만나면 함수는 종료하게 됩니다. 몸통 부분을 끝내려면 들여쓰기를 멈춥니다.

함수를 사용하려면 정의된 함수를 호출해야합니다. 호출하는 방법은 함수이름과 소괄호를 이용합니다.

In [2]: a = F(10)
   ...: print(a)
   ...: print(a + F(30))
   ...: 
50.0
136.0

반복문을 이용해서 다음과 같이 여러 번 호출하여 리스트를 만들수도 있습니다.

In [3]: Cdegrees = [-15, 0, 10, 20, 30]
   ...: Fdegrees = [F(C) for C in Cdegrees]
   ...: Fdegrees
   ...: 
Out[3]: [5.0, 32.0, 50.0, 68.0, 86.0]

반환값을 문자열로 하는 함수를 다음과 같이 정의할 수 있습니다.

In [4]: def F1(C):
   ...:   Fval = 9 / 5 * C + 32
   ...:   return '섭씨: {}, 화씨: {}'.format(C, Fval)
   ...: 
   ...: print(F1(20))
   ...: 
섭씨: 20, 화씨: 68.0

프로그램 흐름 이해

프로그램 실행 순서를 이해하는 것이 중요합니다.

In [5]: def F(C):
   ...:   Fval = 9 / 5 * C + 32
   ...:   return Fval
   ...: 
   ...: dC = 10
   ...: C = -30
   ...: while C < 50:
   ...:   print('섭씨:{:5.1f}, 화씨: {:5.1f}'.format(C, F(C)))
   ...:   C += dC
   ...: 
섭씨:-30.0, 화씨: -22.0
섭씨:-20.0, 화씨:  -4.0
섭씨:-10.0, 화씨:  14.0
섭씨:  0.0, 화씨:  32.0
섭씨: 10.0, 화씨:  50.0
섭씨: 20.0, 화씨:  68.0
섭씨: 30.0, 화씨:  86.0
섭씨: 40.0, 화씨: 104.0

지역/전역 변수

함수 안에서 정의된 변수를 지역변수(local variable)이라고 합니다. 지역변수는 함수 밖에서는 접근할 수 없습니다.

함수 F() 에서 정의된 변수 Fval은 지역변수이고 이것을 함수밖에서 호출하면 에러가 발생합니다.

In [6]: F(30)
   ...: Fval
   ...: 
        NameError: name 'Fval' is not defined

반면에 함수 밖에서 정의된 변수를 전역변수(global variable)이라고 합니다.

In [7]: def F2(C):
   ...:   Fval = 9 / 5 * C + 32
   ...:   print('함수 F2() 안: C: {}, Fval: {}, r: {}'.format(C, Fval, r))
   ...:   return Fval
   ...: 
   ...: C = 60
   ...: r = 21
   ...: F2(r)
   ...: 
함수 F2() 안: C: 21, Fval: 69.80000000000001, r: 21
Out[7]: 69.80000000000001

위에서 변수 C는 함수 안에서는 지역변수이고 밖에서는 60을 갖는 전역변수입니다. 함수 안에 있는 C는 지역변수로서 전역변수 C를 가리는 역할을 합니다. 함수 안에서 C값을 변경해도 전역변수 C는 영향을 받지 않습니다.

변수 검색 규칙은 먼저 지역변수를 찾고, 없으면 전역변수를 찾습니다. 그래도 못찾으면 내장변수가 있는지를 찾고 없으면 에러를 발생시킵니다.

1. 지역변수
2. 전역변수
3. 내장된 이름

다음 예제를 살펴봅니다.

In [8]: print(sum)          # 내장 함수 sum
   ...: sum = 500           # 전역변수 sum 정의
   ...: print(sum)          # 전역 sum
   ...: 
   ...: def myfunc(n):
   ...:   sum = n + 1       # 지역변수 sum 정의
   ...:   print(sum)
   ...:   return sum
   ...: 
   ...: sum = myfunc(2) + 1 # 전역변수 sum
   ...: print(sum)
   ...: del sum
   ...: 
<built-in function sum>
500
3
4

global 키워드를 사용해서 함수 안에서 전역변수의 값을 변경할 수도 있습니다. 하지만 어쩔수없는 경우를 제외하고는 함수안에서 전역변수 값을 변경하는 것을 피해야합니다.

In [9]: a = 20; b = -2.5   # 전역변수들
   ...: def f1(x):
   ...:   a = 21           # 지역변수
   ...:   return a * x + b
   ...: 
   ...: print('함수 실행전 a:', a) # 20 출력
   ...: 
   ...: def f2(x):
   ...:   global a         # 전역변수 a 사용
   ...:   a = 21           # 전역변수 a 값 변경
   ...:   return a * x + b
   ...: 
   ...: f1(3)
   ...: print('함수 f1(3) 실행 후 a:', a) # 20 출력
   ...: f2(3)
   ...: print('함수 f2(3) 실행 후 a:', a) # 21 출력
   ...: 
함수 실행전 a: 20
함수 f1(3) 실행 후 a: 20
함수 f2(3) 실행 후 a: 21

여러 개 인자들

함수는 여러 개의 인자들을 취할 수 있습니다. 예를 들어 다음과 같은 함수를 생각해봅니다.

\[y(t) = v_0 t - \frac{1}{2} g t^2\]

g는 상수, \(v_0\)는 변하는 수라고 가정합니다. \(y(t)\)는 수학적으로는 t에 대한 함수이지만 \(v_0\)가 변함에 따라 y 값이 변하는 것을 알 수 있습니다. 파이썬에서는 이러한 함수를 2개의 인자를 입력받아 정의할 수 있습니다.

In [10]: def yfunc(t, v0):
   ....:   g = 9.8
   ....:   return v0 * t - 0.5 * g * t ** 2
   ....: 

함수 호출은 다음과 같이 합니다.

In [11]: yfunc(0.1, 6)
   ....: yfunc(0.1, v0=6)
   ....: yfunc(t=0.1, v0=6)
   ....: yfunc(v0=6, t=0.1)
   ....: 
Out[11]: 0.551

인자=값 형태로 인자를 넘겨줄 수도 있습니다. 이러한 인자를 키워드인자라고 부릅니다. 이럴 때는 순서가 바뀌어도 상관없습니다. 하지만 일반 인자와 키워드인자가 동시에 포함된 인자들은 키워드인자가 일반 인자 뒤에 위치해 있어야 합니다.

다음은 키워드인자가 일반 인자보다 앞에 있기때문에 에러를 발생합니다.

In [12]: yfunc(v0=6, 0.1) # 에러
  File "<ipython-input-12-eaaa44c187b0>", line 1
    yfunc(v0=6, 0.1) # 에러
               ^
SyntaxError: positional argument follows keyword argument

수학 함수를 넘어

앞에서는 수학 함수를 파이썬 함수로 표현하는 것을 보았습니다. 파이썬 함수는 수학함수를 넘어 일련의 문장을 반복해서 실행할 필요가 있을 때 사용됩니다.

예를 들어 파이썬 내장 함수 range와 비슷한 기능을 하는 함수를 만들어 봅니다. 즉, 시작, 끝, 간격을 입력받아 리스트를 반환하는 함수를 작성해봅니다.

In [13]: def makelist(start, stop, inc):
   ....:   value = start
   ....:   result = []
   ....:   while value <= stop:
   ....:     result.append(value)
   ....:     value = value + inc
   ....:   return result
   ....: 
   ....: mylist = makelist(0, 1, 0.2)
   ....: print(mylist)
   ....: 
[0, 0.2, 0.4, 0.6000000000000001, 0.8, 1.0]

range 함수는 정수형 리스트만 반환하는 반면 makelist는 실수형 리스트도 반환할 수 있습니다.

여러 개의 반환값

다음과 같이 함수값 및 그것의 미분값도 동시에 얻고 싶다고 해봅니다.

\[\begin{split}y(t) = v_0 t - \frac{1}{2} g t^2 \\ y'(t) = v_0 - gt\end{split}\]

\(y, y'\) 값을 동시에 반환하려면 튜플 형태로 반환하면 됩니다.

In [14]: def yfunc2(t, v0):
   ....:   g = 9.8
   ....:   y = v0 * t - 0.5 * g * t ** 2
   ....:   dydt = v0 - g * t
   ....:   return y, dydt
   ....: 

함수값을 받을 때도 순서대로 쉼표를 이용하여 받습니다.

In [15]: pos, vel = yfunc2(0.6, 3)
   ....: print(pos, vel)
   ....: 
0.03599999999999981 -2.88

또는 다음과 같이 튜플 변수에 저장하여 사용할 수도 있습니다.

In [16]: tup = yfunc2(0.6, 3)
   ....: print(tup)
   ....: 
(0.03599999999999981, -2.88)

\(t\), \(y(t)\), \(y'(t)\)에 대한 표를 for 문을 이용하여 얻을 수 있습니다.

In [17]: t_vals = [0.05 * i for i in range(10)]
   ....: for t in t_vals:
   ....:   pos, vel = yfunc2(t, v0=5)
   ....:   print('t={:<10g} pos={:<10g} velocity={:<10g}'.format(t, pos, vel))
   ....: 
t=0          pos=0          velocity=5         
t=0.05       pos=0.23775    velocity=4.51      
t=0.1        pos=0.451      velocity=4.02      
t=0.15       pos=0.63975    velocity=3.53      
t=0.2        pos=0.804      velocity=3.04      
t=0.25       pos=0.94375    velocity=2.55      
t=0.3        pos=1.059      velocity=2.06      
t=0.35       pos=1.14975    velocity=1.57      
t=0.4        pos=1.216      velocity=1.08      
t=0.45       pos=1.25775    velocity=0.59      

{:<10g}은 왼쪽 정렬 10자리 표현식입니다.

합계산

\(\sin x\) 함수의 근사값을 구하는 함수를 작성하려고 합니다.

\[\sin(x) = x - \frac{1}{3!} x^3 + \frac{1}{5!} x^5 \cdots\]

\(x\)에서 \(n\)항까지 더했을 때의 근사값을 구하려고 합니다.

\[\text{sinSum}(x; n) = \sum_{k=1}^n \frac{(-1)^{k+1}}{(2k-1)!} x^{2k-1}\]
In [18]: def sinSum(x, n):
   ....:   import math
   ....:   sum = 0
   ....:   sign = 1
   ....:   for i in range(1, n+1):
   ....:     sum += sign * x ** (2 * i - 1) / math.factorial(2 * i -1)
   ....:     sign *= -1
   ....:   return sum
   ....: 

약간 바꿔서 정확한 오차와 근사 오차를 함께 반환하도록 하겠습니다.

In [19]: def sinSum2(x, n):
   ....:   import math
   ....:   sum = 0
   ....:   sign = 1
   ....:   for i in range(1, n+1):
   ....:     sum += sign * x ** (2 * i - 1) / math.factorial(2 * i -1)
   ....:     sign *= -1
   ....:   approx_error = sign * x ** (2 * n + 1) / math.factorial(2 * n + 1)
   ....:   exact_error = math.sin(x) - sum
   ....:   return sum, approx_error, exact_error
   ....: 

함수를 호출합니다. 다음과 \(x=1\)에서 오차가 거의 없는 것을 알 수 있습니다.

In [20]: x = 1
   ....: val, approx_error, exact_error = sinSum2(x, 10)
   ....: print('합: {:20.18f}\n근사오차: {:12.10E}\n오차: {:12.10E}'.format(val, approx_error, exact_error))
   ....: 
합: 0.841470984807896505
근사오차: 1.9572941063E-20
오차: 0.0000000000E+00

\(x=10\)에서 값을 보면 오차가 많아서 값으로 사용할 수 없는 것을 알 수 있습니다.

In [21]: x = 10
   ....: val, approx_error, exact_error = sinSum2(x, 10)
   ....: print('합: {:20.18f}\n근사오차: {:12.10E}\n오차: {:12.10E}'.format(val, approx_error, exact_error))
   ....: 
합: -16.811850137411582295
근사오차: 1.9572941063E+01
오차: 1.6267829027E+01

\(x=100\)에서는 더욱 더 많이 나는 것을 알 수 있습니다.

In [22]: x = 100
   ....: val, approx_error, exact_error = sinSum2(x, 10)
   ....: print('합: {:20.18f}\n근사오차: {:12.10E}\n오차: {:12.10E}'.format(val, approx_error, exact_error))
   ....: 
합: -794697857233433001984.000000000000000000
근사오차: 1.9572941063E+22
오차: 7.9469785723E+20

이것은 테일러 다항식을 \(x=0\)에서 전개했기 때문에 0으로부터 멀리 있는 값은 발산할 수 밖에 없습니다. 이럴 때는 항의 갯수를 더 늘려서 근사값을 사용해야 합니다.

얼마나 많은 차이가 나는지를 알아보기 위해서 함수 자체에서 출력을 하도록 프로그램을 해보도록 합니다.

return 문이 없는 함수

앞에서 정의한 sinSum2 함수를 이용해 함수 안에서 출력을 하도록 새로운 함수 table를 작성하도록 하겠습니다.

In [23]: def table(x):
   ....:   import math
   ....:   print('x={}, sin(x)={}'.format(x, math.sin(x)))
   ....:   for n in [1, 2, 10, 100, 200]:
   ....:     val, approx, error = sinSum2(x, n)
   ....:     print('n={:5d} 합: {:< 15.4e} 근사오차: {:< 15.4e} 오차: {:< 15.4e}'.format(n, val, approx, error))
   ....: 

출력 포맷 {:< 15.4e}에서 <는 좌측 정렬을 의미하고 다음에 나오는 스페이스(빈 칸)는 숫자의 부호가 양이면 한 칸 띄워서 음일 때와 자리를 마추는 역할을 합니다.

다음은 \(x=10\)일 때 각각의 오차를 나열한 것입니다. 오차가 항의 갯수가 증가함에 따라 감소하는 것을 볼 수 있습니다.

In [24]: table(10)
x=10, sin(x)=-0.5440211108893698
n=    1 합:  1.0000e+01     근사오차: -1.6667e+02     오차: -1.0544e+01    
n=    2 합: -1.5667e+02     근사오차:  8.3333e+02     오차:  1.5612e+02    
n=   10 합: -1.6812e+01     근사오차:  1.9573e+01     오차:  1.6268e+01    
n=  100 합: -5.4402e-01     근사오차:  6.3083e-177    오차: -9.9809e-14    
n=  200 합: -5.4402e-01     근사오차:  0.0000e+00     오차: -9.9809e-14    

n=100 이상일 때 근사오차가 실제 오차보다 작은 것으로 나오는 것은 파이썬 실수 표현 방식에서 유효숫자를 나타내는 자릿수가 정해져 있기 때문입니다.

table() 함수는 return 문이 존재하지 않는 함수입니다. return 문이 존재하지 않는 함수는 None이라는 파이썬 객체를 반환합니다.

In [25]: res = table(10)
   ....: res == None
   ....: 
x=10, sin(x)=-0.5440211108893698
n=    1 합:  1.0000e+01     근사오차: -1.6667e+02     오차: -1.0544e+01    
n=    2 합: -1.5667e+02     근사오차:  8.3333e+02     오차:  1.5612e+02    
n=   10 합: -1.6812e+01     근사오차:  1.9573e+01     오차:  1.6268e+01    
n=  100 합: -5.4402e-01     근사오차:  6.3083e-177    오차: -9.9809e-14    
n=  200 합: -5.4402e-01     근사오차:  0.0000e+00     오차: -9.9809e-14    
Out[25]: True

키워드 인자

함수를 정의할 때 기본값을 지정하면 편리할 때가 많습니다. 기본값을 지정하면 해당 인자를 입력하지 않아도 자동으로 기본값이 지정이 됩니다.

In [26]: def somefunc(arg1, arg2, kwarg1=True, kwarg2=0):
   ....:   print(arg1, arg2, kwarg1, kwarg2)
   ....: 

다음과 같이 호출할 수 있습니다. 기본값이 지정된 인자들 kwarg1, kwarg2를 키워드 인자들이라고 합니다.

함수를 호출할 때 키워드 인자를 생략하면 기본값들이 설정되어 계산됩니다.

다음은 키워드인자를 넣지 않은 경우입니다.

In [27]: somefunc('Hello', [1, 2])
Hello [1, 2] True 0

키워드 인자를 하나만 입력한 경우입니다. 다른 키워드인자는 기본값이 설정됩니다.

In [28]: somefunc('Hello', [1, 2], kwarg1='Hi')
Hello [1, 2] Hi 0

키워드인자에 기본값과 다른 객체가 대입되도 상관없는 것을 알 수 있습니다.

In [29]: somefunc('Hello', [1, 2], kwarg2='Hi')
Hello [1, 2] True Hi

순서를 바꿔 입력해도 상관없습니다.

In [30]: somefunc('Hello', [1, 2], kwarg2='Hi', kwarg1=65)
Hello [1, 2] 65 Hi

다음과 같은 함수를 키워드인자를 이용해서 기본값을 설정하여 정의할 수 있습니다.

\[f(t; A, a, \omega) = Ae^{-at} \sin (\omega t)\]
In [31]: import math
   ....: def f(t, A=1, a=1, omega=2 * math.pi):
   ....:   return A * math.exp(-a * t) * math.sin(omega * t)
   ....: 

다음과 같이 불러서 사용합니다. 아래는 \(e^{-0.2}\sin(2\pi \cdot 0.2)\)를 구하는 것이됩니다.

In [32]: v1 = f(0.2)

만일 \(e^{-1}\sin(\pi)\)와 같은 식을 구하려면 다음과 같이 입력하면 됩니다.

In [33]: v2 = f(1, omega=math.pi)

합을 구할 때 기본 임계값을 설정하면 편리합니다.

In [34]: def sinSum3(x, epsilon=1.0E-6):
   ....:   import math
   ....:   sign = 1
   ....:   n = 1
   ....:   term = x ** (2 * n - 1) / math.factorial(2 * n -1)
   ....:   sum = term
   ....:   while term > epsilon:
   ....:     n += 1
   ....:     sign *= -1
   ....:     term = x ** (2 * n - 1) / math.factorial(2 * n - 1)
   ....:     sum += sign * term
   ....:   return sum, n
   ....: 

위 함수는 주어진 임계값보다 근사오차가 작을 때까지 더하기를 계속 합니다.

In [35]: def table2(x):
   ....:   for k in range(4, 14, 2):
   ....:     epsilon = 10 ** (-k)
   ....:     approx, n = sinSum3(x, epsilon)
   ....:     exact = math.sin(x)
   ....:     exact_error = exact - approx
   ....:     print('epsilon: {:.1e}, exact error: {:< .2e}, n={:3d}'.format(epsilon, exact_error, n))
   ....: 

다음과 같이 출력됩니다.

In [36]: table2(10)
epsilon: 1.0e-04, exact error:  6.80e-07, n= 18
epsilon: 1.0e-06, exact error: -4.62e-08, n= 19
epsilon: 1.0e-08, exact error: -1.58e-10, n= 21
epsilon: 1.0e-10, exact error: -4.71e-13, n= 23
epsilon: 1.0e-12, exact error: -8.40e-14, n= 24

함수 객체 인자

함수도 파이썬 객체이므로 인자로 넘겨줄 수 있습니다. 미적분학 문제를 풀 때에 종종 함수를 인자로 넘겨주면 편리할 때가 많습니다.

예를 들면

  • 수치적으로 \(f(x) = 0\)의 해를 구할 때

  • 수치적으로 미분 \(f'(x)\)를 구할 때

  • 수치적으로 적분 \(\int_a^b f(x) dx\)를 구할 때

  • 수치적으로 미분방정식 \(\frac{df}{dt} = f(x)\)를 풀 때

등 함수 \(f(x)\)에 따라 원하는 답이 달라지기 때문에 함수를 인자로 넘겨줘야 합니다.

2계 미분을 예들 들어 봅니다. 이차 미분은 다음과 같이 수치적으로 미분할 수 있습니다.

\[f''(x) \approx \frac{f(x-h) - 2f(x) + f(x+h)}{h^2}\]

여기서 \(h\)는 작은 수입니다. 우변은 \(h \to 0\) 이면 \(f''(x)\)로 수렴하는 것을 알 수 있습니다.

위 식을 함수로 정의하면 다음과 같습니다.

In [37]: def diff2nd(f, x, h=1.E-6):
   ....:   r = (f(x-h) - 2 * f(x) + f(x+h)) / h ** 2
   ....:   return r
   ....: 

여기서 f는 미분할 함수이고 x\(f(x)\)x 입니다.

diff2ndf를 함수로 정의해서 넘겨주면 됩니다.

다음은 \(g(t) = t^{-6}\) 함수를 파이썬으로 정의한 것입니다.

In [38]: def g(t):
   ....:   return t ** (-6)
   ....: 

수치미분을 적용하면 다음과 같습니다.

In [39]: t = 1.2
   ....: d2g = diff2nd(g, t)
   ....: print('t={:.1f}, d2g={:.5f}'.format(t, d2g))
   ....: 
t=1.2, d2g=9.76780

\(t=1\)에서 \(h \to 0\)를 변해가며 미분값을 출력해 봅니다.

In [40]: for k in range(1, 15):
   ....:   h = 10 ** (-k)
   ....:   d2g = diff2nd(g, 1, h)
   ....:   print('h={:.1e}, {:.5f}'.format(h, d2g))
   ....: 
h=1.0e-01, 44.61504
h=1.0e-02, 42.02521
h=1.0e-03, 42.00025
h=1.0e-04, 42.00000
h=1.0e-05, 41.99999
h=1.0e-06, 42.00074
h=1.0e-07, 41.94423
h=1.0e-08, 47.73959
h=1.0e-09, -666.13381
h=1.0e-10, 0.00000
h=1.0e-11, 0.00000
h=1.0e-12, -666133814.77509
h=1.0e-13, 66613381477.50939
h=1.0e-14, 0.00000

\(h < 10^{-8}\) 보다 작으면 계산된 미분값이 발산하는 것을 알 수 있습니다. 이것은 앞에서도 이야기했듯이 파이썬 실수형 자릿수를 정확히 표시할 수 있는 숫자는 \(10^{-16}\)이기 때문에 이것 보다 작은 수는 부정확한 계산이 되어 믿을 수 없는 계산이 됩니다. 그리고 분자가 값이 0에 가까워서 유효숫자가 상실되기 때문에 부정확한 계산이 됩니다.

따라서 이러한 것을 보완하기 위해서는 유효자릿수를 늘려 계산하는 수밖에 없습니다. 파이썬 decimal 모듈이 이러한 것을 해결할 수 있도록 원하는 자릿수에서 계산할 수 있도록 합니다.

lambda 함수

함수를 한 줄에 정의할 수 있는 파이썬 예약어가 lambda 입니다.

In [41]: f = lambda x: x ** 2 + 4

위 함수는 다음과 똑 같습니다.

In [42]: def f(x):
   ....:   return x ** 2 + 4
   ....: 

인자가 여러 개일 때는 다음과 같이 정의합니다.

g = lambda arg1, arg2, ...: 표현식

\(g(x, y) = x^y\)은 다음과 같이 정의할 수 있습니다.

In [43]: g2 = lambda x, y: x ** y
   ....: 
   ....: g2(2, 3)
   ....: 
Out[43]: 8

따라서 람다 함수를 이용해서 diff2nd를 이용할 수 있습니다.

In [44]: g = lambda x: x ** (-6)
   ....: diff2nd(g, 1)
   ....: 
Out[44]: 42.000736222291835

조건문

다음과 같이 조건에 따라 함수를 정의하는 경우에 대해서 알아봅니다.

\[\begin{split}f(x) = \begin{cases} \sin(x), & 0 \le x \le \pi \\ 0, & \text{otherwise} \end{cases}\end{split}\]

if-else 문

일반적인 if-else 문은 다음과 같습니다.

if 조건:
  조건이 True일  실행되는 구역
else:
  조건이 False일  실행되는 구역
In [45]: import math
   ....: def f(x):
   ....:   if 0 <= x <= math.pi:
   ....:     val = math.sin(x)
   ....:   else:
   ....:     val = 0
   ....:   return val
   ....: 

else ifelif로 사용해야 합니다.

if condition1:
  <block of statements>
elif condition2:
  <block of statements>
elif condition3:
  <block of statements>
else:
  <block of statements>
<next statement>

다음과 같은 hat 함수

\[\begin{split}N(x) = \begin{cases} 0, & x < 0 \\ x, & 0 \le x < 1 \\ 2 - x, & 1 \le x < 2 \\ 0, & x \ge 2 \end{cases}\end{split}\]
In [46]: def N(x):
   ....:   if x < 0:
   ....:     return 0.0
   ....:   elif 0 <= x < 1:
   ....:     return x
   ....:   elif 1 <= x < 2:
   ....:     return 2 - x
   ....:   elif x >= 2:
   ....:     return 0.0
   ....: 

더 짧은 형태로 코들 작성할 수 있습니다.

In [47]: def N(x):
   ....:   if x <= 0 < 1:
   ....:     return x
   ....:   elif 1 <= x < 2:
   ....:     return 2 - x
   ....:   else:
   ....:     return 0.0
   ....: 

if 한 줄 표현식

다음과 같은 if-else 문장을 한 줄 표현식으로 바꿀 수 있습니다.

if condition:
  a = value1
else:
  a = value2
a = (value1 if condition else value2)

여기서 소괄호는 선택사항으로 있어도되고 없어도 되지만 권장사항입니다.

예를 들면,

def f(x):
  return (math.sin(x) if 0 <= x <= 2 * math.pi else 0)

이것을 lambda 함수를 이용하면 한 줄에 표현할 수 있습니다.

f = lambda x: math.sin(x) if 0 <= x <= 2 * math.pi else 0

lambda 함수 안에 일반적인 if-else 문을 사용할 수 없습니다. 람다함수는 한 개의 표현식만 가질 수 있습니다.

연습문제

  1. 다음 수학 함수를 파이썬 함수 g(t) 로 구현하고 g(0), g(1)을 구하세요.

    \[g(t) = e^{-t} \sin(\pi t)\]
  2. 위 문제의 수학 함수를 약간 변형해서 매개변수가 들어간 다음 함수를 파이썬 함수로 작성하시오. 파이썬 함수를 만들 때 a를 매개변수(인자)로 갖도록 작성하시오. 그리고 a=10 일 때 \(h(0), h(1)\)의 값을 구하시오.

    \[h(t) = e^{-at} \sin(\pi t)\]
  3. 다음 프로그램이 작동하는 순서를 자세히 설명하시오.

    def add(A, B):
      C = A + B
      return C
    
    a = 3
    b = 2
    print(add(a, b))
    print(add(2 * a, b + 1) * 3)
    
  4. 화씨를 섭씨로 변환하는 함수

    \[C = \frac{5}{9}(F - 32)\]

    를 파이썬 함수 F(C)로 작성하고 이것의 역함수 C(F)도 파이썬 함수로 작성하세요. 그리고 작성한 함수들이 역함수가 맞는지 F(C(f)), C(F(c))의 값을 확인하여 보세요.

  5. \(s = \sum_{k=1}^M \frac{1}{k}\)의 합 \(s\)를 구하는 파이썬 함수 sum1(M)을 작성하고 M=3 일 때 값을 손으로 구한 값과 비교해보세요.

  6. 이차방정식 \(ax^2 + bx + c =0\)의 해를 구하는 파이썬 함수 roots(a, b, c)를 작성하세요. 실수근을 가지면 float 형을, 복소수근을 가지면 complex 형을 반환하도록 작성하세요.

  7. \(n+1\)개의 해 \(r_0, r_1, \ldots, r_n\)을 갖는 \(n+1\) 차 다항식은 다음과 같이 구할 수 있습니다.

    \[p(x) = \prod_{i=0}^n (x - r_i) = (x-r_0)(x-r_1) \cdots (x -r_n).\]

    이것에 대한 파이썬 함수 poly(x, roots)를 작성하시오. roots는 해 리스트입니다.

  8. 계단함수 또는 Heaviside 함수라고 알려진 다음 함수

    \[\begin{split}H(x) = \begin{cases} 0, & x < 0 \\ 1, & x \ge 0 \end{cases}\end{split}\]

    를 파이썬 함수 H(x)로 작성하고 \(H(-10), H(-10^{-15}), H(0), H(10^{-15}), H(10)\)의 값을 구하세요.

  9. 위에서 정의한 Heaviside 함수는 0에서 불연속인 것을 알 수 있습니다. 이러한 것을 극복하기 위해 수정된 미분가능한 Heaviside를 다음과 같이 정의해서 사용하기도 합니다.

    \[\begin{split}H_{\epsilon}(x) = \begin{cases} 0, & x < -\epsilon \\ \frac{1}{2} + \frac{x}{2\epsilon} + \frac{1}{2\pi}\sin\left( \frac{\pi x}{\epsilon} \right), & -\epsilon \le x \le \epsilon \\ 1, & x > \epsilon \end{cases}\end{split}\]

    위의 수정된 함수 \(H_\epsilon(x)\)를 파이썬 함수 H_eps(x, eps=0.01)으로 구현하고 \(x < -\epsilon\), \(x = -\epsilon\), \(x = 0\), \(x = \epsilon\), \(x > \epsilon\) 각각의 경우에 대해서 값을 확인해보세요.

  10. 많은 응용 문제에서 다음과 같은 indicator 함수를 사용합니다. indicator 함수란 주어진 구간 \([L, R]\)에서는 1이고 나머지에서는 0을 갖는 함수를 뜻합니다.

    \[\begin{split}I(x; L, R) = \begin{cases} 1, & x \in [L, R] \\ 0, & \text{otherwise} \end{cases}\end{split}\]

    indicator 함수를 파이썬 함수 indicator(x, L=-1, R=1)로 직접 작성해보세요. 그리고 indicator 함수는 다음과 같이 Heaviside 함수들을 이용해서도 구할 수 있습니다.

    \[I(x; L, R) = H(x - L) H(R - x)\]

    Heaviside 함수들의 곱으로 정의한 것을 파이썬 함수 indicator2(x, L=-1, R=1)로 작성하고 두 함수를 비교해 보세요. \(x < L\), \(x = L\), \(x = (L + R)/2\), \(x = R\), \(x > R\) 일 때에 대해서 값을 확인해 보세요.

연습문제 풀이