상위 목록: 하위 목록: 작성 날짜: 읽는 데 11 분 소요

기본 함수

제 4강에서는 연산자를 이용한 계산을 다루었습니다. 이번 강좌에서는 별도의 import 없이 사용할 수 있는 수치 계산용 내장 함수와, 두 값을 비교하여 참과 거짓을 판단하는 비교 연산, 여러 조건을 조합하는 논리 연산을 다룹니다. 비교 연산과 논리 연산은 이후 조건문과 반복문에서 프로그램의 흐름을 결정하는 데 사용되므로 동작 원리를 정확히 이해하는 것이 중요합니다.

함수 결과
int(x) int 형변환
float(x) float 형변환
pow(x, y) x의 y승
pow(x, y, z) x의 y승을 z로 나눈 나머지
divmod(x, y) (몫, 나머지)
abs(x) 절대값
max(x, y, z, …) 최대값
min(x, y, z, …) 최소값
round(x) 정수 반올림
round(x, n) 소수점 n번째 자리까지 반올림

pow(x, y)는 x**y와 동일합니다.

pow(x, y, z)는 (x**y) % z와 결과가 같지만, 큰 수를 직접 계산하지 않으므로 더 빠르게 계산합니다.

divmod(x, y)는 (x//y, x%y)와 동일합니다.

abs(x)는 x의 값이 복소수여도 절대값(크기)으로 반환합니다.


print(int(3.9), float(7))
print(pow(2, 10), 2 ** 10)
print(pow(2, 10, 1000))
print(divmod(17, 5))
print(divmod(-17, 5))
print(abs(-7), abs(3 + 4j))
print(max(3, 9, 4), min(3, 9, 4))
print(round(3.14159), round(3.14159, 2))
결과
3 7.0
1024 1024
24
(3, 2)
(-4, 3)
7 5.0
9 3
3 3.14

divmod(-17, 5)는 //, %와 같은 규칙으로 계산되므로 (-3, -2)가 아닌 (-4, 3)을 반환합니다.

결과를 한 줄씩 읽어 보면 다음과 같습니다.

  • int(3.9)는 소수점 이하를 버려 3, float(7)은 7.0입니다.
  • pow(2, 10)과 2 ** 10은 모두 1024입니다.
  • pow(2, 10, 1000)은 1024 % 1000인 24입니다.
  • divmod(17, 5)는 몫 3과 나머지 2를 튜플 하나로 반환합니다.
  • abs(-7)은 7, abs(3 + 4j)는 복소수의 크기 5.0입니다.
  • max(), min()은 전달한 값 중 가장 큰 값 9와 가장 작은 값 3을 반환합니다.
  • round(3.14159)는 정수 3, round(3.14159, 2)는 소수점 둘째 자리까지 남긴 3.14입니다.


pow()의 세 번째 인수

pow(x, y, z)가 빠른 이유는 나머지 연산의 성질 때문입니다. (a × b) % z는 ((a % z) × (b % z)) % z와 같으므로, 거듭제곱을 하는 도중에 계속 z로 나눈 나머지만 남겨도 최종 결과가 같습니다. 그래서 중간 값이 z보다 커지지 않아, x ** y처럼 수천 자리의 큰 수를 만든 뒤 나누는 방식보다 훨씬 빠르고 메모리도 적게 사용합니다. 암호 알고리즘처럼 매우 큰 수의 거듭제곱 나머지를 구할 때 사용합니다.

print(pow(3, -1, 7))
print(3 * 5 % 7)
print(type(pow(2, 10)), type(pow(2, -1)))
결과
5
1
<class ‘int’> <class ‘float’>

Python 3.8부터는 pow(x, -1, z)처럼 지수에 -1을 넣으면 z로 나눈 나머지 세계에서의 역수를 구합니다. 3 × 5 = 15를 7로 나눈 나머지가 1이므로, 7을 기준으로 3의 역수는 5입니다.

pow()의 반환 자료형은 **와 같은 규칙을 따릅니다. 정수의 양수 거듭제곱은 int, 음수 지수는 float을 반환합니다.


divmod() 활용

seconds = 3725
minutes, sec = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)
print(hours, minutes, sec)
결과
1 2 5

divmod()는 몫과 나머지를 한 번의 호출로 구하므로 단위 변환에 편리합니다. 3725초를 60으로 나누면 62분 5초이고, 62분을 다시 60으로 나누면 1시간 2분입니다. 반환값이 튜플이므로 제 4강의 언패킹으로 두 변수에 바로 나누어 저장할 수 있습니다.


max()와 min()

print(max([3, 9, 4]), min("python"))
print(max(["apple", "kiwi", "banana"], key=len))
print(max([], default=0))

try:
    max([])
except ValueError as err:
    print(err)

try:
    max(1, "2")
except TypeError as err:
    print(err)
결과
9 h
banana
0
max() iterable argument is empty
‘>’ not supported between instances of ‘str’ and ‘int’

max(), min()은 값을 여러 개 전달하는 대신 리스트나 문자열처럼 여러 값을 가진 객체 하나를 전달할 수도 있습니다. 문자열을 전달하면 글자 하나하나를 비교하므로 min("python")은 알파벳 순서상 가장 앞에 있는 h를 반환합니다.

key에 함수를 지정하면 원소를 그 함수의 결과로 비교합니다. key=len은 문자열의 길이로 비교하므로 가장 긴 banana가 반환됩니다. 반환되는 값은 길이 6이 아니라 원래 원소인 "banana"입니다.

빈 리스트에는 최대값이 없으므로 ValueError가 발생합니다. 빈 리스트가 전달될 수 있다면 default로 대신 반환할 값을 지정합니다. 또한 max()는 내부적으로 > 비교를 하므로, 숫자와 문자열처럼 비교할 수 없는 값을 섞으면 TypeError가 발생합니다.


반올림의 주의사항

print(round(0.5), round(1.5), round(2.5))
print(round(2.675, 2))
결과
0 2 2
2.67

round()는 정확히 중간값(.5)일 때 가장 가까운 짝수로 반올림합니다. 이를 은행가의 반올림(Banker's Rounding)이라 하며, 그래서 round(2.5)는 3이 아닌 2입니다.

round(2.675, 2)가 2.68이 아닌 2.67인 이유는 2.675가 2진수로 정확히 저장되지 않아 실제로는 2.67499999...에 가까운 값이기 때문입니다.

round(0.5)는 가까운 정수 0과 1 중 짝수인 0, round(1.5)는 1과 2 중 짝수인 2, round(2.5)는 2와 3 중 짝수인 2가 됩니다.


values = [0.5, 1.5, 2.5, 3.5]
print(sum(round(v) for v in values), sum(values))
결과
8 8.0

짝수로 반올림하는 이유는 반올림 오차가 한쪽으로 쌓이지 않도록 하기 위해서입니다. 위 예제의 원래 합은 8.0입니다. 항상 올리는 방식으로 반올림하면 1 + 2 + 3 + 4 = 10으로 원래 합보다 커지지만, 짝수로 반올림하면 0 + 2 + 2 + 4 = 8로 올림과 내림이 번갈아 일어나 원래 합과 같아집니다. 많은 값을 반올림한 뒤 더하는 통계, 회계 계산에서 이 방식이 유리합니다.

sum()은 목록의 합을 구하는 내장 함수이며, round(v) for v in values는 각 원소를 반올림한 값을 차례로 만들어 내는 표현으로 이후 강좌에서 다룹니다.


from decimal import Decimal, ROUND_HALF_UP

print(Decimal(2.675))
print(Decimal("2.675").quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))
print(Decimal("2.5").quantize(Decimal("1"), rounding=ROUND_HALF_UP))
결과
2.67499999999999982236431605997495353221893310546875
2.68
3

Decimal(2.675)로 실수 2.675가 실제로 저장한 값을 확인하면 2.674999...이므로, 반올림 규칙과 관계없이 2.67이 되는 것이 맞습니다.

학교에서 배운 사사오입 반올림이 필요하다면 Decimal을 문자열로 생성하여 오차를 없앤 뒤, quantize()로 남길 자릿수를 지정하고 rounding=ROUND_HALF_UP으로 반올림 방식을 지정합니다. Decimal("0.01")은 소수점 둘째 자리까지, Decimal("1")은 정수 자리까지 남긴다는 의미입니다.

  • Tip : 항상 올리는 사사오입 반올림이 필요하다면 decimal 모듈의 Decimal과 ROUND_HALF_UP을 사용합니다.


print(round(1234.567, -2), round(1234, -2))
print(type(round(3.7)), type(round(3.7, 0)), round(3.7, 0))
결과
1200.0 1200
<class ‘int’> <class ‘float’> 4.0

round()의 두 번째 인수에 음수를 넣으면 소수점 왼쪽 자리에서 반올림합니다. -2는 백의 자리까지 남긴다는 의미이므로 1234.567은 1200.0이 됩니다.

반환 자료형도 주의합니다. 두 번째 인수를 생략하면 정수(int)를 반환하지만, round(3.7, 0)처럼 자릿수를 지정하면 실수를 넣었을 때 실수(float)를 반환합니다. 결과를 리스트의 위치 번호처럼 정수가 필요한 곳에 사용할 때는 두 번째 인수를 생략해야 합니다.



비교 연산

비교 연산은 두 값의 크기나 같음을 판단하여 참(True) 또는 거짓(False)을 반환합니다. “점수가 60점 이상인가”, “입력이 비어 있는가”처럼 프로그램이 상황에 따라 다르게 동작하려면 먼저 조건이 참인지 거짓인지를 알아야 하므로, 비교 연산은 조건문의 기본 재료가 됩니다.

연산 의미
x > y 크다
x >= y 크거나 같다
x < y 작다
x <= y 작거나 같다
x == y 같다
x != y 같지 않다

비교 연산은 결과값이 True 또는 False로 반환됩니다.

연산이 참일 경우 True, 거짓일 경우 False로 반환합니다.

=는 대입, ==는 같은지 비교입니다. 조건을 작성할 때 == 대신 =를 쓰는 실수가 잦으므로 주의합니다.


x = 5

print(1 < x < 10)
print(1 < x and x < 10)
print(3 == 3.0)
결과
True
True
True

Python은 1 < x < 10처럼 비교 연산을 연결하여 사용할 수 있으며, 이는 1 < x and x < 10과 같은 의미입니다.

3 == 3.0처럼 정수와 실수는 값이 같으면 True를 반환합니다.

==는 자료형이 아니라 값을 비교합니다. 정수 3과 실수 3.0은 자료형은 다르지만 수학적으로 같은 값이므로 True입니다. 단, 제 4강에서 다룬 것처럼 계산을 거친 실수는 오차가 있을 수 있으므로 0.1 + 0.2 == 0.3처럼 실수끼리 ==로 비교하는 것은 피합니다.


연결 비교의 동작

def check(n):
    print("check", n)
    return n

print(check(1) < check(5) < check(3))
print(1 < 3 > 2)
결과
check 1
check 5
check 3
False
True

check()는 값을 출력한 뒤 그대로 반환하는 함수로, 어떤 값이 몇 번 계산되는지 확인하기 위해 사용합니다.

a < b < c는 a < b and b < c와 같지만, 가운데의 b는 한 번만 계산됩니다. 출력을 보면 check 5가 한 번만 출력됩니다. 1 < 5는 참이고 5 < 3은 거짓이므로 전체 결과는 False입니다.

연결 비교는 서로 다른 연산자도 이을 수 있습니다. 1 < 3 > 2는 1 < 3 and 3 > 2이므로 True입니다. 다만 이런 표현은 읽기 어려우므로, 수학의 범위 표현처럼 같은 방향으로 연결하는 것이 좋습니다.


다른 자료형의 비교

print("apple" < "banana", "Apple" < "apple", "10" < "9")
print(ord("A"), ord("a"))
print([1, 2, 3] < [1, 3])
print(1 == "1")

try:
    print(1 < "2")
except TypeError as err:
    print(err)
결과
True True True
65 97
True
False
‘<’ not supported between instances of ‘int’ and ‘str’

문자열은 사전 순서로 비교합니다. 첫 글자부터 차례로 비교하여 처음으로 다른 글자에서 크고 작음이 결정됩니다. 글자의 크기는 각 글자에 매겨진 유니코드 번호로 비교하며, ord()로 확인할 수 있습니다. 대문자 A(65)가 소문자 a(97)보다 번호가 작으므로 "Apple" < "apple"은 True입니다.

"10" < "9"가 True인 것은 숫자의 크기가 아니라 첫 글자 "1"과 "9"를 비교하기 때문입니다. 숫자로 비교하려면 int()로 변환한 뒤 비교해야 합니다.

리스트도 같은 방식으로 앞의 원소부터 비교합니다. [1, 2, 3]과 [1, 3]은 두 번째 원소에서 2 < 3이므로 True입니다.

정수와 문자열처럼 서로 다른 종류의 값은 ==로 비교하면 단순히 False이지만, < 같은 크기 비교는 TypeError가 발생합니다. 순서를 정할 기준이 없기 때문입니다.


a = [1, 2]
b = [1, 2]

print(a == b, a is b)

x = None
print(x is None)
결과
True False
True

==는 값이 같은지, is는 같은 객체인지를 비교합니다. a와 b는 내용이 같은 리스트이지만 따로 만든 서로 다른 객체이므로 a == b는 True, a is b는 False입니다.

None은 프로그램 전체에 하나만 존재하는 객체이므로, None인지 확인할 때는 x is None을 사용하는 것이 관례입니다. 숫자나 문자열을 비교할 때는 is가 아닌 ==를 사용합니다.


논리값과 숫자

a = int(False)
b = float(True)

print(a)
print(b)
print(True + True)
print(isinstance(True, int))
결과
0
1.0
2
True

True와 False의 상수 값은 각각 1과 0을 의미합니다.

bool은 int를 상속한 자료형이므로 True + True처럼 숫자 연산에 사용하면 1과 0으로 계산됩니다.

상속(Inheritance)은 기존 자료형의 성질을 물려받아 새 자료형을 만드는 것으로, bool은 값이 0과 1 두 개뿐인 특별한 정수라고 생각할 수 있습니다. isinstance(True, int)가 True인 것도 이 때문입니다.

이 성질을 이용하면 sum([True, False, True])처럼 조건을 만족하는 개수를 셀 수 있습니다. 비교 연산의 결과가 True이면 1, False이면 0으로 더해지기 때문입니다.



논리 연산

논리 연산은 여러 개의 조건을 조합하여 하나의 참과 거짓을 만듭니다. “나이가 20세 이상이고 회원인 경우”, “주말이거나 공휴일인 경우”처럼 조건이 둘 이상일 때 사용합니다.

연산 의미
x or y 논리합
x and y 논리곱
not x 부정

논리 연산은 True와 False를 조합하여 참과 거짓을 판단합니다.

비교 연산을 혼합하여 논리 연산의 사용이 가능합니다.


x = True
a = (0>7) or (6>9)
b = (2>3) and (1>0)
c = not (1>0)
d = (4>3) and x

print(a)
print(b)
print(c)
print(d)
결과
False
False
False
True

논리합의 경우 두 조건 중 하나만 참이라면 True를 반환합니다.

논리곱의 경우 두 조건 모두 참이여야 True를 반환합니다.

부정의 경우 결과값을 반전시킵니다. not은 항상 True 또는 False를 반환합니다.

논리 연산 자체에 True와 False를 직접 입력하여 사용 할 수 있습니다.

각 줄을 계산해 보면, a는 0>7과 6>9가 모두 거짓이므로 False, b는 2>3이 거짓이므로 False, c는 참인 1>0을 뒤집어 False, d는 4>3과 x가 모두 참이므로 True입니다.

x y x or y x and y not x
True True True True False
True False True False False
False True True False True
False False False False True


논리 연산자의 우선순위

print(True or False and False)
print((True or False) and False)
print(not 1 == 2)
결과
True
False
True

논리 연산자의 우선순위는 not → and → or 순서입니다. 수학에서 곱셈이 덧셈보다 먼저인 것처럼, and가 or보다 먼저 계산됩니다.

그래서 True or False and False는 True or (False and False) = True or False = True입니다. 괄호로 or를 먼저 계산하면 True and False = False가 되어 결과가 달라집니다.

비교 연산은 논리 연산보다 우선순위가 높으므로 not 1 == 2는 not (1 == 2) = not False = True입니다. and와 or가 섞인 조건은 의도를 분명히 하기 위해 괄호를 사용하는 것이 좋습니다.


논리 연산의 주의사항

a = (7>3) or 0
b = (7<3) or 1
c = (7>3) and 2
d = (7<3) and 3

print(a)
print(b)
print(c)
print(d)
결과
True
1
2
False

or와 and는 결과를 True, False로 바꾸어 반환하는 것이 아니라, 마지막으로 확인한 피연산자의 값을 그대로 반환합니다.

논리 연산의 경우 앞의 값을 먼저 확인한 후, 필요한 경우에만 뒤의 값을 확인합니다. 이를 단락 평가(Short-circuit Evaluation)라 합니다.

  • x or y : x가 참이면 y를 확인하지 않고 x를 반환하며, x가 거짓이면 y를 반환합니다.
  • x and y : x가 거짓이면 y를 확인하지 않고 x를 반환하며, x가 참이면 y를 반환합니다.

그래서 (7>3) or 0은 앞의 값 True를, (7<3) or 1은 뒤의 값 1을 반환합니다.

(7>3) and 2는 앞의 값이 참이므로 뒤의 값 2를, (7<3) and 3은 앞의 값이 거짓이므로 앞의 값 False를 반환합니다.

단락 평가가 가능한 이유는 앞의 값만으로 결과가 이미 정해지는 경우가 있기 때문입니다. or는 하나라도 참이면 참이므로 앞이 참이면 뒤를 볼 필요가 없고, and는 하나라도 거짓이면 거짓이므로 앞이 거짓이면 뒤를 볼 필요가 없습니다. 그리고 결과를 결정한 그 값을 그대로 돌려주면, 그 값의 참과 거짓이 곧 전체의 참과 거짓과 같으므로 조건문에서는 True, False를 반환한 것과 똑같이 동작합니다.


def check(name, value):
    print("check", name)
    return value

print(check("a", False) and check("b", True))
print(check("c", True) or check("d", True))
print(check("e", True) and check("f", False))
결과
check a
False
check c
True
check e
check f
False

check()는 자신의 이름을 출력한 뒤 값을 반환하는 함수로, 어떤 피연산자가 실제로 실행되는지 확인하기 위해 사용합니다.

첫 번째 줄은 앞의 a가 거짓이므로 and의 결과가 이미 거짓으로 정해져 b는 호출조차 되지 않습니다. 두 번째 줄도 앞의 c가 참이므로 d가 호출되지 않습니다. 세 번째 줄은 앞의 e가 참이라 결과가 정해지지 않았으므로 f까지 확인합니다.

이처럼 단락 평가에서는 뒤쪽 피연산자가 실행되지 않을 수도 있으므로, 반드시 실행되어야 하는 작업을 논리 연산의 뒤쪽에 두지 않도록 주의합니다.


print(0 or "default")
print("" or "empty")
print([1] and "x")
print(0 and 1/0)
print(bool(0), bool(0.0), bool(""), bool([]), bool(None), bool("0"))
결과
default
empty
x
0
False False False False False True

0, 0.0, 빈 문자열(""), 빈 목록([]), None 등은 거짓으로, 그 외의 값은 참으로 판단됩니다. 문자열 "0"은 비어 있지 않으므로 참입니다.

0 and 1/0은 앞의 값 0이 거짓이므로 뒤의 1/0을 계산하지 않아 오류가 발생하지 않습니다.

Python에서는 True, False뿐 아니라 모든 값을 참 또는 거짓으로 판단할 수 있습니다. 규칙은 간단하게 “0이거나 비어 있으면 거짓“입니다. bool()로 어떤 값이 참과 거짓 중 무엇으로 판단되는지 확인할 수 있습니다.


name = ""
print(name or "anonymous")

count = 0
print(count or 10)
print(count if count is not None else 10)
결과
anonymous
10
0

name or "anonymous"는 name이 빈 문자열이면 기본값 "anonymous"를 사용합니다. if문을 쓰지 않고 기본값을 정할 수 있어 자주 사용되는 표현입니다.

하지만 count or 10은 count가 올바른 값 0인데도 0을 거짓으로 판단하여 10을 반환합니다. 값이 없을 때(None)만 기본값을 사용하려면 count if count is not None else 10처럼 None인지 직접 확인합니다. 이 표현은 조건 표현식으로, 조건문 강좌에서 자세히 다룹니다.

  • Tip : 값 or 기본값 형태는 값이 비어 있을 때 기본값을 사용하는 용도로 자주 사용됩니다. 단, 0도 거짓으로 판단되므로 0이 올바른 값인 경우에는 주의합니다.



정리

이름 설명 반환값/특징
pow(x, y, z) 거듭제곱의 나머지 큰 수를 만들지 않아 빠름
divmod(x, y) 몫과 나머지 (x // y, x % y) 튜플
max(), min() 최대값, 최소값 key, default 지정 가능
round(x) 정수로 반올림 중간값은 짝수로, int 반환
round(x, n) n자리까지 반올림 실수를 넣으면 float 반환
==, != 값이 같은지 비교 자료형이 달라도 값이 같으면 True
<, >, <=, >= 크기 비교 연결 비교 가능, 다른 종류는 TypeError
is 같은 객체인지 비교 None 확인에 사용
and, or 논리곱, 논리합 단락 평가, 피연산자 값 그대로 반환
not 부정 항상 True 또는 False

비교 연산은 True, False를 만들고, 논리 연산은 그 결과를 조합합니다. and, or가 피연산자를 그대로 반환하고 필요한 만큼만 계산한다는 점과, 0이거나 비어 있으면 거짓이라는 규칙은 조건문에서 계속 사용됩니다.

댓글 남기기