Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

5 함수

Updated: 10 mrt 2026

프로그램은 명령문의 조합으로 구현된다. 많은 프로그램은 수백, 수천 줄의 명령문으로 이루어지며, 경우에 따라 수십만, 수백만 줄, 심지어 천만 줄이 넘는 경우도 있다.

수십 줄 이상의 프로그램을 구현하다 보면 특정 기능을 수행하는 명령문을 반복해서 사용하게 된다. 또한 프로그램의 소스코드를 여러 파일로 나누어 작성하고 관리할 필요도 생긴다. 이때 함수와 모듈을 활용한다.

이 장에서는 먼저 함수를 살펴보고, 이어지는 장에서 모듈을 소개한다.

5.1함수 호출과 표현식

함수function는 간단히 말해 특정 코드에 붙인 이름이다. 예를 들어 아래 코드를 실행하면 "함수란 특정 명령문을 대신한다." 라는 문장이 화면에 출력된다.

print('함수란', '특정 명령문을 대신한다.')
함수란 특정 명령문을 대신한다.

print는 실제로는 매우 복잡하게 작성된 코드를 나타내는 이름이다. 그 코드가 정확히 어떻게 구현되었는지 사용자는 쉽게 알 수 없으며, 또 굳이 알 필요도 없다. 대신 화면에 문자열 등의 값을 출력하고 싶을 때 print() 함수를 사용한다는 사실만 알면 된다. 이처럼 함수의 용도를 알고 적절하게 활용하는 것이 가장 중요하다.

위 코드에서 print() 함수는 공백을 사이에 두고 주어진 두 개의 문자열 인자를 화면에 한 줄에 연속해서 출력한다.

  • 첫째 인자: 함수란

  • 둘째 인자: 특정 명령문을 대신한다.

5.1.1함수 호출

함수는 아래 형식의 함수 호출function call 명령문을 통해 활용된다.

함수이름(인자1, 인자2, ..., 인자n)

함수 호출에 필요한 인자의 수는 함수의 정의에 의존한다. 예를 들어, round() 함수는 둘째 인자를 지정하지 않으면 소수점 이하를 버리고 정수를 계산한다.

round(3.141592)
3

둘째 인자를 0으로 지정하는 것과 결과가 동일하다.

round(3.141592, 0)
3.0

다만 두 함수호출 결과의 자료형이 서로 다르다. 이는 round() 함수가 그렇게 작동하도록 구현되었기 때문이며, 사용자는 이 점을 고려하면서 round() 함수를 활용해야 한다.

type(round(3.141592))
int
type(round(3.141592, 0))
float

함수 호출에 요구되는 인자의 개수가 달라질 수 있다는 게 이상하게 보일 수 있지만 사실은 전혀 그렇지 않다. 이에 대해서는 키워드 인자를 소개할 때 보다 자세히 설명한다.

인자 없는 함수 호출

print() 함수처럼 특정 함수는 인자를 지정하지 않으면서 호출될 수도 있다.

print()

print() 함수를 인자없이 호출하면 아무런 결과가 없는 것처럼 보이지만 일단 오류가 발생하지 않은 점에 주목한다. 왜냐하면 어떤 함수는 인자 없이 호출되면 오류가 발생하기 때문이다. 예를 들어, 자료형을 확인해주는 round() 함수가 그렇다.

round()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[2], line 1
----> 1 round()

TypeError: round() missing required argument 'number' (pos 1)

이유는 round() 함수는 round(3.141592) 또는 round(3.141592, 2) 처럼 소수점 이하 자릿수를 지정할 부동소수점이 필요한데 인자가 없으면 할 수 있는 일이 없기 때문이다.

결론적으로 다음 두 가지를 기억한다.

  1. 함수 호출에 사용되는 인자의 개수는 함수의 정의에 의존한다.

  2. 인자가 필요 없는 경우에도 print() 처럼 열고 닫는 소괄호를 반드시 사용해야 한다.

함수를 열고 닫는 소괄호 없이 코드에 사용할 수는 있으며 절대 오류가 발생하지 않는다. 하지만 그것는 함수 호출이 아니다. 예를 들어 아래 코드의 실행결과에 주목한다.

print
<function print(*args, sep=' ', end='\n', file=None, flush=False)>

위 코드의 실행 결과는 print라는 이름이 가리키는 값이 함수라는 사실과 함수의 인자로 사용될 수 있는 값들에 대한 정보, 즉 함수 이름이라는 사실과 함수의 시그니처를 보여준다. round 라고 입력해도 동일하다. 즉, 소괄호 없이 함수 이름만 확인하는 일은 변수가 가리키는 값을 확인하는 것과 동일하다. 실제로 함수 이름 또한 변수처럼 취급된다.

round
<function round(number, ndigits=None)>

함수의 인자에 대한 보다 자세한 설명은 다음 절에서 다룬다.

5.1.2함수 호출 표현식

함수 호출 표현식은 함수 호출 형식으로 값을 나타내는 표현식이다. 예를 들어 round(3.141592, 2)는 부동소수점 3.14를, ord('A')는 정수 65를 나타내는 표현식들이며, 따라서 변수 할당 등에 바로 활용될 수 있다.

pi = round(3.141592, 2)
print('원주율:', pi)
원주율: 3.14
unicode_A = ord('A')
print('A의 코드포인트:', unicode_A)
A의 코드포인트: 65

심지어 type(3.14)type(65)도 각각 부동소수점 3.14의 자료형인 float와 정수 65의 자료형인 int를 값으로 나타낸 함수 표현식이다. 파이썬 코드에서 다루는 모든 대상이 값인 이유가 여기에 있다.

type_of_pi = type(3.14)
print('3.14의 타입:', type_of_pi)
3.14의 타입: <class 'float'>
type_of_65 = type(65)
print('65의 타입:', type_of_65)
65의 타입: <class 'int'>

5.1.3예제

(1) ord() 함수는 인자로 문자가 주어지면 해당 문자의 유니코드 코드 포인트를 반환한다. 영어 소문자 알파벳 'a''z'의 유니코드 코드 포인트를 화면에 다음과 같이 출력하는 코드를 작성하라.

a와 z의 코드포인트: 97 과 122

단, 두 변수 code_point_a, code_point_b 각각에 'a''z'의 유니코드 코드 포인트를 할당한 후에 print() 함수의 인자로 활용해야 한다.

답:

code_point_acode_point_b 두 변수 각각에 ord() 함수를 호출하여 반환된 값을 할당한 다음에 아래 코드에서처럼 print() 함수의 인자로 두 변수를 활용한다.

code_point_a = ord('a')
code_point_z = ord('z')
print('a와 z의 코드포인트:', code_point_a, '과', code_point_z)
a와 z의 코드포인트: 97 과 122

(2) chr() 함수는 유니코드 코드 포인트(정수)가 주어지면 그 숫자에 해당하는 문자를 반환한다. 숫자 97, 98, 99를 chr() 함수의 인자로 사용할 때 어떤 문자들이 연속으로 출력되는지 확인하는 코드를 작성하라.

답:

97, 98, 99는 각각 영어 소문자 알파벳 'a', 'a', 'a'에 해당하는 유니코드 코드 포인트다.

print("97:", chr(97))
print("98:", chr(98))
print("99:", chr(99))
97: a
98: b
99: c

(3) ord() 함수가 반환하는 값은 단순한 정수이므로 숫자처럼 덧셈이나 곱셈에 활용할 수 있다. 알파벳 'a''b'의 유니코드 코드 포인트 값을 서로 더한 결과가 어떤 문자의 코드 포인트와 같은지 chr() 함수를 사용해 확인하는 코드를 작성하라.

답:

97 + 98 = 195 이고, 이는 라틴 확장 문자열 중 Ã (A with tilde) 에 해당한다.

code_a = ord('a')
code_b = ord('b')
sum_code = code_a + code_b

print("두 문자의 코드를 더한 값:", sum_code)
print("해당 값의 유니코드 문자:", chr(sum_code))
두 문자의 코드를 더한 값: 195
해당 값의 유니코드 문자: Ã

(4) ROT13 은 영어 단어의 각 알파벳을 13 자리만큼 회전시키는 방식의 간단한 암호 작성 기법이다. 알파벳을 회전시킨다는 것은 해당 알파벳을 지정된 수만큼 이동한 위치에 있는 알파벳으로 대치한다는 의미이며, 영어 알파벳이 총 26개이기에 알파벳 'a'부터 'z'까지 회전하면서 이동시킨다. 예를 들어, 'a' 를 3만큼 이동하면 'd' 가, 'z' 를 3만큼 이동하면 'c' 가 된다. 영어 소문자 알파벳이 주어졌을 때 ROT13 기법으로 암호화된 알파벳을 계산하는 코드를 작성하라.

힌트: ord() 함수와 chr() 함수를 이용한다.

주어진 영어 소문자 알파벳의 유니코드 코드 포인트에 13을 더한 값에 해당하는 문자를 구하면 된다. 그런데 영어 소문자 알파벳은 총 26개이기에 주어진 알파벳을 13 자리만큼 회전시키려면 ord(char) 값에 따라 다르게 계산해야 한다. 이를 위해 먼저 소문자 'a''z'의 유니코드 코드 포인트를 확인한다.

ord('a')
97
ord('z')
122

영어 소문자의 유니코드 범위는 97('a')부터 122('z')다. 원래 문자의 유니코드 코드 포인트에 13만큼 더했을 때 소문자의 마지막인 'z'(122)를 넘어간다면, 알파벳 전체 개수인 26을 빼주어 다시 맨 앞('a')으로 순환하도록 만들어 준다.

  • ord(char) + 13 <= 122인 경우: ord(char)에 13을 더해준 값에 해당하는 알파벳

  • ord(char) + 13 > 122인 경우: ord(char)에 13을 더한 뒤 26을 뺀 (결과적으로 13을 빼준) 값에 해당하는 알파벳

아래 코드는 위 설명을 그대로 코드로 구현하여 'a'를 13자리만큼 이동시키면 'n'이 됨을 확인시켜준다.

char = 'a'

if ord(char) + 13 <= 122:
    encrypted = chr(ord(char) + 13)
else:
    encrypted = chr(ord(char) - 13)

print("원래 문자:", char)
print("암호화된 문자:", encrypted)
원래 문자: a
암호화된 문자: n

반면에 'z'를 13자리만큼 이동시키면 'm'이 된다.

char = 'z'

if ord(char) + 13 <= 122:
    encrypted = chr(ord(char) + 13)
else:
    encrypted = chr(ord(char) - 13)

print("원래 문자:", char)
print("암호화된 문자:", encrypted)
원래 문자: z
암호화된 문자: m

5.1.4연습문제

(1) 영어 알파벳에서 소문자와 대문자의 유니코드 코드 포인트(정수값) 차이는 항상 일정하게 유지된다. 이를 이용하여, 변수 char_upper에 할당된 대문자 'A'의 코드 포인트를 구한 후에 특정 숫자를 더해 소문자 'a' 로 변환시키는 코드를 작성하라.

답:

먼저 두 알파벳의 유니코드 코드 포인트의 차이를 계산한다. 소문자 알파벳의 유니코드 코드 포인트가 더 큼에 주의한다.

char_upper = 'A'
diff = ord('a') - ord('A')  # 소문자와 대문자의 코드값 차이 (32)
print('대문자 A와 소문자 a의 코드값 차이:', diff)
대문자 A와 소문자 a의 코드값 차이: 32

대문자 유니코드 코드 포인트에 32를 더하면 소문자 유니코드 코드포인트가 계산된다. 이값에 chr() 함수를 적용하면 소문자로 변환된 알파벳이 구해진다.

char_lower_code = ord(char_upper) + diff
char_lower = chr(char_lower_code)

print("대문자 A를 소문자로 변환:", char_lower)
대문자 A를 소문자로 변환: a

(2) 영어 알파벳 'a''z'의 유니코드 코드 포인트(정수값)의 평균을 구하면, 알파벳 순서상 한가운데 위치한 문자가 무엇인지 수학적으로 알아낼 수 있다. ord() 함수를 이용해 'a''z'의 코드 포인트를 더한 뒤 2로 나눈 몫(정수 나눗셈 //)을 구하고, 그 결과값을 chr() 함수에 전달하여 정중앙에 있는 문자를 확인하는 코드를 작성하라.

답:

두 유니코드 코드 포인트의 평균값을 두 정수의 합을 2로 나눈 몫으로 계산하고, 계산된 정수에 해당하는 알파벳을 확인하면 된다.

code_a = ord('a')
code_z = ord('z')

# 두 코드 포인트의 더한 후 2로 나눈 몫 (평균값 구하기)
mid_code = (code_a + code_z) // 2

# 계산된 코드 포인트의 문자 출력
print("a와 z의 한가운데 문자:", chr(mid_code))
a와 z의 한가운데 문자: m

(3) ROT13 암호화를 영어 대문자 알파벳에 대해 작동하도록 구현한 다음에 대문자 알파벳 'C''P'가 암호화된 알파펫을 확인하라.

힌트: 대문자 알파벳 'A''Z'의 유니코드 코드 포인트를 확인한다.

답:

먼저 대문자 알파벳 'A''Z'의 유니코드 코드 포인트를 확인한다.

ord('A')
65
ord('Z')
90

영어 대문자 알파벳의 유니코드 범위는 65('A')부터 90('Z')이다. 원래 문자의 유니코드 코드 포인트에 13만큼 더했을 때 대문자의 마지막인 'Z'(90)를 넘어간다면, 알파벳 전체 개수인 26을 빼주어 다시 맨 앞('A')으로 순환하도록 만들어 준다.

  • ord(char) + 13 <= 90인 경우: ord(char)에 13을 더해준 값에 해당하는 알파벳

  • ord(char) + 13 > 90인 경우: ord(char)에 13을 더한 뒤 26을 뺀 (결과적으로 13을 빼준) 값에 해당하는 알파벳

아래 코드가 이 성질을 이용하여 대문자 알파벳 'C'를 암호화 시킨다.

char = 'C'

if ord(char) + 13 <= 90:
    encrypted = chr(ord(char) + 13)
else:
    encrypted = chr(ord(char) - 13)

print("원래 문자:", char)
print("암호화된 문자:", encrypted)
원래 문자: C
암호화된 문자: P

5.2사용자 정의 함수

지금까지 함수로 활용한 print(), type(), round() 등은 모두 파이썬이 기본으로 제공한다. 그런데 수십 줄 이상의 코드를 작성하다 보면 특정 기능을 수행하는 코드를 반복해서 사용할 필요가 발생하곤 한다. 이런 경우 반복 사용되는 코드에 사용자가 직접 이름을 주어 함수로 정의하여 해당 코드가 필요할 때마다 정의된 함수를 대신 호출하면 된다.

함수 정의

함수는 아래 형식으로 정의된다.

def 함수이름(매개변수1, 매개변수2, ...):
    함수본문 

5.2.1함수 구성요소

함수 시그니처

def 키워드로 시작하는 줄은 함수의 이름과 함수를 호출할 때 사용하는 인자들에 대한 정보를 담으며, 보통 함수 시그니처function signature 또는 함수 헤더function header라고 불린다. 함수 시그니처의 끝에는 콜론 기호(:)가 위치한다.

함수 본문

함수 시그니처 아래에 들여쓰기와 함께 작성된 코드는 함수의 본문body이다. 함수의 본문은 함수가 호출되었을 때 실행되어야 하는 코드, 즉 필요할 때마다 반복해서 사용하고 싶은 코드가 위치한다.

함수 본문은 사용되는 코드는 일정한 깊이로 들여써야 하며, 들여쓰기는 보통 Tab 키를 이용한다. 한 번의 탭 키 입력은 일반적으로 두 번 또는 네 번의 스페이스 키 입력에 해당하는데, 파이썬 편집기마다 조금씩 다르다.

return 표현식과 함수 반환값

함수 호출 명령문의 실행은 함수 본문을 실행하다가 return 표현식 명령문이 실행되는 순간에 지정된 표현식이 가리키는 값을 함수의 반환값으로 지정하며 종료되고, 이어지는 명령문의 실행으로 넘어간다.

이는 함수 호출에 대해 어떠한 경우에도 두 개의 return 표현식 명령문이 실행되지 않음을 의미한다. 하지만 함수 호출에 의해 하나의 반환값이 정해진다고 해서 함수 본문에 return 표현식 명령문이 한 번만 사용된다는 의미는 아니다.

예를 들어, 아래 add_multiply() 함수는 두 인자의 크기를 비교해서 첫째 인자가 크면 두 인자의 덧셈을, 그렇지 않으면 두 인자의 곱셈을 반환값으로 지정한다.

def add_multiply(left, right):
    if left < right:
        sum = left + right
        return sum
    else:
        product = left * right
        return product

두 인자의 크기에 따라 덧셈 또는 곱셈의 결과가 반환되지만 어떤 경우에도 return sumreturn product 가 동시에 실행되지는 않는다. 이런 의미에서 함수의 반환값은 항상 하나다. 아래 두 add_multiply() 함수 호출 예제에서 볼 수 있듯이 어떤 값이 반환될 것인가는 함수 정의 자체보다는 함수 호출에 사용된 인자들에 의해 정해진다.

  • 첫째 인자가 둘째 인자보다 작을 때: 덧셈 실행

added = add_multiply(-2, 5)
print("더한 값:", added)
더한 값: 3
  • 첫째 인자가 둘째 인자보다 클 때: 곱셈 실행

multiplied = add_multiply(5, -2)
print("곱한 값:", multiplied)
곱한 값: -10

함수 이름과 매개변수 이름

함수 정의에 사용되는 함수 이름과 매개변수의 개수와 각 매개변수의 이름은 구현되는 함수와 각 매개변수의 기능에 맞춰 사용자가 직접 정한다. 또한 함수 이름과 함께 매개변수 이름은 1.3에서 설명한 변수 이름 짓기와 동일한 조건을 따르면서 지정되어야 한다.

예를 들어, add_multiply라는 이름은 두 개의 값이 인자로 주어졌을 때 두 값을 더하거나 곱하는 기능과 연관된다. 또한 두 개의 수를 인자로 받아 합을 계산해야 하기에 두 개의 매개변수를 사용하고, 덧셈과 곱셈 모두 연산자 왼쪽과 오른쪽에 인자가 위치하기에 두 매개변수의 이름을 leftright로 지정하였다.

5.2.2함수 인자 전달

함수의 인자argument는 함수 호출에 사용되는 값이다. 그리고 인자의 종류와 개수는 함수의 정의에 의존한다.

예를 들어, added = add_multiply(-2, 5) 변수 할당 명령문이 실행되면 added 변수에 할당할 값을 정하기 위해 먼저 add_multiply(-2, 5) 형식으로 add_multiply()함수가 -25 두 인자와 함께 호출된다.

add_multiply(-2, 5) 형식으로 함수를 호출하면 left < right 판정 결과에 따라 덧셈 또는 곱셈을 실행한다.

그런데 left < right를 판정하기 위해서는 두 변수가 가리키는 값이 지정되어 있어야 한다. 따라서 함수 호출이 발생하면 파이썬 실행기는 함수의 본문을 실행하기 전에 먼저 모든 매개변수에 대한 변수할당을 주어진 인자들을 이용하여 실행한다.

함수가 호출되었을 때 파이썬 실행기 내부에서 실제로 실행되는 명령문은 두 단계로 나뉜다.

첫째, 매개변수 각각에 대해 주어진 인자의 순서대로 변수 할당 명령문이 실행된다.

left = -2
right = 5

leftright 두 매개변수가 함수의 본문에서 사용될 때 이렇게 할당된 값을 이용한다. 이런 의미에서 '매개변수가 인자를 함수의 본문에 전달한다’고 말한다

둘째, add_multiply() 함수의 본문에 사용된 명령문이 차례대로 실행된다.

결국 add_multiply(-2, 5) 형식으로 함수 호출이 실행되면 아래 코드가 파이썬 실행기 내부에서 실행된다.

left = -2
right = 5

if left < right:
    sum = left + right
    return sum
else:
    product = left * right
    return product

add_multiply(-2, 5) 함수 호출의 반환값은 left < right 여부에 따라 결정되는데, 이 경우엔 덧셈이 실행되어 아래 코드에 의해 3이 반환값으로 지정된다.

sum = left + right
return sum

반환된 지정값은 애초에 실행된 변수 할당 명령문에 의해 added 변수에 할당되어 계속해서 활용된다.

5.2.3예제

(1) 아래 코드는 only_sum() 함수를 정의한다.

def only_sum(left, right):
    sum = left + right
    product = left * right
    return sum
    return product

아래 코드의 실행결과를 확인하고 그 이유를 설명하라.

only_sum(-2, 5)
3

답:

only_sum(-2, 5)을 호출하면 먼저 아래 코드가 파이썬 실행기에 의해 실행된다.

left = -2
right = 5
sum = left + right
product= left * right

따라서 sumprod 두 변수에 각각 2와 -10이 할당된다. 이제 아래 두 명령문을 실행할 차례다.

return sum
return product

그리고 먼저 실행된 return sum 명령문에 의해 sum에 할당된 값이 반환값으로 지정되어 반환되면서 동시에 only_sum(-2, 5) 함수 호출이 종료되어 return product 는 아예 실행되지 않는다. 결국 only_sum() 함수는 다음과 같이 정의될 수 있다. 즉, product 변수의 정의와 반환값 지정은 필요 없다.

def only_sum(left, right):
    sum = left + right
    return sum

(2) 아래 코드를 실행할 때 발생하는 오류를 설명하라.

func_hi()

def func_hi() :
  print("Hi")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[1], line 1
----> 1 func_hi()
      3 def func_hi() :
      4   print("Hi")

NameError: name 'func_hi' is not defined

파이썬 코드는 윗줄의 명령문부터 차례대로 실행된다. func_hi() 함수가 정의되기 이전에 호출되면 해당 함수가 아직 정의되지 않았다는 의미의 NameError 오류가 발생한다. 이는 함수 호출 아랫쪽에 func_hi() 함수가 정의되어 있다 하더라도 마찬가지다.

(3) 아래 코드는 소문자 알파벳 'a'를 13칸 이동시킨 결과를 확인해준다.

char = 'a'

if ord(char) + 13 <= 122:
    encrypted = chr(ord(char) + 13)
else:
    encrypted = chr(ord(char) - tur13)

print("원래 문자:", char)
print("암호화된 문자:", encrypted)

위 코드의 일부를 이용해서 아래 기능을 갖는 rot13() 함수를 정의하라.

  • char라는 매개변수 하나 사용

  • 반환값은 인자로 사용된 소문자 알파벳을 13칸 이동시킨 위치의 소문자 알파벳

답:

함수를 정의하려면 함수의 시그니처와 반환값을 먼저 정해야 한다. 먼저 함수의 시그니처는 다음과 같아야 한다.

def rot13(char):

함수 반환값은 13칸 이동한 위치의 소문자 알파벳이며, 위 코드에서 encrypted 변수에 가리키는 값이다. 따라서 반환값은 아래처럼 지정된다.

return encrypted

본문의 나머지는 char를 이용해서 encrypted를 계산하는 과정으로 구성된다. 즉, 아래 과정이 함수 본문으로 사용된다.

if ord(char) + 13 <= 122:
    encrypted = chr(ord(char) + 13)
else:
    encrypted = chr(ord(char) - tur13)

return encrypted

위 설명을 종합하여 rot13() 함수를 다음과 같이 정의한다.

def rot13(char):
    if ord(char) + 13 <= 122:
        encrypted = chr(ord(char) + 13)
    else:
        encrypted = chr(ord(char) - 13)
    
    return encrypted

이제 소문자 알파벳을 인자로 하여 rot() 함수를 호출해서 제대로 작동하는지 확인해 본다.

rot13('a')
'n'
rot13('n')
'a'

5.2.4연습문제

(1) 정수 num을 인자로 해서 호출되었을 때, 1의 자릿수가 3의 배수이면 입력값의 세 배를, 아니면 두 배를 반환하는 함수 three_or_not() 함수를 구현하라. 매개변수는 num 하나만 사용한다.

힌트: if ... else ... 조건문과 나머지 연산자 % 활용

함수를 정의하기 위해 먼저 주어진 조건으로부터 정의되어야 하는 함수의 시그니처와 반환값이 다음과 같음을 알 수 있다.

  • 시그니처: def three_or_not(num):

  • 반환값: num * 3 또는 num * 2

반환값은 인자로 주어진 정수의 1의 자릿수가 3의 배수인지 여부에 따라 달라진다. 따라서 정수의 1의 자릿수를 확인하는 방법을 확인한 다음에 반환값을 지정하도록 함수의 본문을 작성한다.

  • 정수의 1의 자릿수 확인: 10으로 나눴을 때의 나머지

123 % 10
3
27 % 10
7
  • 3의 배수 여부: 3으로 나눈 나머지가 0인지 여부

3 % 3 == 0
True
7 % 3 == 0
False

종합하면, num을 10으로 나눴을 때의 나머지에 대해 3의 배수 여부를 확인하고 그에 따라 num의 3배수 또는 2배수를 반환값으로 지정하는 본문으로 함수를 구현하면 된다.

def three_or_not(num):
    unit = num % 10
    if unit % 3 == 0:
        return num * 3
    else:
        return num * 2
  • 3은 3은 배수

three_or_not(13)
39
  • 7은 3의 배수 아님

three_or_not(27)
54

(2) 짝수, 홀수 여부를 판정하는 함수를 활용하는 문제다.

가. 정수 인자와 함께 호출되어 인자가 짝수일 때는 1, 홀수일 때는 0을 반환하는 함수 even_odd()를 구현하라. 매개변수는 num 하나만 사용한다. 단, 함수 본문에서 return 표현식 명령문은 한 번만 사용되어야 한다.

힌트: if ... else ... 조건문과 나머지 연산자 % 활용

num의 인자로 지정되는 정수의 짝수, 홀수 여부는 2로 나눈 나머지가 0인지, 1인지에 의해 결정된다. 따라서 num % 2 == 0 논리식의 진릿값에 따른 if 조건문을 아래처럼 사용한다.

def even_odd(num):
    if num % 2 == 0:
        return 1
    else:
        return 0
even_odd(10)
1
even_odd(11)
0

위 함수는 하지만 return 표현식 명령문을 한 번만 사용해야 한다는 조건을 위배한다. 이유는 if 조건문을 사용하면서 각각의 경우에 대해 반환값을 지정하였기 때문이다. 따라서 조건 결과에 따라 매번 반환값을 바로 지정하는 대신 해당 경우에 반환될 값을 변수에 할당해서 저장해 둔다. 그런 다음 if 조건문이 완료된 후에 한 번만 해당 변수가 가리키는 값을 반환값으로 지정한다. 단, 각각의 경우에 정의되는 변수 이름이 동일해야 함에 주의한다.

def even_odd(num):
    if num % 2 == 0:
        answer = 1
    else:
        answer = 0  
      
    return answer
even_odd(10)
1
even_odd(11)
0

나. even_odd() 함수를 이용하여 인자가 짝수면 2로 나눈 몫을, 홀수이면 3배 더하기 1을 한 값을 반환하는 mini_collatz() 함수를 정의하라. 참고로 collatz는 콜라츠라고 읽으며, 콜라츠는 콜라츠 추측이라는 아직 미해결로 남아 있는 문제를 1937년에 제시한 독일 수학자 이름이다.

답:

주어진 값 num이 짝수인지 여부는 num % 2 == 0으로 판단하여 mini_collatz() 함수를 다음과 같이 정의할 수 있다.

def mini_collatz(num):
    if num % 2 == 0:
        return num // 2
    else:
        return num * 3 + 1

위 정의는 하지만 even_odd() 함수를 사용하지 않기에 수정이 필요하다. 이미 정의된 함수를 이용하려면 해당 함수의 시그니처, 특히 인자에 대한 정보와 반환값의 정보를 알아야 한다.

even_odd() 함수는 인자로 주어진 정수의 짝수, 홀수 여부에 따라 1 또는 0을 반환한다. 이 두 성질을 이용하여 mini_collatz() 함수를 다음과 같이 정의할 수 있다. 특히, num % 2 == 0even_odd(num) == 1로 대체함에 주의한다.

def mini_collatz(num):
    if even_odd(num) == 1:
        return num // 2
    else:
        return num * 3 + 1
  • 짝수는 2로 나누기

mini_collatz(10)
5
  • 홀수는 3배 더하기 1

mini_collatz(7)
22

5.3None 반환값

아래 코드에 정의된 double_int() 함수의 정의는 return ... 명령문을 사용하지 않는다. 즉, 반환값을 지정하지 않는다.

def double_int(num_param):
    double_param = num_param * 2

그런데 아래 코드를 실행하면 None 이라는 이상한 값이 출력된다.

none_return = double_int(5)
print(none_return)
None

앞서 설명한 대로 double_int(5)가 호출되면 아래 코드가 실행된다.

num_param = 5
double_param = num_param * 2

위 코드가 실행되면 double_param 변수에 10이 할당되는데 이후에 실행할 아무런 명령문도 없기에 반환값이 지정되지 않은 채로 함수의 실행이 종료된다.

none_return 변수에는 아무 것도 할당되지 않아서 오류가 발생해야 하지만, 파이썬 실행기는 이런 경우에 반환값을 None으로 강제 지정한다. 즉, double_int() 함수는 실제로는 아래 코드처럼 정의된 것으로 간주된다.

def double_int(num_param):
    double_param = num_param * 2
    return None                   # None 반한값 지정

None 값

이상하게 들리겠지만 None은 '아무런 의미가 없음’을 의미하는 값이다. 아무런 의미가 없는 값이지만 그래도 하나의 값이라서 변수 할당에 사용될 수 있다.

non_value = None
print(non_value)
None

하지만 연산에 활용될 수 없어서 허용되지 않은 자료형의 값을 연산에 사용했다는 의미의 TypeError 오류가 발생한다.

non_value + 1
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[7], line 1
----> 1 non_value + 1

TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

print() 함수의 반환값

반환값이 None인 대표적인 함수가 print() 함수이다. 그런데 print() 함수를 아래와 같이 활용하면 마치 인자를 그대로 반환하는 것처럼 보인다.

print(3.14)
3.14

하지만 화면에 출력되는 3.14는 반환값이 아니다. 다만 print() 함수의 본문에 포함된 명령문 중에서 지정된 값을 화면에 출력하는 기능을 가진 어떤 명령문이 실행된 결과에 불과하다. 예를 들어, 아래 변수 할당 명령문을 실행하면 3.14가 화면에 출력된다.

print_return = print(3.14)
3.14

하지만 변수 할당 명령문은 등호 기호 오른편에 위치한 표현이 가리키는 값을 등호 기호 왼편에 위치한 변수가 가리키도록 하는 기능 이외에 아무 것도 하지 않는다. 그럼에도 불구하고 화면에 3.14가 출력되는 이유는 print(3.14) 함수 호출의 결과를 계산하는 과정에서 앞서 설명한 대로 인자로 지정된 3.14를 화면에 출력하기 때문이다. 반면에 print(3.14)를 실행한 뒤에 반환된 값은 None임을 아래 코드가 확인해준다.

print(print_return)
None

5.3.1예제

(1) 아래 코드의 실행 결과를 예측하고, 화면에 출력된 result의 값을 설명하라.

def show_multiply(a, b):
    product = a * b
    print("두 수의 곱:", product)

result = show_multiply(3, 4)
print("함수의 반환값:", result)
두 수의 곱: 12
함수의 반환값: None

답:

명령문은 윗줄부터 차례대로 실행된다.

  • 먼저 변수 result에 값을 할당하기 위해 show_multiply(3, 4) 함수가 호출된다.

  • 함수 본문이 실행되면서 a * b의 결과인 12가 계산되고, print("두 수의 곱:", product) 명령문에 의해 화면에 두 수의 곱: 12가 먼저 출력된다.

  • 그다음, show_multiply() 함수에는 return 명령문을 통한 반환값 지정이 없기 때문에 파이썬 실행기는 강제로 None을 반환한다.

  • 따라서 변수 result에는 None이 할당되고, 마지막 명령문에 의해 함수의 반환값: None이 출력된다.

(2) 아래 코드의 실행 결과를 설명하라. func() 함수는 인자를 사용하지 않음에 주의한다.

print("A")

def func() :
    print("B")

print("C")

func()
A
C
B

파이썬 코드는 윗줄의 명령문부터 차례대로 실행되기에 아래 순서대로 명령문이 실행된다.

  • print("A") 실행: 'A' 출력

  • func() 함수 정의

  • print("C") 실행: "C" 출력

  • func() 함수 호출: 인자 없이 함수 실행되어 함수 본문인 print("B") 실행. 따라서 "B" 출력

(3) 아래 코드의 실행 결과를 설명하라.

x = print("A")

def func() :
    print("B")

y = func()

print(x == y)
A
B
True

윗줄의 명령문부터 차례대로 실행된다.

  • x = print("A") 실행

    • 변수 x에 할당할 값을 정하기 위해 print("A") 호출

    • print("A") 호출 과정에서 'A'를 화면에 출력.

    • 변수 xprint("A")의 실행이 끝나면서 반환하는 값인 None 할당.

  • func() 함수 정의

  • y = func() 실행

    • 변수 y에 할당할 값을 정하기 위해 func() 호출

    • 함수 본문인 print("B") 실행 과정에서 'B'를 출력.

    • 변수 yfunc() 호출의 실행이 끝나면서 반환하는 값인 None 할당.

  • print(x == y) 실행

    • 변수 x와 변수 y가 가리키는 값이 모두 None임.

    • None이 의미 없는 값이지만 NoneNone은 서로 동일한 값임.

    • 따라서 x == yTrue로 계산됨.

    • 화면에 True 출력

5.3.2연습문제

(1) 아래 코드를 실행하면 한 줄의 문자열이 정상적으로 출력된 후 오류가 발생한다. 화면에 출력되는 문자열이 무엇인지 확인하고, 오류가 발생하는 이유를 설명하라.

def double_number(n):
    result = n * 2
    print("두 배 계산 완료:", result)

value = double_number(5)
final_value = value + 10
print("최종 값:", final_value)
두 배 계산 완료: 10
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[43], line 6
      3     print("두 배 계산 완료:", result)
      5 value = double_number(5)
----> 6 final_value = value + 10
      7 print("최종 값:", final_value)

TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

답:

코드를 실행하면 제일 먼저 아래 문자열이 정상적으로 출력된다.

두 배 계산 완료: 10

하지만 그 직후 함수 본문에 반환값을 지정하지 않은 double_number() 함수가 호출되어 final_value에 사칙연산에 사용할 수 없는 None이 할당된다. 최종적으로 연산에 사용할 수 없는 자료형의 값을 사용해서 발생한 오류를 의미하는 TypeError가 발생하게 된다.

(2) 아래 코드의 실행 결과를 설명하라.

def funcA() :  
    print("A")

def funcB() :  
    print("B")
    funcA()   

funcB()
B
A

코드는 윗줄의 명령문부터 차례대로 실행된다. 따라서 아래 순서대로 명령문이 실행된다.

  • funcA(), funcB() 두 개의 함수 정의. 모두 인자 사용하지 않음.

  • funcB() 호출:

    • print("B") 실행. 따라서 "B" 출력

    • funcA() 호출:

      • print("A") 실행. 따라서 "A" 출력

따라서 아래 순서대로 화면에 출력된다.

B, A

(3) 아래 코드의 실행 결과를 설명하라.

print("Hi")   

def funcA() :  
    print("A")

def funcB() :  
    print("B")
    funcA()   

def funcC() :
    print("C")
    funcA()
    funcB()

print("D")
funcC()
Hi
D
C
A
B
A

코드는 윗줄의 명령문부터 차례대로 실행된다. 따라서 아래 순서대로 명령문이 실행된다.

  • print("Hi") 실행: 'Hi' 출력

  • funcA(), funcB(), funcC() 세 개의 함수 정의. 모두 인자 사용하지 않음.

  • print("D") 실행: "D" 출력

  • funcC() 호출

    • print("C") 실행. 따라서 "C" 출력

    • funcA() 호출:

      • print("A") 실행. 따라서 "A" 출력

    • funcB() 호출:

      • print("B") 실행. 따라서 "B" 출력

      • funcA() 호출:

        • print("A") 실행. 따라서 "A" 출력

따라서 아래 순서대로 화면에 출력된다.

Hi, D, C, A, B, A

5.4위치 인자와 키워드 인자

함수의 인자는 위치 인자키워드 인자 두 종류로 구분된다.

  • 위치 인자positional argument: 함수를 호출할 때 반드시 순서에 맞게 지정되어야 하는 인자

  • 키워드 인자keyword argument: 함수를 호출할 때 필요에 따라 추가로 지정될 수 있는 인자. 키워드 인자들 사이의 순서는 중요하지 않지만 반드시 위치 인자 다음에 자리해야 함.

주어진 값을 위치 인자로 사용해야 할지 키워드 인자로 사용해야 할지는 함수의 정의에 의존한다. print() 함수를 이용하여 위치 인자와 키워드 인자의 활용법을 소개한다.

5.4.1print() 함수의 인자

여러 개의 값을 화면에 출력하려면 print() 함수에 여러 개의 인자를 지정하여 호출한다. 그러면 인자들이 공백으로 구분되어 함께 한 줄에 출력된다. 예를 들어 아래 코드는 안녕, 파이썬, ! 세 개의 문자열을 공백으로 구분하면서 한 줄에 출력한다.

print('파이썬', '안녕', '!')
파이썬 안녕 !

출력 대상으로 지정된 세 개의 인자가 공백으로 구분되어 한 줄에 출력되는 이유는 sep이라는 매개변수에 공백 문자 (' ')가 기본(defalut) 키워드 인자로 약속되어 있기 때문이다. 위 코드를 실행하면 파이썬 실행기는 실제로는 아래 코드를 실행한다.

print('파이썬', '안녕', '!', sep=' ')
파이썬 안녕 !

변수 할당 명령문처럼 생긴 sep=' '은 화면에 출력해야할 인자들을 공백으로 구분하여 한 줄에 출력하도록 하는 기본 옵션 기능으로 작동한다. 함수 호출에 키워드 인자를 굳이 지정하지 않으면 기본 키워드 인자가 대신 사용된다. 따라서 함수를 활용할 때 숨겨진 키워드 인자들이 있는지를 미리 알아두어야 제대로된 함수 호출 명령문을 작성할 수 있다.

키워드 인자 변경

키워드 인자에 할당된 값은 임의로 바꿀 수 있다. 예를 들어, 출력 대상으로 지정된 각각의 인자를 하이픈 기호-로 구분하려면 즉, 인자와 인자를 하이픈으로 연결하려면 아래 코드에서처럼 sep 매개변수에 문자열 -을 키워드 인자로 지정하면 된다.

print('파이썬', '안녕', '!', sep='-')
파이썬-안녕-!

위 코드에서 print() 함수를 호출할 때 사용된 위치 인자와 키워드 인자는 다음과 같다.

  • 위치 인자: 화면 출력에 사용되는 세 개의 인자, '파이썬', '안녕', '!'

  • 키워드 인자: sep 매개변수에 할당된 문자열 '-'

print() 함수의 키워드 인자

print() 함수의 위치 인자는 0개 이상 원하는 만큼 사용할 수 있다. 반면에 키워드 인자는 sep 이외에 end, file, flush 매개변수에 할당될 수 있다. 실제로 print() 함수의 시그니처는 다음과 같다.

print?
Signature: print(*args, sep=' ', end='\n', file=None, flush=False)
Docstring:
Prints the values to a stream, or to sys.stdout by default.

sep
  string inserted between values, default a space.
end
  string appended after the last value, default a newline.
file
  a file-like object (stream); defaults to the current sys.stdout.
flush
  whether to forcibly flush the stream.
Type:      builtin_function_or_method

매개변수 각각의 역할은 다음과 같다.

각 키워드 인자는 고유의 기능을 수행하며, 지정된 키워드 인자를 사용할 경우 함수 호출 과정에서 해당 키워드 인자를 굳이 언급할 필요가 없다. 예를 들어, end 매개변수의 기본 키워드 인자가 줄바꿈이기에 print() 함수를 호출한 다음엔 항상 줄바꿈이 실행된다. 만약에 빈 문자열 ''end 매개변수에 할당하면 줄바꿈을 하지 않는다. 아래 두 코드를 비교하면 end 매개변수에 할당되는 키워드 인자의 역할을 확인할 수 있다.

  • 기본 키워드 인자를 그대로 사용한 경우: 첫째 print() 함수 호출에 의해 '파이썬 안녕 !'이 출력된 후에 줄바꿈이 이뤄지고 그 다음에 둘째 print() 함수 호출에 의해 '===' 문자열이 출력됨.

print('파이썬', '안녕', '!')
print('줄바꿈 후 현재 문장 출력.')
파이썬 안녕 !
줄바꿈 후 현재 문장 출력.
  • end 매개변수의 인자를 빈 문자열로 지정한 경우: 첫째 print() 함수 호출에 의해 '파이썬 안녕 !'이 출력된 후에 줄바꿈을 하지 않은 채로 둘째 print() 함수 호출에 의해 '===' 문자열이 출력됨.

print('파이썬', '안녕', '!', end='')
print('줄바꿈 없이 현재 문장 출력.')
파이썬 안녕 !줄바꿈 없이 현재 문장 출력.

5.4.2키워드 인자 사용 함수 정의

아래 four_basic_operations() 함수는 두 개의 정수 또는 부동소수점이 주어졌을 때 셋째 인자로 주어진 문자열에 따라 사칙연산 중의 하나를 실행해서 계산된 값을 반환한다.

def four_basic_operations(left, right, op=None):
    if op == "sum":
        result = left + right
    elif op == "difference":
        result = left - right
    elif op == "product":
        result = left * right
    elif op == "quotient":
        result = left / right
    else:
        result = None
    return result    

위 함수 정의에 사용된 매개변수 세 개의 역할은 다음과 같다.

  • left: 연산의 왼쪽 인자로 사용될 값을 할당받을 매개변수

  • right: 연산의 오른쪽 인자로 사용될 값을 할당받을 매개변수.

  • op: 연산자를 지정하는 문자열. 기본 인자로 None 지정.

함수 본문은 op 매개변수에 대해 지정되는 키워드 인자에 따라 leftright 두 개의 키워드 인자에 대한 연산을 실행하고 그 결과를 반환한다.

op 키워드 인자수행 연산
op="sum"덧셈
op="difference"뺄셈
op="product"곱셈
op="quotient"나눗셈
기타연산 없음

아래 코드에서처럼 four_basic_operations() 함수를 호출할 때 셋째 인자가 생략되면 자동으로 Noneop 매개변수의 키워드 인자로 지정된다. 따라서 함수 본문의 if 조건문에서 else 부분의 명령문 실행되어 result 변수에 None 할당된다. 결국 None이라는 쓸모 없는 값을 반환하게 되어 아래 코드를 실행해도 아무 것도 얻지 못한다.

four_basic_operations(5, 3)

위 코드를 실행할 때 파이썬 실행기는 아래 코드를 실행한다.

four_basic_operations(5, 3, op=None)

None 대신 적절할 문자열을 op 매개변수의 키워드 인자로 지정하면 적절한 연산 결과가 반환된다. 예를 들어, 아래 코드는 5 더하기 3, 즉 8을 반환한다.

four_basic_operations(5, 3, op='sum')
8

반면에 아래 코드는 7 나누기 2의 결과는 3.5를 반환한다.

four_basic_operations(7, 2, op='quotient')
3.5

5.4.3키워드 인자 위치와 순서

아래 두 코드는 동일한 결과를 낳는다.

print('파이썬', '안녕', '!', sep='-', end='')
print('줄바꿈 없이 현재 문장 출력.')
파이썬-안녕-!줄바꿈 없이 현재 문장 출력.
print('파이썬', '안녕', '!', end='', sep='-')
print('줄바꿈 없이 현재 문장 출력.')
파이썬-안녕-!줄바꿈 없이 현재 문장 출력.

이유는 키워드 인자들은 작성 순서와 상관없이 할당되어지는 매개변수가 함께 지정되기 때문이다. 반면에 아래 두 코드는 결과가 다르다.

print('파이썬', '안녕', '!', sep='-', end='')
print('줄바꿈 없이 현재 문장 출력.')
파이썬-안녕-!줄바꿈 없이 현재 문장 출력.
print('안녕', '파이썬', '!', end='', sep='-')
print('줄바꿈 없이 현재 문장 출력.')
안녕-파이썬-!줄바꿈 없이 현재 문장 출력.

이유는 위치 인자들의 순서를 바꾸면 값이 할당되는 매개변수가 달라지기 때문이다. 결국 인자들의 순서가 절대적으로 중요한데, 키워드 인자들의 경우엔 매개변수를 함께 작성하기 때문에 순서가 중요하지 않다.

단, 키워드 인자는 반드시 위치 인자들보다 나중에 위치해야 한다. 그렇지 않으면 아래 코드에서처럼 함수 호출 형식이 틀렸다는 의미의 SyntaxError 오류가 발생한다.

print('파이썬', '!', sep='-', '안녕', end='')
print('줄바꿈 없이 현재 문장 출력.')
  Cell In[59], line 1
    print('파이썬', '!', sep='-', '안녕', end='')
                                           ^
SyntaxError: positional argument follows keyword argument

5.4.4예제

(1) round() 함수의 매개변수 ndigits를 이용하여 아래 부동소수점을 소수점 이하 입곱째 자리에서 반올림하라.

pi = 3.141592653589793

답:

round() 함수의 시그니처를 확인하면 함수 정의에 사용된 매개변수들을 확인할 수 있다. 함수의 시그니처는 함수 이름만으로 확인된다.

round
<function round(number, ndigits=None)>

시그니처에 따르면 round() 함수는 number, ndigits 두 개의 매개변수를 사용하는데 그중에 ndigits 매개변수에 키워드 인자를 지정할 수 있다. 기본 키워드 인자는 None이며, 이는 반환값으로 소수점 이하를 버린 부동소수점으로 지정한다. 반면에 ndigits=6으로 지정하면 소수점 이하 일곱째 자리에서 반올림한 부동소수점을 반환한다.

pi = 3.141592653589793

result = round(pi, ndigits=6)
print("일곱째 자리에서의 반올림 결과:", result)
일곱째 자리에서의 반올림 결과: 3.141593

(2) 함수 호출에 사용되는 인자들이 함수 정의에 사용된 매개변수들의 순서에 맞게 사용된다면 키워드 인자들을 매개변수를 언급하지 않으면서 사용해 된다. 예를 들어 아래 두 코드는 파이썬 실행기에 의해 동일하게 처리된다.

four_basic_operations(10, 5, op='difference')
5
four_basic_operations(10, 5, 'difference')
5

반면에 print() 함수의 경우는 키워드 인자를 사용할 때 매개변수를 반드시 언급해야 함을 확인해준다. 두 코드의 결과가 다른 이유를 설명하라.

print('파이썬', '안녕', '!', sep='---')
파이썬---안녕---!
print('파이썬', '안녕', '!', '---')
파이썬 안녕 ! ---

답:

print() 함수를 호출할 때 사용할 수 있는 키워드 인자의 개수가 함수를 정의할 때 정해지지 않았기 때문이다. 따라서 둘째 코드는 네 개의 인자 모두를 키워드 인자로 처리하며, sep 매개변수에 대한 인자는 기본값은 공백을 사용한다.

(3) 아래 rot13() 함수는 ROT13 암호 기법을 실행한다.

def rot13(char):
    if ord(char) + 13 <= 122:
        encrypted = chr(ord(char) + 13)
    else:
        encrypted = chr(ord(char) - 13)
    
    return encrypted

칸 이동을 13 뿐만 아니라 임의의 수만큼 진행하는 암호화 기법을 실행하는 rot() 함수를 구현하라.

  • 두 개의 매개변수 charturn 사용

  • char는 영어 대문자 알파벳을 위치 인자로 받고, turn은 칸의 이동 횟수를 키워드 인자로 받음. 기본 키워드 인자는 13.

  • 반환값은 encrypted 변수가 가리키는 값. 인자로 지정된 대문자 알파벳을 turn만큼 이동시켰을 때의 알파벳을 가리켜야 함.

  • 단, turn이 1보다 작거나 25보다 크면 칸 이동을 하지 않아야 함.

답:

기존 rot13() 함수는 이동하는 칸 수가 13으로 고정되어 있었지만, 이를 키워드 인자를 통해 임의의 수(기본값 13)만큼 이동할 수 있도록 일반화하는 아래 모양의 시그니처를 가져야 한다.

def rot(char, turn=13):

반환값은 rot13() 경우와 동일하게 암호화된 알파벳을 가리키는 encrypted 변수로 지정한다. 다만 encrypted 변수의 정의를 정수 13이 아닌 turn 변수를 이용해야 한다.

먼저 1 <= turn <= 25이 성립하지 않으면 encryptedchar을 그대로 할당한다. 만약 해당 조건이 성립하면 아래 내용을 참고해서 함수를 작성한다.

  • 영어 대문자의 유니코드 범위는 65('A')부터 90('Z')까지다.

  • 원래 문자의 유니코드 포인트에 turn만큼 더했을 때 90을 넘어간다면, 알파벳 전체 개수인 26을 빼주어 다시 맨 앞(A)으로 순환하도록 만들어 줄 수 있다.

위 설명을 이용하여 rot() 함수를 다음과 같이 정의할 수 있다.

def rot(char, turn=13):
    # turn이 1 이상 25 이하가 아닐 때는 원래 문자를 반환
    if not (1 <= turn <= 25):
        encrypted = char

    # 문자를 이동시켰을 때 Z(90)를 넘어가는지 확인
    if ord(char) + turn <= 90:
        encrypted = chr(ord(char) + turn)
    else:
        # Z를 넘어가면 26을 빼서 다시 앞부분순서로 돌아가게 함
        encrypted = chr(ord(char) + turn - 26)
        
    return encrypted

둘째 인자, 즉 키워드 인자를 지정하지 않으면 turn이 13을 가리키게 되어 rot13() 함수와 동일하게 작동한다.

print("A를 기본값 13만큼 이동:", rot('A'))           # A -> N
print("U를 기본값 13만큼 이동:", rot('U'))           # U -> H
A를 기본값 13만큼 이동: N
U를 기본값 13만큼 이동: H

turn 매개변수의 키워드 인자를 다르게 지정하면 ROT13 기법과 다른 암호화 기법이 적용된다.

print("A를 3만큼 이동:", rot('A', turn=3))          # A -> D
print("U를 7만큼 이동:", rot('U', turn=7))          # U -> B
A를 3만큼 이동: D
U를 7만큼 이동: B

turn이 1보다 작거나 25보다 크면 암호화가 진행되지 않는다.

print("A 미이동:", rot('A', turn=0))               # A -> A
print("U 미이동:", rot('U', turn=27))              # U -> U
A 미이동: A
U 미이동: V

5.4.5연습문제

(1) 문자열과 양의 정수를 인자로 받아, 문자열을 정수만큼 반복 출력하는 함수 text_copy_2or3()를 구현하려 한다. 단, 매개변수는 다음 두 개를 사용하며, 시그니처는 다음과 같아야 한다.

def text_copy_2or3(text, count=1):
  • text: 첫째 인자. 문자열을 인자로 사용.

  • count: 둘째 인자. 양의 정수를 인자로 사용. 기본 키워드 인자로 1 지정. 0이하의 정수이면 text 인자를 그대로 화면에 출력해야 함.

예를 들어 text_copy_2or3("Hello ", 3)를 실행하면 다음처럼 출력돼야 한다.

Hello Hello Hello 

반면에 text_copy_2or3("Hello ")를 실행하면 다음처럼 출력돼야 한다.

Hello 

힌트: print() 함수 활용

text에 주어진 문자열을 count 정수만큼 반복하려면 문자열 복제 연산을 이용한다.

text * cound

복제 연산 결과를 출력하는 print() 명령문을 작성하면 된다. 단, 반환값에 대한 요구사항은 없기에 return 표현식 명령문은 사용하지 않는다. 즉, 함수의 반환값이 인자와 무관하게 항상 None이다.

def text_copy_2or3(text, count=1):
    print(text * count)
text_copy_2or3("Hello ", 3)
Hello Hello Hello 
text_copy_2or3("Hello ")
Hello 

그런데 문자열 이어붙이기 연산은 양의 정수에 대해서만 제대로 작동한다. 0 이하의 정수에 대해서는 연산 결과가 무조건 빈 문자열이기 때문이다. 아래 두 코드를 실행하면 아무 것도 출력되지 않지만 사람 눈에 보이지 않는 빈 문자열 ''이 출력된다.

text_copy_2or3("Hello ", 0)

text_copy_2or3("Hello ", -2)

아래 함수는 count가 양의 정수가 아닌 경우를 별도로 다뤄 첫째 인자를 그대로 화면에 출력하도록 한다.

def text_copy_2or3(text, count=1):
    if count <= 0:
        print(text)
    else:
        print(text * count)

양의 정수에 대해서는 이전과 동일하게 작동한다.

text_copy_2or3("Hello ", 3)
Hello Hello Hello 
text_copy_2or3("Hello ")
Hello 

반면에 1보다 작은 값에 대해서는 첫째 인자를 다시 화면에 출력한다.

text_copy_2or3("Hello ", 0)
Hello 
text_copy_2or3("Hello ", -2)
Hello 

(2) 문자열 또는 리스트 포함된 항목을 인덱싱으로 확인할 수 있다. 예를 들어 'python' 문자열의 0번 인덱스의 문자는 'p'이고, [1, 3, 5, 7, 9] 리스트의 -2번 인덱스의 항목은 7이다.

language = 'python'
odd_upto_10 = [1, 3, 5, 7, 9]

print('python 문자열의 0번 인덱스 항목:', language[0])
print('홀수 리스트의 뒤에서 2번째 항목:', odd_upto_10[-2])
python 문자열의 0번 인덱스 항목: p
홀수 리스트의 뒤에서 2번째 항목: 7

문자열 또는 리스트의 길이가 n일 때 인덱스로 허용되는 정수의 범위는 아래와 같다.

  • 양의 정수 인덱스: 0부터 (n-1) 까지의 양의 정수

  • 음의 정수 인덱스: -1부터 -n 까지의 음의 정수

즉, 아래 범위에 포함된 정수들만 인덱스로 사용 가능하다.

-n <= 정수인덱스 < n

언급된 범위를 범위를 벗어나는 인덱스를 사용하면 오류가 발생한다.

예를 들어, 아래 두 코드는 허용 범위를 벗어난 정수를 인덱스로 사용하면 오류가 발생함을 보여준다.

language[6]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[83], line 1
----> 1 language[6]

IndexError: string index out of range
odd_upto_10[-8]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[84], line 1
----> 1 odd_upto_10[-8]

IndexError: list index out of range

문자열 또는 리스트의 인덱싱과 동일한 기능을 지원하지만 허용된 범위의 인덱스가 아닐 때 오류를 발생시키는 대신 지정된 값을 반환하는 getitem() 함수를 정의하라. 단, 함수 시그니처는 다음과 같아야 한다.

def getitem(str_or_list, idx, default=None):
  • str_or_list: 문자열 또는 리스트를 받는 매개변수

  • idx: 인덱스를 받는 매개변수

  • default=None: idx가 허용된 범위를 벗어날 때 오류를 발생시키는 대신 사용되는 반환값 지정. 기본 키워드 인자는 None

답:

getitem(str_or_list, idx, default) 형식의 함수 호출이 발생했을 때 함수의 본문은 아래 내용을 실행해야 한다.

  1. str_or_list의 길이 확인

  2. idx가 허용 범위에 속하는지 여부 판단

  3. 반환값 지정

    • 인덱스가 허용된 범위에 속하는 경우: str_or_list[idx]를 통해 해당 위치의 항목 반환

    • 인덱스가 허용된 범위를 벗어난 경우: default 값 반환

위 내용을 반영하여 getitem() 함수를 다음과 같이 정의한다.

def getitem(str_or_list, idx, default=None):
    # 인덱스가 허용된 범위 안에 있는지 확인
    if -len(str_or_list) <= idx < len(str_or_list):
        return str_or_list[idx]
    else:
        return default

허용된 범위에 속하는 정수 인덱스는 정상적으로 작동한다.

getitem(language, 0)
'p'
getitem(odd_upto_10, -2)
7

허용된 범위를 벗어난 정수 인덱스에 대해서는 default 값이 반환된다.

  • default의 기본 키워드 인자 사용하는 경우

getitem(language, 6)
  • default의 키워드 인자를 별도로 지정하는 경우

getitem(odd_upto_10, -8, '인덱스 허용범위 초과')
'인덱스 허용범위 초과'

5.5지역 변수와 전역 변수

코드에 사용되는 변수를 크게 지역 변수와 전역 변수로 구분한다. 설명을 위해 아래 exponentiation() 함수를 이용한다. exponentiation() 함수는 위치 인자와 키워드 인자를 각각 하나씩 받아서 위치인자를 키워드 인자만큼 거듭제곱한다.

def exponentiation(number, power=2):
    power_result = number ** power
    return power_result

power 매개변수의 기본 키워드 인자가 2이기에 둘째 인자를 지정하지 않은 채로 exponentiation() 함수를 호출하면 지정된 인자의 제곱이 반환된다.

seventeen = 17
squared = exponentiation(seventeen)
print('17의 제곱:', squared)
17의 제곱: 289

제곱 대신 세제곱을 계산하려면 power=3 방식으로 키워드 인자를 변경해서 함수를 호출해야 한다.

twelve = 12
cubed = exponentiation(twelve, power=3)
print('12의 세제곱:', cubed)
12의 세제곱: 1728

5.5.1지역 변수

exponentiation() 함수의 두 매개변수 numberpower, 그리고 함수 본문에서 정의된 power_result 세 변수는 exponentiation() 함수와 독립적으로 존재하지 않으며, 아래와 같이 변수들을 확인하려 하면 NameError 오류가 발생한다.

print(number)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[33], line 1
----> 1 print(number)

NameError: name 'number' is not defined
print(power)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[34], line 1
----> 1 print(power)

NameError: name 'power' is not defined

이는 함수가 호출되어 실행이 완료된 이후에도 마찬가지다.

exponentiation(5, power=3)
125
print(power)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[43], line 1
----> 1 print(power)

NameError: name 'power' is not defined
print(power_result)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[44], line 1
----> 1 print(power_result)

NameError: name 'power_result' is not defined

이유는 함수 정의에 사용된 매개변수와 본문에서 정의된 변수는 함수 호출에 함수의 본문이 실행되는 과정에서만 변수 할당에 의해 정의되고, 함수의 실행이 종료되는 순간 컴퓨터(메모리)에서 삭제되어 더 이상 존재하지 않기 때문이다.

함수의 매개변수 또는 함수 본문에서 정의된 변수처럼 특정 코드의 실행시간동안만 활용할 수 있는 변수를 지역 변수local variable라 부른다.

5.5.2전역 변수

지역 변수와는 다르게 함수와 무관하게 정의된 변수는 함수의 실행과 무관하게 언제든지 코드에 사용할 수 있다. 예를 들어, seventeentwelve, 변수가 그렇다.

print("seventeen 변수:", seventeen)
seventeen 변수: 17

이처럼 언제나 활용할 수 있는 변수를 전역 변수global variable라 부른다.

함수 반환값 지정 기준

함수 호출 과정에서 계산된 값들 중에 일부를 함수 호출이 끝난 후에도 활용하려면 반환값으로 지정해야 한다. 예를 들어 5의 제곱을 계산해서 활용하고자 한다면 다음과 같이 반환값을 변수에 할당해서 활용한다.

five_cubed = exponentiation(5, power=3)
print("5의 세제곱:", five_cubed)
5의 세제곱: 125

변수 종류에 따른 이름 구분

지역 변수와 전역 변수의 이름은 구분해서 작성하는 게 좋다. 그렇지 않으면 아래 코드가 보여주듯이 혼란스러워질 수 있다.

아래 코드에서 power 가 전역 변수와 지역 변수로 사용된다. 하지만 두 변수는 서로 전혀 상관이 없다.

power = 3

print("2의 네제곱:", exponentiation(2, power=4))
print("power =", power)
2의 네제곱: 16
power = 3

위 코드 마지막 줄의 함수 호출 print("power =", power)에서의 power 변수는 첫째줄에서 정의된 전역 변수 power에 할당된 3을 가리킨다. 반면에 exponentiation(2, power=4) 함수의 매개변수로 사용된 지역 변수 power는 함수가 호출되어 실행되는 과정동안 4를 가리키지만

함수의 실행이 완료되고, 다음 명령문으로 넘어가는 순간 존재 자체가 사라진다. exponentiation() 함수의 본문에서 사용되는 power 변수를 함수 외부에서 확인할 수 있는 방법은 없다. 함수 외부로 가져오기 위해 반환값으로 지정할 수는 있지만 그렇게 하면 함수의 기능이 완전히 달라지기 때문이다.

하지만 매개변수는 함수가 실행되는 동안은 살아있다는 사실은 아래 코드에서처럼 함수 본문에 print() 함수를 추가해서 확인할 수 있다.

power = 3

def exponentiation(number, power=2):
    print("실행해야할 거듭제곱 =", power)
    power_result = number ** power
    return power_result

print("2의 네제곱:", exponentiation(2, power=4))
print("power =", power)
실행해야할 거듭제곱 = 4
2의 네제곱: 16
power = 3

동일한 이름을 지역 변수와 전역 변수에 사용함으로써 발생할 수 있는 혼란을 방지하기 위해 아래 코등처럼 서로 다른 이름을 사용할 것을 권장한다.

power_global = 3

def exponentiation(number, power=2):
    print("실행해야할 거듭제곱 =", power)
    power_result = number ** power
    return power_result

print("2의 네제곱:", exponentiation(2, power=4))
print("power_global =", power_global)
실행해야할 거듭제곱 = 4
2의 네제곱: 16
power_global = 3

5.5.3예제

(1) 아래 코드는 전역 변수 multiplier의 값을 multiply() 함수 내부에서 사용하려 시도한다. 코드를 실행했을 때 어떤 결과가 나오는지 설명하고, 그 이유를 변수 탐색 원리에 기반하여 설명하라.

multiplier = 5

def multiply(number):
    return number * multiplier

print("10 곱하기 5 =", multiply(10))
10 곱하기 5 = 50

답:

파이썬은 함수 내부에서 변수를 사용할 때 지역 변수에서 먼저 찾고, 없으면 함수 외부의 전역 변수에서 찾는다. 위 코드에서 multiplier는 함수 내부에 할당된 적이 없으므로 외부의 전역 변수 5를 가져와서 사용한다. 따라서 오류 없이 50이 정상적으로 반환되어 화면에 출력된다.

(2) 아래 코드는 함수 내부와 외부에 동일한 이름의 변수 message를 사용한다. 코드를 실행했을 때의 출력 결과를 예측하고, 왜 그렇게 출력되는지 지역 변수와 전역 변수의 특징을 이용해 설명하라.

message = "전역 변수-안녕하세요"

def say_message():
    message = "지역 변수-반갑습니다"
    print("함수 내부 출력:", message)

say_message()
print("함수 외부 출력:", message)
함수 내부 출력: 지역 변수-반갑습니다
함수 외부 출력: 전역 변수-안녕하세요

답:

함수 내부에서 변수에 값을 할당하면, 함수 외부에 동일한 이름의 변수가 있더라도 덮어쓰지 않고 새로운 '지역 변수’가 만들어진다. 지역 변수는 함수가 종료되면 메모리에서 사라지므로, 외부의 전역 변수에는 아무런 영향을 주지 않는다. 따라서 결과는 다음과 같이 출력된다.

함수 내부 출력: 지역 변수-반갑습니다
함수 외부 출력: 전역 변수-안녕하세요

(3) 지역 변수와 전역 변수에 대한 이해를 바탕으로, 아래 코드를 실행할 때 발생하는 오류의 원인을 설명하라. 또한 매개변수와 반환값을 활용하여 전역 변수 count가 1 증가한 값으로 안전하게 갱신되도록 코드를 수정하라.

count = 0

def increment():
    count = count + 1
    return count

print("증가된 값:", increment())
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
Cell In[97], line 7
      4     count = count + 1
      5     return count
----> 7 print("증가된 값:", increment())

Cell In[97], line 4, in increment()
      3 def increment():
----> 4     count = count + 1
      5     return count

UnboundLocalError: cannot access local variable 'count' where it is not associated with a value

답:

함수 내부에서 할당 연산자(=)를 사용하면 파이썬은 해당 변수를 지역 변수로 처리한다. 즉 count = count + 1을 실행할 때, 오른쪽의 count는 전역 변수가 아닌 아직 값이 정해지지 않은 지역 변수로 취급되므로 값이 없다는 오류(UnboundLocalError)가 발생한다.

전역 변수를 직접 함수 안에서 수정하려 하기보다, 아래와 같이 함수가 인자로 값을 전달받아 계산한 뒤 반환하는 형태로 수정해 사용하는 것이 바람직하다.

count = 0

def increment(current_count):
    new_count = current_count + 1
    return new_count

count = increment(count)
print("증가된 값:", count)
증가된 값: 1

5.5.4연습문제

(1) 아래 코드를 실행할 때 발생하는 오류를 설명하라.

def square_return(number):
    square = number ** 2
    return square

number = 17
square17 = square_return(number)

print(number, "의 제곱을 계산한", square17, "과", square, "는 동일한 값을 가리킨다.")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[99], line 8
      5 number = 17
      6 square17 = square_return(number)
----> 8 print(number, "의 제곱을 계산한", square17, "", square, "는 동일한 값을 가리킨다.")

NameError: name 'square' is not defined

위 코드 마지막 줄의 print() 함수의 다섯번 째 인자로 사용된 square 변수가 정의되지 않았다는 의미의 NameError 오류가 발생했다. 이유는 square 변수는 square_return(number)이 호출되어 square_return() 함수가 인자 17과 함께 실행될 때에만 존재 의미가 있는데 이미 반환값이 정해지고 함수가 종료되었기 때문이다.

(2) 다음 코드를 실행한 후 화면에 출력되는 ab의 값을 예측하고, 그렇게 출력되는 이유를 매개변수와 지역 변수의 특징에 기반하여 설명하라.

a = 10
b = 20

def swap(a, b):
    temp = a
    a = b
    b = temp
    return a, b

swap(30, 40)
print("a =", a)
print("b =", b)
a = 10
b = 20

답:

함수 swap() 정의에 사용된 매개변수 ab는 함수 내부에서만 존재하는 지역 변수다. 함수를 호출할 때 전달된 30과 40은 지역 변수 ab의 값을 바꾸는 데 사용될 뿐, 함수 바깥에서 정의된 전역 변수 a = 10b = 20에는 아무런 영향을 주지 않는다. 따라서 마지막 print() 함수는 원래의 전역 변수 값을 그대로 출력한다.

(3) 아래 add_item() 함수는 장바구니 역할을 하는 리스트 전역 변수 cart에 새로운 물건을 추가하려고 작성되었다. 하지만 코드를 실행하면 오류가 발생한다.

cart = ["사과", "우유"]

def add_item(item):
    cart = cart + [item]

add_item("파이썬 책")
print("현재 장바구니:", cart)
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
Cell In[102], line 6
      3 def add_item(item):
      4     cart = cart + [item]
----> 6 add_item("파이썬 책")
      7 print("현재 장바구니:", cart)

Cell In[102], line 4, in add_item(item)
      3 def add_item(item):
----> 4     cart = cart + [item]

UnboundLocalError: cannot access local variable 'cart' where it is not associated with a value

발생하는 오류의 원인을 설명하고, 매개변수와 반환값을 활용하는 방식으로 코드를 정상 작동하도록 수정하라. 단, 새로운 함수의 시그니처는 다음과 같아야 한다.

def add_item(current_cart, item):

답:

함수 내부에서 할당 연산자(=)를 사용해 cart 변수에 값을 저장하려고 시도하는 순간, 파이썬은 cart를 그 함수의 지역 변수로 취급한다. 그러나 할당을 위한 계산식 cart + [item]에서 지역 변수 cart의 값이 아직 지정되지 않았기 때문에, 해당 변수를 확인할 수 없다는 오류(UnboundLocalError)가 발생한다.

전역 변수에 의존하기보다는, 기존 장바구니 상태와 새로운 항목을 인자로 받아 새로운 장바구니 리스트를 반환하는 형식으로 수정하는 것이 좋다.

cart = ["사과", "우유"]

def add_item(current_cart, item):
    new_cart = current_cart + [item]
    return new_cart

cart = add_item(cart, "파이썬 책")
print("현재 장바구니:", cart)
현재 장바구니: ['사과', '우유', '파이썬 책']

5.6input() 함수

지금까지는 변수에 담을 값이나 함수에 전달할 인자를 코드 안에 직접 지정했다. 하지만 프로그램이 실행되는 도중에 사용자로부터 원하는 값을 직접 입력받으려면 어떻게 해야 할까? 이제 앞서 배운 내용을 바탕으로 사용자와 상호작용하는 간단한 프로그램을 만들어 보자. 파이썬에서 키보드를 통해 데이터를 직접 입력받을 때는 내장 함수인 input()을 사용한다.

아래 코드를 실행한 다음에 키보드에서 숫자 13을 입력하고 Enter 키를 누르면 최종적으로 '맞았습니다!'가 출력된다.

print("숫자맞히기 게임에 환영합니다.")

secret = 13
guess_str = input("10부터 19 사이의 숫자 하나를 입력하세요: ") 
print("입력한 값:", guess_str)
guess = int(guess_str)

if secret == guess:
    print("맞았습니다!")
else:
    print("틀렸습니다!")
숫자맞히기 게임에 환영합니다.
입력한 값: 13
맞았습니다!

위 코드의 실행 내용은 다음과 같다.

  • 첫째줄의 print() 함수 호출이 실행되어 환영인사를 출력한다.

  • secret 변수에 정수 13을 할당한다.

  • input() 함수가 호출되면 사용자가 무언가를 입력할 때까지 기다리고, 1, 3과 엔터 기호를 입력하면 다음 명령문을 실행한다.

  • 사용자가 입력한 1과 3은 input() 함수에 의해 문자열 '13'으로 반환되어 변수 guess_str에 할당된다.

  • 할당된 값을 화면에 출력한다.

  • 문자열 '13'이 곧바로 guess 변수에 정수 13으로 변환되어 할당된다.

  • secret == guessTrue로 계산되어 "맞았습니다!"가 화면에 출력된다.

5.6.1input() 함수 사용법

일반적인 input()의 사용법은 다음과 같다.

input("입력값 안내 문구")

input() 함수 인자

input() 함수와 함께 지정되는 문자열 인자는 사용자가 입력해야 하는 값에 대한 정보를 알려주는 문구이며, 사용자의 입력값과는 무관하다.

입력값 안내 문자열을 지정하지 않으면 prompt='' 인자가 적용되어 아무런 정보가 보여지지 않은 상태에서 사용자가 Enter 키를 입력할 때까지 코드의 실행이 정지된다. 예를 들어 아래 코드를 실행한 후에 파이썬을 입력한 후에 Enter 키를 입력하면 그제서야 입력된 문자열 '파이썬'이 그대로 반환값으로 지정되고 print() 함수가 그 반환값을 인자로 받아 화면에 출력한다.

print(input())
파이썬

반환값

input() 함수의 반환값은 사용자가 Enter 키를 치기 전까지 타이핑한 기호들로 구성된 문자열이다.

아래 코드를 실행할 때 사용자가 1과 3을 연속으로 입력한 후에 Enter 키를 치면 문자열 '13'이 반환된다.

input("10부터 19 사이의 숫자 하나를 입력하세요: ")
'13'

이런 이유로 guess_str 변수에 할당된 값은 정수 13이 아니라 문자열 '13'이다.

guess_str
'13'
type(guess_str)
str

5.6.2반환값 변환

문자열 '13'secret에 할당된 정수와 비교하면, 즉, secret == guess_str을 실행하면 무조건 "틀렸습니다!"가 출력된다. 이유는 정수와 문자열을 비교하면 무조건 False로 계산되기 때문이다.

13 == '13'
False

int() 함수 활용

따라서 int() 함수를 이용하여 input() 함수의 반환값을 정수형으로 변환한 다음에 secret 변수가 가리키는 값과 비교해야 한다.

print(guess == int(guess_str))
True

int(input()) 활용

앞서 입력된 값을 처리하기 위해 두 개의 변수를 사용하였다.

guess_str = input("10부터 19 사이의 숫자 하나를 입력하세요: ") 
guess = int(guess_str)

그런데 어차피 정수로 변환된 값을 사용할 것이기에 guess_str 변수를 사용하지 않으면서 직접 guess를 다음과 같이 정의해도 된다.

guess = int(input("10부터 19 사이의 숫자 하나를 입력하세요: "))

이제 secret 변수에 저장된 13을 맞히는 코드를 다음과 같이 작성할 수 있다.

print("숫자맞히기 게임에 환영합니다.")

secret = 13
guess = int(input("10부터 19 사이의 숫자 하나를 입력하세요: "))
print("입력한 값:", guess)

if secret == guess:
    print("맞았습니다!")
else:
    print("틀렸습니다!")
숫자맞히기 게임에 환영합니다.
입력한 값: 13
맞았습니다!

물론 13이 아닌 다른 숫자를 입력하면 '틀렸습니다’가 출력된다. 아래 코드는 17일 입력했을 때의 결과를 보여준다.

print("숫자맞히기 게임에 환영합니다.")

secret = 13
guess = int(input("10부터 19 사이의 숫자 하나를 입력하세요: "))
print("입력한 값:", guess)

if secret == guess:
    print("맞았습니다!")
else:
    print("틀렸습니다!")
숫자맞히기 게임에 환영합니다.
입력한 값: 17
틀렸습니다!

float() 함수 활용

만약에 입력값을 부동소수점으로 변환해야 한다면 float() 형변환 함수를 이용한다. 아래 코드를 실행할 때 7을 2로 나눈 값 3.5를 입력하면 '맞았습니다!'가 출력된다.

print("나눗셈 질문입니다.")

guess = float(input("7 나누기 2는 얼마인가요? "))
print("입력한 값:", guess)

if guess == 3.5:
    print("맞았습니다!")
else:
    print("틀렸습니다!")
나눗셈 질문입니다.
입력한 값: 3.5
맞았습니다!

만약 int()를 사용하면 부동소수점 형식의 문자열 '3.5'를 처리할 수 없기에 오류가 발생하기 때문에 입력값의 종류에 맞는 적절한 변환 함수를 사용해야 한다.

print("나눗셈 질문입니다.")

guess = int(input("7 나누기 2는 얼마인가요? "))
print("입력한 값:", guess)

if guess == 3.5:
    print("맞았습니다!")
else:
    print("틀렸습니다!")
나눗셈 질문입니다.
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[84], line 3
      1 print("나눗셈 질문입니다.")
----> 3 guess = int(input("7 나누기 2는 얼마인가요? "))
      4 print("입력한 값:", guess)
      6 if guess == 3.5:

ValueError: invalid literal for int() with base 10: '3.5'

이렇듯 input() 함수로 얻어낸 문자열 데이터를 프로그램의 목적에 맞게 int()float()으로 적절히 변환하는 일은 매우 중요하다.

5.6.3예제

(1) 사용자로부터 하나의 정수를 입력받은 후 입력값이 17보다 크면 입력값과의 편차의 제곱을, 아니면 입력된 정수를 그대로 출력하는 코드를 작성한다.

답:

두 정수의 크기비교를 위해 > 연산자를 이용한다. a가 표현하는 값이 b가 표현하는 값보다 클 때 표현식 a > bTrue로 계산된다는 사실을 if ... else ... 조건문에 적용한다.

  • 20을 입력할 때

user_input = int(input("정수 하나를 입력하세요: "))
print("입력한 값:", user_input)

if user_input > 17:
    print('17과의 편차의 제곱:', (user_input - 17)**2)
else:
    print("17보다 크지 않은 값:", user_input)
입력한 값: 20
17과의 편차의 제곱: 9
  • 15를 입력할 때

user_input = int(input("정수 하나를 입력하세요: "))
print("입력한 값:", user_input)

if user_input > 17:
    print('17과의 편차의 제곱:', (user_input - 17)**2)
else:
    print("17보다 크지 않은 값:", user_input)
입력한 값: 15
17보다 크지 않은 값: 15

(2) 아래 기능을 갖는 format_sum() 함수를 구현하라.

  • 매개변수는 사용하지 않는다.

  • 함수를 호출하면 두 개의 정수를 사용자로 입력받는다.

  • 입력된 두 개의 정수는 각각 ab로 두 개의 지역변수에 할당한다.

  • 반환값은 a + b = a와b의합 형식의 문자열이다.

예를 들어 함수를 호출해서 3과 2 두 개의 정수를 각각 입력하면 아래와 같은 문자열이 함수 반환값이 되어야 한다.

3 + 2 = 5

힌트: int()str() 두 함수 활용.

답:

format_sum() 함수가 매개변수를 사용하지 않기에 시그니처가 다음과 같다.

def format_sum():

format_sum() 형식으로 함수가 호출되면 먼저 정수 두 개를 각각 입력받을 수 있도록 input() 함수를 두 번 사용한다. 각 입력값을 ab에 차례대로 할당한다. 이때 입력된 값을 정수로 변환하지 않는다. 이유는 반환값이 a + b = ... 형식으로 지정되어야 하기 때문에 문자열 연산을 사용해야 하기 때문이다.

반환값을 작성에 필요한 입력값의 합은 int(a) + int(b)로 계산하고, 그 결과를 str() 함수로 다시 문자열로 변환한다. 즉, 아래 표현식을 활용한다.

str(int(a) + int(b))

지금까지의 설명을 정리하여 format_sum() 함수를 다음과 같이 정의한다.

def format_sum():
    a = input("첫째 정수를 입력하세요: ")
    b = input("둘째 정수를 입력하세요: ")

    return a + " + " + b + " = " + str(int(a) + int(b))

아래 코드를 실행한 다음에 2와 3을 각각 입력한 결과를 확인하라.

format_sum()
Loading...

아래 코드를 실행한 다음에 17과 42를 각각 입력한 결과를 확인하라.

format_sum()
Loading...

(3) 사용자로부터 0이 아닌 한자리 양의 정수 n 을 입력받아 n + nn + nnn 을 계산하는 코드를 작성한다. 단, nnnnn은 곱셈이 아니고 문자열 n을 각각 두 번과 세 번 반복한 결과로 생성된 정수를 가리킨다. 예를 들어 5가 입력되면 아래 값이 출력되어야 한다.

5 + 55 + 555 = 615

힌트: int(input()) 형식을 사용하지 않는다.

답:

가. 문자열 + 연산자 활용

5를 입력하면 input() 함수는 문자열 '5'를 입력값으로 지정한다.

n = input("한 자리 정수 하나를 입력하세요: ")

이제 문자열 + 연산자를 이용하여 변수 n에 할당된 값을 이어붙인다. 예를 들어 문자열 '5'를 이어붙이면 '55'가 된다. 여기에 int() 함수를 적용하면 정수 55가 계산된다. 유사한 방식으로 정수 555를 계산할 수 있다.

위 설명을 코드로 구현한 다음에 5를 입력하면 언급된 결과가 화면에 출력된다.

n = input("한 자리 정수 하나를 입력하세요: ")
print("입력한 값:", n)

nn = n + n
nnn = nn + n

print(n+' + '+nn+' + '+nnn+' =', int(n) + int(nn) + int(nnn))
입력한 값: 5
5 + 55 + 555 = 615

나. 문자열 * 연산자 활용

두 변수 nnn* 연산자를 이용하여 정의한다. 아래 코드를 실행할 때 7을 입력하면 7 + 77 + 777 = 861을 화면에 출력한다.

n = input("한 자리 정수 하나를 입력하세요: ")
print("입력한 값:", n)

nn = n * 2
nnn = n * 3

print(n+' + '+nn+' + '+nnn+' =', int(n) + int(nn) + int(nnn))
입력한 값: 7
7 + 77 + 777 = 861

5.6.4연습문제

(1) 사용자로부터 입력받은 정수에 대한 3의 배수 여부를 판정하는 코드를 작성하라. 단, 3의 배수인 경우와 아닌 경우 각각 다르게 아래 형식으로 출력되도록 한다.

24 은(는) 3의 배수다.

또는

25 은(는) 3으로 나누면 나머지가 1 이다.

답:

3의 배수 여부는 3으로 나눈 나머지에 의존한다. 나머지가 0일 때만 3의 배수며, 나머지 연산자를 이용하여 확인한다.

아래 코드는 24를 입력할 때의 결과를 보여준다.

user_input = int(input("정수 하나를 입력하세요: "))
print("입력한 값:", user_input)

if user_input % 3 == 0:
    print(user_input, "은(는)", "3의 배수다.")
else:
    remainder = user_input % 3
    print(user_input, "은(는)", "3으로 나누면 나머지가", remainder, "이다.")
입력한 값: 24
24 은(는) 3의 배수다.

25를 입력하면 다르게 출력된다.

user_input = int(input("정수 하나를 입력하세요: "))
print("입력한 값:", user_input)

if user_input % 3 == 0:
    print(user_input, "은(는)", "3의 배수다.")
else:
    remainder = user_input % 3
    print(user_input, "은(는)", "3으로 나누면 나머지가", remainder, "이다.")
입력한 값: 25
25 은(는) 3으로 나누면 나머지가 1 이다.

(2) 입력받은 두 부동소수점의 나눗셈을 아래 지정된 형식으로 출력하는 코드를 작성하라. 입력된 값은 각각 변수 ab에 저장한다. 예를 들어 3.3과 5.8이 입력되면 아래처럼 화면에 출력한다. 또한 계산 결과는 소수점 이하 셋째 자리에서 반올림한다.

3.3 / 5.8 = 0.57

답:

부동소수점 두 개를 각각 입력받을 수 있도록 input() 함수를 float() 함수와 함께 사용한다. 아래 코드를 실행 한 후에 3.3과 5.8을 따로따로 입력하면 언급한 방식으로 화면에 결과가 출력된다.

a = float(input("첫째 정수를 입력하세요: "))
print("첫째 정수:", a)
b = float(input("둘째 정수를 입력하세요: "))
print("둘째 정수:", b)

print(a, "/", b, "=", round(a / b, 2))
첫째 정수: 3.3
둘째 정수: 5.8
3.3 / 5.8 = 0.57

(3) 아래 조건을 만족시키는 taking_seats() 함수를 정의하라.

  • 매개변수는 사용하지 않는다.

  • taking_seats() 형식으로 함수 호출이 일어나면 먼저 변수 N 지역변수에 할당할 양의 정수를 사용자로부터 입력받는다.

  • N 변수가 가리키는 정수는 대기실에 일렬로 일렬로 놓여 있는 의자의 개수라 가정한다.

  • 반환값은 없다. 대신 함수가 종료하기 전에 아래 조건을 만족하는 두 개의 문자열을 화면에 출력해야 한다.

    • 어느 두 사람도 이웃하여 앉을 수 없다는 조건하에, 앉을 수 있는 최대 인원이 앉는 방식과 최소 인원이 앉는 방식을 표현하는 문자열

    • 단, 자리를 채운 후에는 더 이상 어느 누구도 자리에 앉을 수 없는 방식이어야 함.

예를 들어 의자가 8개인 경우 아래 방식으로 앉는 것은 허용되지 않는다. 아래 표에서 O은 채워진 자리를, X는 빈자리를 가리킨다.

불가능 방식이유
XXOXOXOX왼쪽 끝에 한 명 더 앉을 수 있음
XOXXOXXX오른쪽 끝에 한 명 더 앉을 수 있음

의자가 8 개일 때 의자에 앉을 수 있는 최소 인원과 최대 인원은 다음과 같다.

인원앉는 방식
최소 3명XOXXOXXO
최대 4명OXOXOXOX

따라서 함수가 호출되고 8을 입력하면 화면에 아래와 같이 출력되어야 한다.

입력한 값: 8
최대 인원 4 명: OXOXOXOX
최소 인원 3 명: XOXXOXXO

답:

N개의 의자가 있을 때 예를 들어 맨 왼쪽 의자에 앉은 다음에 한 자리 건널 때마다 앉으면 최대 인원이 앉게 된다. 즉 두 의자당 하나에 앉으면 된다. 만약 N이 홀수이면 맨 오른쪽 자리에 한 명이 더 앉을 수 있다. 따라서 N이 2배수인지 여부가 중요하다.

  • N이 짝수일 때: 최대 인원은 N//2

  • N이 홀수일 때: 최대 인원은 N//2 + 1

반면에 최소 인원은 다음과 같이 계산할 수 있다.

먼저 의자가 1개 또는 2개이면 최대 1명 앉을 수 있다. 이제부터 의자가 3개 이상이라고 가정한다. 먼저 N = 3이라고 하자. 그러면 아래 방식으로 1명만 앉을 수 있다.

XOX

N > 3인 경우는 3배수 여부로 결정된다.

  • N이 3의 배수인 경우: XOX 방식을 N//3 번 반복한다. 예를 들어 N = 6이면 아래 방식으로 최소 인원 6//3 명이 앉는다.

      XOXXOX
  • N을 3으로 나눴을 때 나머지가 1인 경우: XOX 방식을 N//3 번 반복한 다음 나머지 한 자리에 앉는다. 예를 들어 N = 7이면 아래 방식으로 최소 인원 7//3 명이 앉는다.

      XOXXOXO
  • N을 3으로 나눴을 때 나머지가 2인 경우: XOX 방식을 N//3 번 반복한 다음 나머지 하나 또는 두 자리 중에 한 자리에 앉는다. 예를 들어 N = 8이면 아래 방식으로 최소 인원 8//3 + 1 명이 앉는다.

      XOXXOXXO
      XOXXOXOX

    반면에 아래 방식은 한 명이 더 앉을 수 있기에 허용되지 않는다.

      XOXXOXXX

위 설명을 정리하여 코드로 작성하면 다음과 같다.

def taking_seats():
    N = int(input("정수를 입력하세요: "))
    print("입력한 값:", N)

    # 최대 인원
    if (N%2 == 0) :
        max_num = N//2
        print("최대 인원", max_num, "명:", "OX" * max_num)
    else :
        max_num = (N//2) + 1
        print("최대 인원", max_num, "명:", "OX" * (max_num-1)+"O")

    # 최소 인원
    if (N%3 == 0) :
        min_num = N//3
        print("최소 인원", min_num, "명:", "XOX" * (min_num))
    else:
        min_num = (N//3) + 1
        if (N%3 == 1):
            print("최소 인원", min_num, "명:", "XOX" * (min_num-1)+ "O")
        else:
            print("최소 인원", min_num, "명:", "XOX" * (min_num-1) + "XO")
  • 의자 8개

taking_seats()
입력한 값: 8
최대 인원 4 명: OXOXOXOX
최소 인원 3 명: XOXXOXXO
  • 의자 10개

taking_seats()
입력한 값: 10
최대 인원 5 명: OXOXOXOXOX
최소 인원 4 명: XOXXOXXOXO
  • 의자 20개

taking_seats()
입력한 값: 20
최대 인원 10 명: OXOXOXOXOXOXOXOXOXOX
최소 인원 7 명: XOXXOXXOXXOXXOXXOXXO

5.7람다 함수

특별한 목적으로 한 번만 사용되는 함수가 필요할 때, 그리고 함수의 본문이 return 표현식으로만 구성될 정도로 매우 단순할 때, 함수의 이름을 지정하지 않으면서 아래 형식으로 함수를 정의할 수 있다.

lambda 매개변수1, ..., 매개변수n : 표현식

lambda(람다)는 그리스어 알파벳 λ를 가리키며, 람다를 이용한다는 이유로 람다 함수lambda function라 부른다.

예를 들어 두 인자의 곱을 반환하는 람다 함수는 아래와 같이 정의된다.

  • ab: 매개변수 2개

  • 콜론 기호 (:) 뒤의 표현식 a * b: 람다 함수의 반환값

lambda a, b : a * b
<function __main__.<lambda>(a, b)>

위 코드는 아래 lambda_func() 함수와 동일한 기능을 수행한다. 두 함수는 이름이 있느냐 없느냐의 차이만 갖는다.

def lambda_func(a, b):
    return a * b

람다 함수는 자신을 가리키는 이름이 없기에 람다 함수를 호출하려면 항상 람다 함수 정의 자체를 사용해야 해서 조금 불편하다. 아래 코드는 2와 5의 곱을 계산하는 람다 함수 호출식이다.

(lambda a, b : a * b)(2, 5)
10

값으로서의 함수

파이썬에서 모든 것이 값이다라고 몇 번 언급했다. 예를 들어, 1, 2, 3, 1.2, 3.141592, 'python', '좋아요', [1, 3, 5, 7] 등을 포함해서, int, float, str, list 등 심지어 자료형을 가리키는 것까지 모두 값이다. 이에 대해 여기서 배우는 함수 또한 하나의 값이며, 앞으로 다른 종류의 값들도 다룰 예정이다.

함수 또한 값이기에 변수 할당에 활용될 수 있다. 아래 코드는 앞서 정의한 람다 함수를 변수에 할당하여 람다 함수 정의대신 변수를 일반적인 함수 호출처럼 사용한다.

lambda_add = lambda a, b : a * b
print(lambda_add(2, 5))
10

인자로서의 함수

함수 또한 일종의 값이기 때문에 다른 함수의 인자로 사용될 수 있다. 예를 들어, 아래 apply_func() 함수는 첫째 매개변수는 함수를 인자로 받고, 둘째 매개변수는 첫째 인자로 지정된 함수가 다룰 수 있는 값을 인자로 받는다. 반환값은 첫째 인자로 들어온 함수를 둘째 인자로 지정된 값과 함께 함수 호출한 결과로 지정된다.

def apply_func(func, value):
    return func(value)

apply_func() 함수를 호출하는 방식은 다른 게 없다. 첫째 인자로 어떤 함수를, 둘째 인자로 첫째 인자로 지정된 함수의 인자로 사용될 수 있는 값을 지정하면 된다.

예를 들어 아래 코드에서 double 변수는 지정된 인자를 두 배한 값을 반환하는 람다 함수를 가리킨다. 따라서 apply_func(double, 3)의 반환값은 double(3), 즉 6을 반환한다.

double = lambda x: x * 2

result = apply_func(double, 3)
print("3을 2배한 값:", result)  # 3 -> 6
3을 2배한 값: 6

위 코드에서 double 변수가 람다 함수를 가리키기에 double 변수 대신 해당 람다 함수를 직접 apply_func() 함수의 인자로 지정해도 된다. 다만, 코드가 조금 복잡해질 뿐이다.

apply_func(lambda x: x * 2, 3)
6

apply_func() 함수 첫째 인자로 다른 함수를 지정하면 당연히 다른 결과가 나온다. 아래 코드에서 add_ten() 함수는 인자에 10을 더한 값을 반환한다.

def add_ten(x):
    return x + 10

result2 = apply_func(add_ten, 5)
print("5에 10을 더하기:", result2)  # 5 -> 15
5에 10을 더하기: 15

5.7.1예제

(1) 섭씨(celsius)를 화씨(fahrenheit)로 변환하는 수식은 다음과 같다.

섭씨 * 9 / 5 + 32

섭씨 26도를 화씨로 변환하기 위해 위 수식을 반환값으로 갖는 다음 람다 함수를 활용하라.

답:

섭씨를 화씨로 변환하는 람다 함수는 아래처럼 작성한다.

lambda c: c * 9 / 5 + 32
<function __main__.<lambda>(c)>

아래 코드는 위 람다 함수를 26을 인자로 해서 호출하면 화씨를 계산한다.

celsius = 26
fahrenheit = (lambda c: c * 9 / 5 + 32)(celsius)

print("섭씨:", celsius, '도')
print("화씨:", fahrenheit, '도')
섭씨: 26 도
화씨: 78.8 도

(2) 화씨를 섭씨로 변환하는 람다 함수를 lambda_celsius 변수에 할당한 다음에 화씨 80도에 해당하는 섭씨 온도를 계산하라. 단, 계산 결과는 소수점 이하 셋째 자리에서 반올림한다.

답:

먼저 섭씨를 화씨로 변환하는 과정을 역으로 수행하는 수식은 다음과 같다.

(화씨 - 32) * 5 / 9

위 식을 반환값으로 갖는 람다 함수를 가리키는 변수 lambda_celsius에 다음과 같이 할당한다.

lambda_celsius = lambda f: (f - 32) * 5 / 9

정의된 변수를 활용하여 화씨 80도를 섭씨로 변환한 결과를 아래 코드처럼 확인할 수 있다. round() 함수를 이용하여 소수점 이하 둘째 자리까지만 화면에 출력한다.

fahrenheit = 80

celsius = lambda_celsius(fahrenheit)
print("화씨:", fahrenheit, '도')
print("섭씨:", round(celsius, 2), '도')
화씨: 80 도
섭씨: 26.67 도

(3) 아래 apply_twice() 함수는 함수를 인자로 받아서 그 함수를 두 번 연속으로 실행한다. 아래 코드의 실행 결과를 설명하라.

def apply_twice(func, value):
    return func(func(value))

double = lambda x: x * 2

result = apply_twice(double, 3)
print("3을 2배하기를 두 번 적용한 값:", result)
3을 2배하기를 두 번 적용한 값: 12

답:

코드가 실행되는 과정은 다음과 같다.

  • double 변수는 입력값을 2배로 만드는 함수를 가리킨다.

  • apply_twice(double, 3)double이 가리키는 함수를 값 3에 두 번 연속 적용한다.

    • 첫 번째 적용: double(3) = 6

    • 두 번째 적용: double(6) = 12

  • result에는 12가 할당되고, 출력 결과는 다음과 같다.

      3을 2배하기를 두 번 적용한 값: 12

5.7.2연습문제

(1) 평수를 제곱미터로 변환하는 코드를 람다 함수를 이용하여 작성하라. 단, 25평을 제곱미터로 변환해야 하며, 소수점 이하 둘째자리까지만 계산한다. 평을 제곱미터로 변환하는 공식은 다음과 같다.

평수 * 3.3058

답:

평수를 제곱미터로 변환하는 람다 함수는 다음과 정의할 수 있다.

lambda pyeong: pyeong * 3.3058
<function __main__.<lambda>(pyeong)>

아래 코드는 이 람다 함수를 이용하여 평을 제곱미터로 변환한 결과를 출력한다.

p = 25
square_meters = (lambda pyeong: pyeong * 3.3058)(p)

print("평수:", p, "평")
print("제곱미터:", round(square_meters, 2), "제곱미터")
평수: 25 평
제곱미터: 82.64 제곱미터

(2) 제곱미터를 평으로 변환하는 람다 함수를 lambda_pyeong 변수에 할당한 다음에 그 변수를 이용하여 90 제곱미터가 몇 평이 되는지 계산하는 코드를 작성하라. 단, 소수점 이하 둘째 자리까지만 계산한다.

답:

제곱미터를 평으로 변환하는 람다 함수는 다음과 같다.

lambda meters: meters / 3.3058
<function __main__.<lambda>(meters)>

아래 코드는 위 람다 함수를 lambda_pyeong 변수에 할당한 다음 80 제곱미터를 평으로 계산한 결과를 출력한다.

sq = 80
lambda_pyeong = lambda meters: meters / 3.3058
pyeong = lambda_pyeong(sq)

print("제곱미터:", sq, "제곱미터")
print("평수:", round(pyeong, 2), "평")
제곱미터: 80 제곱미터
평수: 24.2 평

(3) 함수를 인자로 사용하는 함수에 대한 문제다.

가. 아래 apply_chain() 함수의 시그니처와 기능을 설명하라.

def apply_chain(func1, func2, value):
    return func2(func1(value))

답:

함수 정의에 사용된 매개변수와 반환값은 다음과 같다.

  • func1: 하나의 인자를 받는 함수를 인자로 받음

  • func2: 하나의 인자를 받는 함수를 인자로 받음

  • value: func1의 인자로 지정된 함수의 인자로 사용될 수 있는 값을 인자로 받음

  • 반환값: valuefunc1을 먼저 적용한 뒤, 그 결과에 func2를 적용한 값

나. 아래 코드의 실행 결과를 설명하라.

print(apply_chain(lambda c: c * 9 / 5 + 32, lambda f: round(f, 1), 26.3))
79.3

답:

apply_chain() 함수의 세 인자는 다음과 같다. 참고로 func1는 섭씨를 화씨로 변환하는 함수를, func2는 소수점 둘째자리에서 반올림하는 함수를 가리킨다.

  • func1 = lambda c: c * 9 / 5 + 32

  • func2 = lambda f: round(f, 1)

  • value=26

함수 본문의 실행 순서는 다음과 같다.

  • func1(26) 호출: 26.3 * 9 / 5 + 32 = 79.34

  • func2(79.34) 호출: round(79.34, 1) = 79.3

  • 출력: 79.3