03. 함수¶
함수 (function)
함수의 정의
함수의 Output
함수의 Input
함수와 스코프
재귀 함수

다음의 코드를 봅시다. 무엇을 하는 코드일까요?
values = [100, 75, 85, 90, 65, 95, 90, 60, 85, 50, 90, 80]
total = 0
cnt = 0
for value in values:
total += value
cnt += 1
mean = total / cnt
total_var = 0
for value in values:
total_var += (value - mean) ** 2
sum_var = total_var / cnt
target = sum_var
count = 0
while True :
count += 1
root = 0.5 * (target + (sum_var / target))
if (abs(root - target) < 0.0000000000000001):
break
target = root
std_dev = target
print(std_dev)
14.499760534421096
이해하기 쉬운가요? 그리고 만약 다른 곳에서 동일한 작업을 다시해야할 경우 어떻게 해야 할까요?
import math
values = [100, 75, 85, 90, 65, 95, 90, 60, 85, 50, 90, 80]
cnt = len(values)
mean = sum(values) / cnt
sum_var = sum(pow(value - mean, 2) for value in values) / cnt
std_dev = math.sqrt(sum_var)
print(std_dev)
14.499760534421096
한줄로도 가능할까요?
import statistics
values = [100, 75, 85, 90, 65, 95, 90, 60, 85, 50, 90, 80]
statistics.pstdev(values)
14.499760534421096
3.1 함수¶
특정한 기능(function)을 하는 코드의 묶음
일의 단위
3.1.2 함수의 선언과 호출¶
함수의 선언은
def
키워드를 활용합니다.들여쓰기(4spaces)로 함수의 body(코드 블록)를 작성합니다.
Docstring은 함수 body 앞에 선택적으로 작성 가능합니다.
함수는 매개변수(parameter)를 넘겨줄 수도 있습니다. (인자 : argument)
함수는 동작후에
return
을 통해 결과값을 전달합니다.반드시 하나의 객체를 반환합니다 (
return
값이 없으면,None
을 반환)
함수는 호출은
함수명()
으로 합니다.예)
func()
/func(val1, val2)
활용법¶
def <함수이름>(parameter1, parameter2):
<코드 블럭>
return value
[연습] 세제곱 함수¶
입력 받은 수를 세제곱하여 반환(return)하는 함수
cube()
을 작성해보세요.
[입력 예시]
cube(2)
[출력 예시]
8
# 위 문제를 참고하여 아래에 cube 함수를 작성하고 실행해봅시다.
def cube(num): # num은 매개변수 (parameter)
return num ** 3
cube = lambda num: num ** 3 # 위와 같은 식
# 해당 코드를 실행하여 잘 동작하는지 확인합니다.
cube(2) # 2는 인자 (argument)
8

# 우리가 활용하는 print문도 파이썬에 지정된 함수입니다.
# 아래에서 'hi'는 argument이고 출력을 하게 됩니다.
print('hi')
hi

# 내장함수 목록을 직접 확인해봅시다.
dir(__builtins__)
['ArithmeticError',
'AssertionError',
'AttributeError',
'BaseException',
'BlockingIOError',
'BrokenPipeError',
'BufferError',
'BytesWarning',
'ChildProcessError',
'ConnectionAbortedError',
'ConnectionError',
'ConnectionRefusedError',
'ConnectionResetError',
'DeprecationWarning',
'EOFError',
'Ellipsis',
'EnvironmentError',
'Exception',
'False',
'FileExistsError',
'FileNotFoundError',
'FloatingPointError',
'FutureWarning',
'GeneratorExit',
'IOError',
'ImportError',
'ImportWarning',
'IndentationError',
'IndexError',
'InterruptedError',
'IsADirectoryError',
'KeyError',
'KeyboardInterrupt',
'LookupError',
'MemoryError',
'ModuleNotFoundError',
'NameError',
'None',
'NotADirectoryError',
'NotImplemented',
'NotImplementedError',
'OSError',
'OverflowError',
'PendingDeprecationWarning',
'PermissionError',
'ProcessLookupError',
'RecursionError',
'ReferenceError',
'ResourceWarning',
'RuntimeError',
'RuntimeWarning',
'StopAsyncIteration',
'StopIteration',
'SyntaxError',
'SyntaxWarning',
'SystemError',
'SystemExit',
'TabError',
'TimeoutError',
'True',
'TypeError',
'UnboundLocalError',
'UnicodeDecodeError',
'UnicodeEncodeError',
'UnicodeError',
'UnicodeTranslateError',
'UnicodeWarning',
'UserWarning',
'ValueError',
'Warning',
'WindowsError',
'ZeroDivisionError',
'__IPYTHON__',
'__build_class__',
'__debug__',
'__doc__',
'__import__',
'__loader__',
'__name__',
'__package__',
'__spec__',
'abs',
'all',
'any',
'ascii',
'bin',
'bool',
'breakpoint',
'bytearray',
'bytes',
'callable',
'chr',
'classmethod',
'compile',
'complex',
'copyright',
'credits',
'delattr',
'dict',
'dir',
'display',
'divmod',
'enumerate',
'eval',
'exec',
'filter',
'float',
'format',
'frozenset',
'get_ipython',
'getattr',
'globals',
'hasattr',
'hash',
'help',
'hex',
'id',
'input',
'int',
'isinstance',
'issubclass',
'iter',
'len',
'license',
'list',
'locals',
'map',
'max',
'memoryview',
'min',
'next',
'object',
'oct',
'open',
'ord',
'pow',
'print',
'property',
'range',
'repr',
'reversed',
'round',
'set',
'setattr',
'slice',
'sorted',
'staticmethod',
'str',
'sum',
'super',
'tuple',
'type',
'vars',
'zip']
# 편하게 써왔던 random.sample() 함수의 내부도 직접 확인해봅시다.
# https://github.com/python/cpython/blob/master/Lib/random.py#L385
[연습] 함수 만들기¶
아래의 코드와 동일한
my_max
함수를 만들어주세요.정수를 두개 받아서, 큰 값을 반환합니다.
my_max(1, 5)
출력 예시)
5
# 내장함수 max()를 확인해봅시다.
max(1, 5)
5
# 위 문제를 참고하여 아래에 my_max 함수를 작성하고 실행해보세요.
def my_max(num1, num2):
if num1 > num2:
return num1
else:
return num2
num1 if num1 > num2 else num2
# 해당 코드를 통해 올바른 결과가 나오는지 확인하세요.
my_max(1, 5)
함수의 선언과 호출 살펴보기¶
# 아래의 코드의 결과는 무엇일까요? 실행하기 전에 예측해봅시다!
num1 = 0
num2 = 1
def func1(a, b):
return a + b
def func2(a, b):
return a - b
def func3(a, b):
return func1(a, 5) + func2(5, b)
result = func3(num1, num2)
print(result)
Python tutor를 통해 실행 순서를 직접 확인하세요.
함수는 호출되면 계산을 수행하고, 값을 반환하며 종료됩니다.
3.2 함수의 Output¶
함수의 return¶
앞서 설명한 것과 마찬가지로 함수는 반환되는 값이 있으며, 이는 어떠한 종류(~~의 객체~~)라도 상관없습니다.
단, 오직 한 개의 객체만 반환됩니다.
복수의 객체를 return 하는 경우 -> 복수의 객체를 하나의
tuple
로 반환합니다.명시적인 return 값이 없는 경우 -> 하나의 객체
None
을 반환합니다.
함수가 return 되거나 종료되면, 함수를 호출한 곳으로 돌아갑니다.
return vs print
return은 함수 안에서만 사용되는 키워드
print는 출력을 위해 사용되는 함수
REPL (Read-Eval-Print Loop) 환경에서는 마지막으로 작성된 코드의 리턴 값을 보여주므로 같은 동작을 하는 것으로 착각할 수 있음.
[실습] 사각형의 넓이를 구하는 함수¶
너비와 높이를 입력 받아 사각형의 넓이와 둘레를 반환(return)하는 함수
rectangle()
을 작성해보세요.
[입력 예시]
rectangle(30, 20)
[출력 예시]
(600, 100)
# 위 문제를 참고하여 아래에 rectangle 함수를 작성하고 실행해봅시다.
def rectangle(width, height):
area = width * height
parameter = 2 * (width + height)
return area, parameter
# 해당 코드를 통해 올바른 결과가 나오는지 확인하세요.
print(rectangle(30, 20))
print(rectangle(50, 70))
(600, 100)
(3500, 240)
[연습] 함수를 정의하고 값을 반환해봅시다.¶
리스트 두개를 받아 각각 더한 결과를 비교하여 값이 큰 리스트를 반환하는 함수를 만들어주세요.
my_list_max([10, 3], [5, 9])
예시 출력)
[5, 9]
# 위 문제를 참고하여 아래에 my_list_max 함수를 작성하고 호출하세요.
def my_list_max(list1, list2):
if sum(list1) > sum(list2):
return list1
else:
return list2
def my_list_max(list1, list2):
return list1 if sum(list1) > sum(list2) else list2
# 해당 코드를 통해 올바른 결과가 나오는지 확인하세요.
print(my_list_max([10, 3], [5, 9]))
[5, 9]
3.3 함수의 입력 (Input)¶
3.3.1 매개변수 & 전달인자¶
(1) 매개변수 (parameter)¶
def func(x):
return x + 2
x
는 매개변수(parameter)입니다.입력을 받아 함수 내부에서 활용할
변수
라고 생각하면 됩니다.함수의 정의 부분에서 볼 수 있습니다.
(2) 전달인자 (argument)¶
func(2)
2
는 (전달)인자(argument)실제로 전달되는
입력값
이라고 생각하면 됩니다.함수를 호출하는 부분에서 볼 수 있습니다.
주로 혼용해서 사용하지만 엄밀하게 따지면 둘은 다르게 구분되어 사용됩니다. 개념적 구분보다 함수가 작동하는 원리를 이해하는게 더 중요합니다.
3.3.2 함수의 인자¶
함수는 입력값(input)으로 인자(argument)
를 넘겨줄 수 있습니다.
위치 인자¶
기본적으로 인자는 위치에 따라 함수 내에 전달(Positional Arguments)됩니다.
[연습] 원기둥의 부피¶
원기둥의 반지름(r)과 높이(h)를 받아서 부피를 return하는 함수
cylinder()
를 작성하세요.원기둥 부피 = 3.14 * 반지름 * 반지름 * 높이
# 위 문제를 참고하여 아래에 cylinder 함수를 작성하고 호출하세요.
def cylinder(r, h):
return round(3.14 * r**2 * h, 5)
# 해당 코드를 통해 올바른 결과가 나오는지 확인하세요.
print(cylinder(5, 2))
print(cylinder(2, 5)) # 순서를 바꾸면 다른 값이 나옵니다.
157.0
62.8

기본 인자 값¶
함수를 정의할 때, 기본값 (Default Argument Values)을 지정하여 함수를 호출할 때 인자의 값을 설정하지 않도록하여, 정의된 것 보다 더 적은 개수의 인자들로 호출 될 수 있습니다.
활용법
def func(p1=v1):
return p1
[연습] 기본 인자 값 활용¶
이름을 받아서 다음과 같이 인사하는 함수
greeting()
을 작성하세요. 이름이 길동이면, “길동, 안녕?” 이름이 없으면 “익명, 안녕?” 으로 출력하세요.
# 위 문제를 참고하여 아래에 greeting 함수를 작성하고 실행해봅시다.
def greeting(name = '익명'):
return f'{name}, 안녕?'
# 해당 코드를 통해 올바른 결과가 나오는지 확인하세요.
print(greeting())
print(greeting('철수'))
익명, 안녕?
철수, 안녕?
기본 인자 값이 설정되어 있더라도 기존의 함수와 동일하게 호출 가능합니다.

호출시 인자가 없으면 기본 인자 값이 활용됩니다.

Warning
단, 기본 인자값(Default Argument Value)을 가지는 인자 다음에 기본 값이 없는 인자를 사용할 수는 없습니다.
위치 매개변수 - 기본 인자 매개변수 순으로 정의해야 함.
순서) 위치인자(basic) => 기본인자(default)
# 다음 코드를 실행해서 오류를 확인해봅시다.
def greeting(name='john', age):
return f'{name}은 {age}살입니다.'
File "<ipython-input-20-85fc8bb36d98>", line 2
def greeting(name='john', age):
^
SyntaxError: non-default argument follows default argument
# 오류가 발생하지 않도록 아래에 직접 수정하고 실행해봅시다.
def greeting(age, name='john'):
return f'{name}은 {age}살입니다.'
# 해당 코드를 통해 올바른 결과가 나오는지 확인하세요.
print(greeting(1))
print(greeting(2, 'json'))
john은 1살입니다.
json은 2살입니다.
키워드 인자¶
함수를 호출할 때 키워드 인자 (Keyword Arguments)를 활용하여 직접 변수의 이름으로 특정 인자를 전달할 수 있습니다.
# 다음 코드를 실행해서 greeting 함수를 선언합니다.
def greeting(age, name):
return f'{name}은 {age}살입니다.'
# 아래와 같이 키워드 인자를 사용해서 함수를 호출할 수 있습니다.
greeting(name='철수', age=24)
'철수은 24살입니다.'
# 위치 인자와 함께 사용할 수 있습니다.
greeting(24, name='철수')
'철수은 24살입니다.'
Warning
단, 아래와 같이 키워드 인자를 활용한 다음에 위치 인자를 활용할 수는 없습니다.
# 다음 코드를 실행해서 오류를 확인해봅시다.
greeting(age=24, '철수')
File "C:\Users\User\AppData\Local\Temp/ipykernel_14428/681848598.py", line 2
greeting(age=24, '철수')
^
SyntaxError: positional argument follows keyword argument
순서
정의할 때 parameter 순서
basic => default value
호출할 때 argument 순서
position => keyword
3.3.3 정해지지 않은 여러 개의 인자 처리¶
우리가 주로 활용하는 print()
함수는 파이썬 표준 라이브러리의 내장함수 중 하나이며, 다음과 같이 구성되어있습니다.
# 직접 실행해서 확인해봅시다.
print('첫번째 문장')
print('두번째 문장', end='_')
print('세번째 문장', '네번째 문장')
print('다섯번째 문장', '마지막 문장', sep='/', end='끝!')
첫번째 문장
두번째 문장_세번째 문장 네번째 문장
다섯번째 문장/마지막 문장끝!
가변(임의) 인자 리스트¶
앞서 설명한 print()
처럼 개수가 정해지지 않은 임의의 인자를 받기 위해서는 함수를 정의할 때 가변 인자 리스트(Arbitrary Argument Lists) *args
를 활용합니다.
가변 인자 리스트는 tuple
형태로 처리가 되며, 매개변수에 *
로 표현합니다.
활용법
def func(a, b, *args):
*args
: 임의의 개수의 위치인자를 받음을 의미보통, 이 가변 인자 리스트는 매개변수 목록의 마지막에 옵니다.
Warning
가변 인자 리스트가 위치 인자보다 앞쪽에 올 수 없습니다.
# 가변 인자 예시
# print문은 *obejcts를 통해 임의의 숫자의 인자를 모두 처리합니다.
# 아래의 코드로 확인해봅시다.
print('hi', '안녕', 'Guten Tag', 'gonnichiwa', sep=',')
hi,안녕,Guten Tag,gonnichiwa
# args는 함수 내부에서 tuple로 처리됩니다.
# 아래의 코드로 확인해봅시다.
def my_func(*args):
return args
print(my_func(1, 2))
print(type(my_func(1, 2)))
(1, 2)
<class 'tuple'>
[연습] 가변 인자 리스트를 사용해봅시다.¶
정수를 여러 개 받아서 가장 큰 값을 반환(return)하는 함수
my_max()
를 작성하세요.max 내장 함수 사용은 금지합니다.
my_max(10, 20, 30, 50)
예시출력)
50
max(1, 2, 3, 4)
4
# 위 문제를 참고하여 아래에 my_max 함수를 작성하세요.
def my_max(*args):
return max(args)
# 다음 코드를 실행해 올바른 결과가 나오는지 확인하세요.
my_max(-1, -2, -3, -4)
-1
가변(임의) 키워드 인자¶
정해지지 않은 키워드 인자들은 함수를 정의할 때 가변 키워드 인자 (Arbitrary Keyword Arguments) **kwargs
를 활용합니다.
가변 키워드 인자는 dict
형태로 처리가 되며, 매개변수에 **
로 표현합니다.
활용법
def func(**kwargs):
**kwargs
: 임의의 개수의 키워드 인자를 받음을 의미합니다
우리가 dictionary를 만들 때 사용할 수 있는 dict()
함수는 파이썬 표준 라이브러리의 내장함수 중 하나이며, 다음과 같이 구성되어 있습니다.
# 딕셔너리 생성 함수 예시입니다.(가변 키워드 인자 활용)
# hi = {'한국어': '안녕', '영어': 'hi'}
hi = dict(한국어='안녕', 영어='hi')
print(hi)
{'한국어': '안녕', '영어': 'hi'}
# 주의사항
# 식별자는 숫자만으로는 이루어질 수가 없습니다.(키워드인자로 넘기면 함수 안에서 식별자로 쓰이기 때문)
# 코드를 실행해서 오류를 확인해봅시다.
dict(1=1, 2=2) # X
File "C:\Users\User\AppData\Local\Temp/ipykernel_14428/1178151218.py", line 5
dict(1=1, 2=2) # X
^
SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
# Key가 숫자인 딕셔너리를 만들고 싶다면, 아래와 같이 사용해야합니다.
dict([(1, 1), (2, 2)])
dict(((1,1), (2,2)))
{1: 1, 2: 2}
# 아래의 코드를 실행시켜 kwargs가 딕셔너리로 처리되는 것을 확인해봅시다.
def my_dict(**kwargs):
return kwargs
print(my_dict(한국어='안녕', 영어='hi', 독일어='Guten Tag'))
{'한국어': '안녕', '영어': 'hi', '독일어': 'Guten Tag'}
Note
위치인자와 *agrs
, **kwargs
를 함께 사용했을 때 올바른 순서
my_info(x, y, *args, **kwargs)
Tip
*
가 하나일 경우 tuple
, **
일 경우 dictionary
로 처리된다.
[실습] URL 생성기¶
my_url()
함수를 만들어 완성된 URL을 반환하는 함수를 작성하세요.
my_url(sidoname='서울', key='asdf')
예시 출력)
https://api.go.kr?sidoname=서울&key=asdf&
# 입력받은 가변 키워드 인자를 활용하여 'https://api.go.kr?'를 BASE_URL로 사용하는 URL을 생성해봅시다.
# 가변 키워드 인자(kwargs)를 사용하는 my_url 함수를 직접 작성해보세요.
def my_url(**kwargs):
url = 'https://api.go.kr?'
for k, v in kwargs.items():
url += f'{k}={v}&'
return url[:-1]
# 다음 코드를 실행하여 결과를 확인해보세요.
print(my_url(sidoname='서울', key='asdf'))
https://api.go.kr?sidoname=서울&key=asdf
3.4 함수와 스코프¶
함수는 코드 내부에 스코프 (scope)를 생성합니다. 함수로 생성된 공간은 지역 스코프 (local scope)라고 불리며, 그 외의 공간인 전역 스코프 (global scope)와 구분됩니다.
전역 스코프(global scope): 코드 어디에서든 참조할 수 있는 공간
지역 스코프(local scope): 함수가 만든 스코프로 함수 내부에서만 참조할 수 있는 공간
전역 변수(global variable): 전역 스코프에 정의된 변수
지역 변수(local variable): 지역 스코프에 정의된 변수
# 전역 스코프와 지역 스코프를 알아봅시다.
# 전역 스코프에서는 지역 스코프의 변수를 참조할 수 없습니다.
# 그러나 반대로 지역스코프에서 전역스코프의 변수는 참조(접근)할 수 있습니다.
# func 함수 바깥에서 함수 안의 지역 변수 c를 출력하고 오류를 확인해보세요.
# 그리고 반대로 func 함수 내부에서 전역 변수 a를 출력하고 결과를 확인해보세요.
a = 10
def func():
c = 20
print(a)
func()
# print(c)
print(a)
10
10
# 전역 스코프와 지역 스코프에 같은 이름의 변수를 만들면 어떻게 될까요?
# 아래 코드를 실행시켜보고 결과를 확인해보세요.
a = 10 # 전역 변수(global)
def func(b):
a = 30 # 지역 변수(local variable)
print(a)
func(a)
print(a)
30
10
3.4.1 변수의 수명주기¶
변수의 이름은 각자의 수명주기 (lifecycle)가 있습니다.
빌트인 스코프 (built-in scope): 파이썬이 실행된 이후부터 영원히 유지
전역 스코프 (global scope): 모듈이 호출된 시점 이후 혹은 이름 선언된 이후부터 인터프리터가 끝날 때 까지 유지
지역(함수) 스코프 (local scope): 함수가 호출될 때 생성되고, 함수가 종료될 때까지 유지 (함수 내에서 처리되지 않는 예외를 일으킬 때 삭제됨)
3.4.2 이름 검색 규칙 (resolution)¶
파이썬에서 사용되는 이름(식별자)들은 이름공간 (namespace)에 저장되어 있습니다.
아래와 같은 순서로 이름을 찾아나가며, LEGB Rule
이라고 부릅니다.
L
ocal scope: 함수E
nclosed scope: 특정 함수의 상위 함수G
lobal scope: 함수 밖의 변수 혹은 import된 모듈B
uilt-in scope: 파이썬안에 내장되어 있는 함수 또는 속성
즉, 함수 내에서는 바깥 스코프의 변수에 접근 가능하나 수정은 할 수 없습니다.
# 이것을 통해 첫시간에 내장함수의 식별자를 사용할 수 없었던 예제에서 오류가 생기는 이유를 확인할 수 있습니다.
# Built-in scope와 Global scope를 알아봅시다.
# 아래 코드를 실행하여 오류를 확인해보세요.
print = 'hello'
print(3)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_14724/2300548552.py in <module>
5
6 print = 'hello'
----> 7 print(3)
TypeError: 'str' object is not callable
# print 함수를 다시 사용할 수 있도록 print라는 이름의 변수를 삭제합니다.
del print
print()
코드가 실행되면함수에서 실행된 코드가 아니기 때문에
L
,E
를 건너 뛰고,print
라는 식별자를 Global scope에서 찾아서print = ssafy
를 가져오고,이는 함수가 아니라 변수이기 때문에
not callable
하다라는 오류를 내뱉게 됩니다.우리가 원하는
print()
은 Built-in scope에 있기 때문입니다.
# Global scope와 Local scope를 알아봅시다.
# 함수 밖의 변수 a는 전역 변수에 해당하고, 함수 내부의 변수 a는 지역 변수에 해당합니다.
# 함수의 실행 결과로 어떤 변수의 값이 반환되는지 확인해보세요.
a = 1 # gloabl
def local_scope(a):
a = 3 # local
print(a)
local_scope(a)
3
# LEGB Rule을 자세히 알아봅시다.
# 아래 코드를 실행시켜보고 print문에서 출력되는
# 각 변수가 어느 스코프에 해당하는 변수인지 확인해보고 왜 그렇게 되는지 고민해보세요.
a = 10 # gloabl
b = 20 # global
def enclosed():
a = 30 # enclosed
def local():
c = 40 # local
print(a, b, c)
# B E G L
local()
a = 50 # enclosed 함수의 local
enclosed()
30 20 40
# 스코프는 함수에서만 적용됩니다.
# 아래와 같이 for문을 확인해보세요.
a = 1
for a in [1, 2, 3]: # for문을 통해 global a 할당
pass
print(a)
3
nonlocal¶
전역을 제외하고 가장 가까운 (둘러 싸고 있는) 스코프의 변수를 연결하도록 합니다.
nonlocal
에 나열된 이름은 같은 코드 플록에서nonlocal
앞에 등장할 수 없음.nonlonal
에 나열된 이름은 매개변수, for 루프 대상, 클래스/함수 정의 등으로 정의되지 않아야 함.
global
과는 달리 이미 존재하는 이름과의 연결만 가능함
# 전역 변수를 바꿀 수 있을까요?
# 기본적으로 지역 스코프에서 전역 스코프의 변수를 바꿀 수는 없습니다.
# 아래 코드에서 함수 내부의 global_num은 지역 변수로 생성됩니다.
# 코드를 실행시킨 뒤 결과를 확인해보세요.
global_num = 3
def local_scope():
global_num = 5
local_scope()
print(global_num)
3
global¶
현재 코드 블록 전체에 적용되며, 나열된 식별자(이름)들이 전역 변수임을 나타냅니다.
global
에 나열된 이름은 같은 코드 블록에서global
앞에 등장할 수 없음.global
에 나열된 이름은 매개변수, for 루프 대상, 클래스/함수 정의 등으로 정의되지 않아야 함.
# 전역에 있는 변수를 바꾸고 싶다면, 아래와 같이 선언할 수 있습니다. (일반적으로 권장 X)
# global 키워드를 사용하여 지역 스코프에서 전역 변수의 값을 바꿀 수 있습니다.
# 코드를 실행시킨 뒤 결과를 확인해보세요.
global_num = 3
def local_scope():
global global_num
global_num = 5
local_scope()
print(global_num)
5
Warning
기본적으로 함수에서 선언된 변수는 Local scope에 생성되며, 함수 종료 시 사라집니다.
해당 스코프에 변수가 없는 경우 LEGB rule에 의해 이름을 검색합니다.
변수에 접근은 가능하지만, 해당 변수를 재할당할 수는 없습니다.
값을 할당하는 경우 해당 스코프의 이름공간에 새롭게 생성되기 때문입니다.
단, 함수 내에서 필요한 상위 스코프 변수는 인자로 넘겨서 활용합니다. (*클로저 제외)
상위 스코프에 있는 변수를 수정하고 싶다면 global, nonlocal 키워드를 활용 가능합니다.
단, 코드가 복잡해지면서 변수의 변경을 추적하기 어렵고, 예기치 못한 오류가 발생합니다.
가급적 사용하지 않는 것을 권장하며 함수로 값을 바꾸고자 한다면 항상 인자로 넘기고 리턴 값을 사용하는 것을 추천
클로저란?
어떤 함수 내부에 중첩된 형태로써 외부 스코프 변수에 접근 가능한 함수
var_x = 1
var_a = 2
def x():
var_x = 10 # enclosed
def y():
nonlocal var_x # enclosed
global var_a # global var_a = 1000
var_x = 100
var_a = 1000
print(var_x, var_a)
y()
x()
print(var_x, var_a) # global var_x = 1
# 그러나 가급적 사용하지 말자.
100 1000
1 1000
3.5 재귀 함수¶
재귀 함수(recursive function)는 함수 내부에서 자기 자신을 호출 하는 함수를 뜻합니다.
무한한 호출을 목표로 하는 것이 아니며, 알고리즘을 설계 및 구현에서 유용하게 활용됩니다.
알고리즘 중 재귀 함수로 로직을 표현하기 쉬운 경우가 있습니다. (ex. 점화식)
변수의 사용이 줄어들며, 코드의 가독성이 높아집니다.
1개 이상의 base case(종료되는 상황)가 존재하고, 수렴하도록 작성합니다.
같은 문제를 다른 Input 값을 통해서 해결하는 과정
큰 문제를 해결하기 위해 작은 문제로 좁히고, 작은 문제의 해답을 이용하여 해결
작은 문제는 base case에 도달하여 재귀 함수가 끝날 수 있도록 함.
팩토리얼 계산¶
팩토리얼은 1부터 n 까지 양의 정수를 차례대로 곱한 값이며
!
기호로 표기합니다. 예를 들어 3!은 3 * 2 * 1이며 결과는 6 입니다.
팩토리얼(factorial)
을 계산하는 함수fact(n)
를 작성하세요.n은 1보다 큰 정수라고 가정하고, 팩토리얼을 계산한 값을 반환합니다.
예시 출력)
120
반복문을 이용한 팩토리얼 계산¶
# 팩토리얼을 반복문을 이용하여 구현해봅시다.
def fact(n):
result = 1
for i in range(1, n + 1):
result *= i
return result
# while문으로 풀기
# def fact(n):
# result = 1
# while n > 1:
# result *= n
# n -= 1
# return result
# 해당 코드를 통해 올바른 결과가 나오는지 확인하세요.
fact(5)
120
재귀를 이용한 팩토리얼 계산¶
1! = 1
2! = 1 * 2 = 1! * 2
3! = 1 * 2 * 3 = 2! * 3
# 재귀를 이용하여 팩토리얼을 구현해봅시다.
# 여기에 코드를 작성하세요.
def factorial(n):
if n == 1:
return 1
return n * factorial(n - 1)
# 해당 코드를 통해 올바른 결과가 나오는지 확인하세요.
factorial(5)
120
반복문과 재귀함수¶
factorial(3)
3 * factorail(2)
3 * 2 * factorial(1)
3 * 2 * 1
3 * 2
6
두 코드 모두 원리는 같습니다!
반복문 코드
n이 1보다 큰 경우 반복문을 돌며, n은 1씩 감소합니다.
마지막에 n이 1이면 더 이상 반복문을 돌지 않습니다.
재귀 함수 코드
재귀 함수를 호출하며, n은 1씩 감소합니다.
마지막에 n이 1이면 더 이상 추가 함수를 호출하지 않습니다.
재귀함수는 기본적으로 같은 문제이지만 점점 범위가 줄어드는 문제를 풀게 됩니다.
재귀함수를 작성시에는 반드시,
base case
가 존재 하여야 합니다.base case
는 점점 범위가 줄어들어 반복되지 않는 최종적으로 도달하는 곳을 의미합니다.재귀를 이용한 팩토리얼 계산에서의 base case는 n이 1일때, 함수가 아닌 정수 반환하는 것입니다
재귀 함수 주의 사항
팩토리얼 재귀함수를 Python Tutor에서 확인해보면, 함수가 호출될 때마다 메모리 공간에 쌓이는 것을 볼 수 있습니다.
이 경우, 메모리 스택이 넘치거나(Stack overflow) 프로그램 실행 속도가 늘어지는 단점이 생깁니다.
파이썬에서는 이를 방지하기 위해 1,000번이 넘어가게 되면 더이상 함수를 호출하지 않고, 종료됩니다. (최대 재귀 깊이 (maximum recursion depth))
호출 횟수가 최대 재귀 깊이를 넘어가게 되면
Recursion Error
가 발생합니다.
import sys
print(sys.getrecursionlimit())
3000
최대 재귀 깊이¶
def ssafy():
print('Hello, ssafy!')
ssafy()
ssafy()
ssafy()
를 호출하면 아래와 같이 문자열이 계속 출력되다가 RecursionError가 발생합니다.
파이썬에서는 최대 재귀 깊이(maximum recursion depth)가 1,000으로 정해져 있기 때문입니다.
Hello, world!
Hello, world!
...
Hello, world!
---------------------------------------------------------------------------
RecursionError Traceback (most recent call last)
...
<ipython-input-11-2bbb40950c86> in hello()
1 def hello():
2 print('Hello, world!')
----> 3 hello()
4
5 hello()
RecursionError: maximum recursion depth exceeded while calling a Python object
# 직접 오류를 확인하세요.
# def ssafy():
# print('Hello, ssafy!', end=" ")
# ssafy()
# ssafy()
[실습] 피보나치 수열¶
첫째 및 둘째 항이 1이며 그 뒤의 모든 항은 바로 앞 두 항의 합인 수열입니다.
(0), 1, 1, 2, 3, 5, 8
피보나치 수열은 다음과 같은 점화식이 있습니다.
피보나치 값을 리턴하는 두가지 방식의 코드를 모두 작성해주세요.
fib(n)
: 재귀함수fib_loop(n)
: 반복문 활용한 함수
예시 입력)
fib(10)
예시 호출)
55
# 재귀를 이용한 코드 fib() 를 작성하세요.
def fib(n):
if n < 2:
return n
else:
return fib(n-1) + fib(n-2)
# 해당 코드를 통해 올바른 결과가 나오는지 확인하세요.
fib(10)
55
# 반복문(for문)을 이용한 코드 fib_loop() 를 작성하세요.
def fib_loop(n):
result = []
for i in range(n):
if i in (0, 1):
result.append(1)
else:
result.append(result[i-2] + result[i-1])
return result[n-1]
def fib_loop(n):
if n < 2:
return n
result = [0, 1]
for i in range(2, n+1):
next_num = result[i-1] + result[i-2]
result.append(next_num)
return result[-1]
# 해당 코드를 통해 올바른 결과가 나오는지 확인하세요.
fib_loop(10)
55
# 반복문(while 문)을 이용한 코드 fib_loop2()을 작성하세요.
def fib_loop2(n):
i = 0
result = []
while i < n + 1:
if i in (0, 1):
result.append(1)
else:
result.append(result[i-2] + result[i-1])
i += 1
return result[n-1]
fib_loop2(10)
55
반복문과 재귀 함수의 차이¶
알고리즘 자체가 재귀적인 표현이 자연스러운 경우 재귀함수를 사용합니다.
재귀 호출은
변수 사용
을 줄여줄 수 있습니다.
# 재귀 호출은 입력 값이 커질 수록 연산 속도가 오래걸립니다.
# fib() 함수에 10 이상의 값을 넣어보고 실행한 뒤 연산 시간을 확인해보세요.
import time
start = time.time()
fib(30)
end = time.time()
print(end - start)
0.13866543769836426
# 반복문은 재귀로 구현된 함수보다 연산 속도가 빠른 편입니다.
# fib_loop() 함수에 10 이상의 값을 넣어보고 실행한 뒤 연산 시간을 확인해보세요.
# 그리고 100배 더 큰 1000 이상의 값도 넣어보고 실행한 뒤 연산 시간을 확인해보세요.
import time
start = time.time()
fib_loop(10000)
end = time.time()
print(end - start)
0.004734039306640625
# 시간을 더 정확하게 측정하는 방법
from timeit import default_timer as timer
from datetime import timedelta
start = timer()
fib(30)
end = timer()
print(timedelta(seconds = end - start))
0:00:00.139081
from timeit import default_timer as timer
from datetime import timedelta
start = timer()
fib_loop(10000)
end = timer()
print(timedelta(seconds = end - start))
0:00:00.004688
convention
리스트 / 배열을 담는 변수명은 복수형으로
함수 이름은 동사형으로 짓는다. 이름에서 return의 type을 추측할 수 있으면 더 좋다. (ex.
get_fibonacci()
,is_adult()
,get_lotto_numbers()
)