Chapter 05 함수
- 함수 기초
- 함수 심화
- 함수의 인수
- 좋은 코드를 작성하는 방법
01. 함수 기초
“여러 명이 프로그램을 함께 개발할 때, 코드를 어떻게 작성하면 좋을까?” 팀원들이 각자 해야 할 부분을 나누고 나중에 합치는 것이 가장 일반적이고 많이 사용하는 방법인데, 이번 장에서 배울 함수라는 개념은 바로 이 방법을 사용할 수 있도록 도와준다.
프로그램을 작성해야 하는 부분을 나누는 방법에는 무엇이 있을까? 파이썬에서는 이를 위해 함수 객체, 모듈을 제공한다. 먼저 이번 장에서 함수의 개념을 배우고, 나중에 객체와 모듈에 대한 개념을 배울 것이다
[1] 함수의 개념과 장점
함수 function 란 무엇일까?
함수는 어떤 일을 수행하는 코드의 덩어리, 또는 코드의 묶음이라고 표현할 수 있다.
예를 들어, 도형의 넓이를 구하는 프로그램에서 사각형의 넓이를 구하는 과업이 있다면, 해당 과업을 함수화하여 필요할 때마다 호출하는 것이다.
다음과 같은 장점이 있다.
- 필요할 때 마다 호출 가능 : 반복적으로 수행해야 하는 업무를 단 한 번만 작성하여 필요할 때 마다 호출 가능
- 논리적인 단위로 분할 가능 : 단순 도형 계산 프로그램을 작성하더라도, 곱셈 수행 코드, 나눗셈 수행 코드 등으로 나눌 수 있음
- 코드의 캡슐화 :
- (캡슐화는 함수의 인터페이스만 잘 정의되면 다른 사람이 자신의 코드를 쉽게 가져다가 사용할 수 있는 특징이다.) )(인터페이스를 정의한다는 것은 코드에 입력되는 입력input 값과 코드의 수행 결과인 출력 Output 값을 명확히 한다는 것이다.) 인터페이스가 잘 정의된 코드라면 코드의 내부 구조를 몰라도 함수를 굉장히 쉽게 사용할 수 있음
[2] 함수의 선언
#파이썬에서 다음과 같이 함수를 선언할 수 있다.
def 함수 이름 (매개변수 #1 …):
수행문 1
수행문 2
return <반환값>
- def : ‘definition’의 줄임말. 함수를 정의하여 시작한다는 의미.
- 함수이름 (사용자정의함수 함수명 작성규칙):
- 소문자로 입력
- 공백 안됨, _ 사용가능. 그외 특수기호 안됨
- 숫자가 처음에 올수 없음
- 행위를 기록하므로 동사와 명사를 사용하는 경우가 많음 (ex. find_number)
- 예약어 사용불가
- 매개변수(parameter)
: 함수에서 입력값으로 사용하는 변수, 1개 이상의 값을 적을 수 있음
수행문if for 같은 제어문을 사용할 수도 있고, 고급 프로그래밍을 하게 되면 함수 안에 함수를 사용하기도 함.
:수행문은 반드시 들여쓰기한 후 코드를 입력해야함. - ‘반환’
: 파이썬의 함수도 같은 개념이다. 수학에서 x에 해당하는 것이 매개변수, 즉 입력값이고 x +1의 계산 과정이 함수 안의 코드이며, 그 결과가 출력값이다.
: 이는 수학에서의 함수와 같은 개념이라고 생각하면 된다. 예를 들어, 수학에서 f(x) = x + 10이라고 한다면 f(1)=2다. 즉 함수 f(x)에서 x에 10이 들어가면 2가 반환되는 것이다.
[3]함수의 실행순서
함수를 선언했다면, 다음으로 함수를 호출하여 사용해야 한다.
#함수의 실행 순서
def calculate_rectangle_area(x, y):
return x * y
rectangle_x = 10
rectangle_y = 20
print("사각형 X의 길이 :", rectangle_x)
print("사각형 y의 길이:", rectangle_y)
#넓이를 구하는 함수 호출
print("사각형의 넓이:", calculate_rectangle_area(rectangle_x, rectangle_y))
''' rectangle_x와 rectangle_y 변수 2개에 할당된값이 calculate_rectangle_area 함수에 입력값이 된다.
그러면 함수에 있는 코드 return x*y에 의해 반환값 200이 반환된 것이 출력된다.'''
[4]프로그래밍의 함수와 수학의 함수
#함수 f(x) = x+1을 코드로 나타내면?
def f(x):
y = x+1
return y
다음과 같은 문제가 있다면 프로그래밍에서 코드의 함수로 어떻게 표현할 수 있을까?
f(x) = 2x + 7, g(x) = x 이고 x = 2일 때 f(x) + g(x) + f(g(x)) + g(f(x))의 값은?
f(2) = 11, g(2) = 4, f(g(x)) = 15, g(f(x)) = 121
11 +4 + 15 + 121 = 151
# 함수 f(x)를 정의합니다. f(x) = 2x + 7
def f(x):
return 2 * x + 7
# 함수 g(x)를 정의합니다. g(x) = x
def g(x):
return x
# x에 2를 대입합니다.
x = 2
# f(x) + g(x) + f(g(x)) + g(f(x))를 계산하여 result에 저장합니다.
result = f(x) + g(x) + f(g(x)) + g(f(x))
# 결과 출력
print(result) # 151이 출력됩니다.
[5] 함수의 형태
매개변수 유무 반환값 유무 설명
매개변수 있음 | 반환값 있음 | 매개변수를 활용하여 수행문 수행하고, 결과를 반환 |
매개변수 있음 | 반환값 없음 | 매개변수를 활용하여 수행문 수행하고, |
매개변수 없음 | 반환값 있음 | 매개변수 없이 수행문 수행하고, 결과를 반환 |
매개변수 없음 | 반환값 없음 | 함수 안 수행문만 수행함, 반환값 없음 |
#함수의 형태 - 매개변수와 반환값 유무
def a_rectangle_area(): # 매개변수 X 반환값 X
print(5 * 7)
def b_rectangle_area(x, y): # 매개변수 O 반환값 X
print(x * y)
def c_rectangle_area(): # 매개변수 X 반환값 O
return(5 * 7)
def d_rectangle_area(x,y): # 매개변수 O 반환값 O
return(x * y)
a_rectangle_area() #print(5 * 7)
b_rectangle_area(5, 7) #매개변수 넘겨받아 계산
print(c_rectangle_area())
print(d_rectangle_area(5, 7))
#반환값 있는 경우에는 함수 호출하는 곳에서 반환값을 변수에 할당하여 사용하는 것 가능
#전부 35
# 매개변수 X 반환값 X . 단순히 5와 7을 곱한 값을 출력
def a_rectangle_area():
print(5 * 7)
# 매개변수 O 반환값 X : 2개의 매개변수를 받아 그 값을 곱한 결과를 출력
def b_rectangle_area(x, y):
print(x * y)
# 매개변수 X 반환값 O : 5와 7을 곱한 값을 반환
def c_rectangle_area():
return 5 * 7
# 매개변수 O 반환값 O : 2개의 매개변수를 받아 그 값을 곱한 결과를 반환
def d_rectangle_area(x, y):
return x * y
# 함수 호출과 반환값 활용
a_rectangle_area() # 출력: 35 (5 * 7)
b_rectangle_area(5, 7) # 출력: 35 (5 * 7)
result_c = c_rectangle_area() # 반환값을 result_c 변수에 할당
result_d = d_rectangle_area(5, 7) # 반환값을 result_d 변수에 할당
print(result_c) # 출력: 35 (c_rectangle_area의 반환값)
print(result_d) # 출력: 35 (d_rectangle_area의 반환값)
02. 함수 심화
: 함수를 좀 더 심도 있게 알아보고, 더 나아가 프로그래밍을 배울 때 컴퓨터의 구조에 대해 이해해야 하는 몇 가지 주제를 하나씩 설명할 것이다.
[1] 함수의 호출 방식
#함수의 호출 방식
def f(x):
y = x
x=5
return y * y
x=3
print(f(x)) #9
print(x) #3
#값 호출할 때 어떻게되는 지 보는 것
def f(x):
y = x
x = 5 # 함수 내에서 x의 값을 5로 변경합니다.
return y * y # y의 제곱을 반환합니다.
x = 3
print(f(x)) # 9: 함수 f(x)를 호출하며, x의 값을 전달합니다. 호출된 함수 내에서는 x의 값이 y로 복사됩니다.
# y는 함수 내에서만 사용되므로 외부 x와는 독립적입니다. 따라서 반환값은 3 * 3인 9가 됩니다.
print(x) # 3 : 외부에서의 x 값은 함수 호출과는 무관하게 3으로 유지됩니다.
이 코드에서 f(x) 함수 안에서의 **x**와 외부에서의 **x**는 서로 다른 변수로 취급됩니다.
함수 내에서 **x**가 변경되더라도 외부 변수 **x**에는 영향을 주지 않습니다.
이는 값에 의한 호출(call by value)의 특성을 나타냅니다. 함수에 인수를 전달할 때, 값만을 복사하여 전달하므로 함수 안에서의 변경은 해당 변수를 호출한 곳에 영향을 미치지 않습니다.
값에 의한 호출 (call by value) | 참조 호출(call by reference) |
함수에 인수를 넘길 때 값만 넘김 | 함수에 인수를 넘길 때 메모리 주소를 넘김 |
함수 안의 인수값 변경시 호출된 변수에 영향을 주지 않음 | 함수 안의 인수값 변경시 호출된 변수값도 변경됨 |
메모리 주소는 변수가 저장되는 공간이고, 그 공간 자체에 새로운 값을 할당하면 그 공간을 가리키고 있는 다른 변수에도 영향을 준다. 즉, [#함수의 호출 방식 코드]가 만약 참조 호출로 적용된다면, 맨 마지막에 있는 x의 값은 5로 변환되어야 한다는 것이다. 하지만 파이썬은 전통적인 두 가지 방식을 혼합한, 조금은 특이한 방식을 사용한다.
파이썬은 객체의 주소가 함수로 넘어간다는 뜻으로, 객체 호출 call by object reference로 명명되는 방식을 사용한다.
객체가 주소를 함수로 넘기므로 전달된 객체에 변경이 있을 경우, 즉 새로운 값을 할당하거나 해당 객체를 지울 때는 영향을 주지 않고, 단순히 해당 객체에 값을 추가할 때는 영향을 준다. 쉽게 이해하기 어려울 수 있는데, [#spam코드]를 살펴보자
def spam(eggs): # spam 함수 정의, eggs 매개변수를 받음
eggs.append(1) **# eggs 리스트에 1을 추가합니다.** eggs는 여전히 같은 객체를 참조하고 있습니다. ([0, 1])
eggs = [2, 3] **# eggs에 새로운 리스트 [2, 3]을 할당합니다. eggs가 새로운 객체를 참조합니다.**
print(eggs) # eggs를 출력합니다. [2, 3]이 출력됩니다.
print(id(eggs)) # eggs 객체의 메모리 주소를 출력합니다.
ham = [0] # 리스트 ham을 생성하고, 값 0을 포함합니다.
spam(ham) # spam 함수를 호출하며, ham을 인수로 전달합니다.
print(ham) # ham을 출력합니다. 여전히 [0, 1]이 됩니다. 함수 호출 시 eggs와 ham은 같은 객체를 참조했지만, eggs가 변경되어도 ham에는 영향을 주지 않습니다.
print(id(ham)) # ham 객체의 메모리 주소를 출력합니다.
ham과 eggs, 두 리스트에서 변수를 사용한 것을 보면 좀 더 명확히 알 수 있다.
먼저 ham이라는 리스트를 만들고, 함수 spam ham을 인수로 넣었다.
이때 함수 안에서는 이름이 ham에서 eggs로 바뀐다. [그림 5-3]에서 보듯이 ham과 eggs는 함수의 호출방식 객체 호출이므로 같은 주소를 공유한다.
따라서 2행의 eggs.append (1) 에 의해 해당 리스트에 1이 추가되면, ham과 eggs 모두의 영향을 받는다.
3행의 eggs = [2, 3]은 새로운 리스트를 만드는 코드다. 그래서 이 경우, 더는 ham과 eggs와 같은 메모리 주소를 가리키지 않고 eggs는 자기만의 메모리 주소를 가지게 된다.
그리고 함수를 빠져나가 7행의 print(ham)이 실행되면 2행의 eggs.append(1)에 의해 [0, 1]이 화면에 출력된다.
이것이 바로 객체 호출 call by object reference 이라는 파이썬의 함수 안 변수 호출 방식이다.
새로운 값을 할당하기 전까지는 기존에 넘어온 인수 객체의 주소값을 쓰는 방식이라고 이해하면 된다. 이 내용을 알아야 하는 가장 큰 이유는 다른 사람의 코드를 이해하기 위함이다. 더 좋은 방식은 메모리 문제가 크지 않다면 좀 더 명확하게 인수값 자체를 바꾸지 말고, 그 값을 복사하여 바로 사용하는 것이다. 그 내용은 이후 과정에서 다루도록 하겠다.
[2]변수의 사용 범위
: 말 그대로 변수가 코드에서 사용되는 범위, 흔히 함수 안 또는 프로그램 전체에서 변수가 사용되는 범위를 결정하는 규칙을 뜻한다.
일반적으로 변수의 사용 범위를 결정할 때, 고려해야 할 두 가지 변수는 다음과 같다.
- 지역 변수(local variable) 함수 안에서만 사용
- 전역 변수(global variable): 프로그램 전체에서 사용
두 변수의 사용방법은 매우 간단하다.
def test(t):
print(x) # x를 함수 내에서 사용 - 전역 변수
t = 20 # t를 20으로 변경 - 매개변수 t의 값만 변경됨
print("In Function:", t)
x = 10 # 전역 변수 x
test(x) # test 함수 호출
print("In Main:", x) # 전역 변수 x 출력
print("In Main:", t) # 함수 내의 지역 변수 t는 함수 외부에서 사용 불가능하여 에러 발생
10
In Function: 20
In Main: 10
---------------------------------------------------------------------------
NameError
Traceback (most recent call last)
<ipython-input-2-bc8256077f63> in <cell line: 9>()
7 test(x)
8 print ("In Main:", x)
----> 9 print("In Main:", t)
NameError: name 't' is not defined
관심을 두어야 할 변수는 x와 t다.
프로그램이 가장 먼저 시작되는 지점은 6행의 x = 10이다.
그리고 7행에서 x는 test(x) 함수로 변수를 넘기게 된다. 그렇다면 함수 안에서 처음 만나는 2행 print(x)의 x는 어떤 변수일까? 이때의 x는 함수 안에서 재정의되지 않았으므로 함수를 호출한 메인 프로그램의 x = 10 의 x를 뜻한다. 즉, 프로그램 전체에서 사용할 수 있는 전역 변수이다. 함수 안의 t는 어떨까? test(x) 함수의 x를 t로 치환하여 사용한다. 즉, 함수 안에서는 x를 따로 선언한 적은 없고, t를 선언하여 사용하는 것이다. 그리고 3행의 t=20 에 의해 t에 20이 할당되고, 실제로 4행 print("In Function:", t)문의 결과에 의해 In Function: 201 화면에 출력되는 것으로 예상할 것이다. 하지만 함수가 종료되고 코드에 9행의 print("In Main:", t)가 실행되면 오류가 출력된다. 왜냐하면 t가 함수 안에서만 사용할 수 있는 지역변수이기 때문이다. 이러한 차이를 이해하지 못한다면, 프로그래밍하는 데 어려움이 있을 수 있다. 다음 [코드 5-8]을 보자.
# 전역 변수 s에 'I love Paris!' 문자열을 할당합니다.
s = "I love Paris!"
# 함수 f를 정의합니다.
def f():
# 함수 내에서 새로운 지역 변수 s에 'I love London!' 문자열을 할당합니다.
s = "I love London!"
# 함수 내에서의 s 값을 출력합니다. (함수 내부에서의 s는 지역 변수입니다.)
print(s)
# 함수를 호출합니다.
f()
# 함수 외부에서의 s 값을 출력합니다. (함수 내부의 s와 외부의 s는 서로 다른 변수입니다.)
print(s)
[코드 5-8]에서 변수 s는 함수 f()의 안에서도 사용되고 밖에서도 사용된다. 5의 값은 어떻게 바뀔까? 프로그램이 시작되자마자 에는 'I love Paris!'라는 값이 할당된다. 하지만 그 후, 함수 안으로 코드의 실행이 옮겨가 다시 s에 'I love London!' 값이 저장되고, 그 값이 먼저 출력된다. 그렇다면 함수 밖 변수의 값은 변경되었을까? 함수가 종료된 후 7행 print(s)의 실행 결과는 'I love Paris!" 이다. 왜 이런 일이 발생했을까? 이유는 간단하다. 함수 안과 밖의 s는 같은 이름을 가졌지만, 사실 다른 메모리 주소를 가진 전혀 다른 변수이기 때문이다. 따라서 함수 안의 는 해당 함수가 실행되는 동안에만 메모리에 있다가 함수가 종료되는 순간 사라진다. 당연히 함수 밖와는 메모리 주소가 달라 서로 영향을 주지 않는다. 조금 어렵게 보이지만 매우 중요한 내용이다. 변수의 이름이 같다고 다 같은 함수가 아니라는 뜻이다.
[3]재귀함수
: 함수가 자기 자신을 다시 부르는 함수이다.
재귀적이라는 표현은 자신을 이용해 다른 것을 정의하는 것을 뜻한다.
03. 함수의 인수
[표 5-3 파이썬에서 인수를 사용하는 방법] *적어두세요
- 키워드 인수
- 함수의 인터페이스에 지정된 변수명을 사용하여 함수의 인수를 지정하는 방법
- 디폴트 인수
- 별도의 인수값이 입력되지 않을 때 인터페이스 선언에서 지정한 초깃값을 사용하는 방법
- 가변 인수
- 함수의 인터페이스에 지정된 변수 이외의 추가 변수를 함수에 입력할 수 있게 지원하는 방법
- 키워드 가변 인수
- 매개변수의 이름을 따로 지정하지 않고 입력하는 방법
- 키워드 인수
'''1키워드 인수 키워드 인수keyword arguments는 함수에 입력되는 매개변수의 변수명을 사용하여 함수의 인수를 지정하는 방법''' #키워드인수 def print_something(my_name,your_name): print("Hello {0}, My name is {1}".format(your_name,my_name)) print_something("gildong",'python') print_something(your_name='python',my_name='gildong')
- 함수의 인터페이스에 지정된 변수명을 사용하여 함수의 인수를 지정하는 방법
- 디폴트 인수
'''② 디폴트 인수 디폴트 인수default arguments는 매개변수에 기본값을 지정하여 사용하고, 아무런 값도 인수로 넘기지 않으면 지정된 기본값을 사용하는 방식이다. [코드 5-13]을 보자.''' #코드 5-13 default.py #디폴트 인수 def print_something(my_name,your_name='PYTHON'): print("Hello {0},My name is {1}".format(your_name,my_name)) print_something('gildong','python') print_something(my_name='gildong') #키워드에 처음부터 받는 매개변수 이름 지정함함
- 별도의 인수값이 입력되지 않을 때 인터페이스 선언에서 지정한 초깃값을 사용하는 방법
- 가변 인수
#가변인수(variable-length arguments) *중요! #함수의 인터페이스에 지정된 매개변수 이외의 추가 매개변수를 함수에 입력할 수 있게 지원하는 방법 코드를 작성할 때, 가끔 함수의 매개변수 개수가 정해지지 않고 진행해야 하는 경우가 있다. 예를 들어, 항의 개수가 정해지지 않은 다항방정식의 덧셈을 수행 하는 연산이라든가, 고객이 물건을 얼마나 구매할지 모르는 마트 계산기에서 합산을 수행하는 연산 등의 사례이다. 이 경우 함수를 어떻게 작성할 수 있을까? 이때 사용하는 것이 바로 가변 인수
def asterisk_test(a, b, *args): print(args)#3,4,5 return a+b + sum(args) #1+2 + 3+4+5 print(asterisk_test(1, 2, 3, 4, 5))
- 함수의 인터페이스에 지정된 변수 이외의 추가 변수를 함수에 입력할 수 있게 지원하는 방법
- 키워드 가변 인수
- 매개변수의 이름을 따로 지정하지 않고 입력하는 방법
#키워드 가변인수(keyword variable-length arguments):매개변수의 이름을 따로 지정하지 않고 입력하는 방법
'''
가변 인수는 변수의 순서대로 튜플형태로 저장된다.
사용할 때는 매우 간단하지만, 변수의 이름을 지정할 수 없다는 단점이 있다.
이러한 단점을 보완하는 방법이 키워드 가변 인수다.
이 방법은 매개변수의 이름을 따로 지정하지 않고 입력하는 방법으로
이전 가변 인수와는 달리
*를 2개 사용하여 함수의 매개변수를 표시한다
'''
def kwargs_test(**kwargs): #key word args
print(kwargs)
print("First value is {first}".format(**kwargs))
print("Second value is {second}".format(**kwargs))
print("Third value id {third}".format(**kwargs))
kwargs_test(first=3,second=4,third=5)