Python은 다음과 같은 기본 비교 연산자를 지원해요:
| 연산자 | 연산 | 설명 |
|---|---|---|
> |
"크다" |
a가 b보다 값이 엄격하게 크면 a > b는 True예요 |
< |
"작다" |
a가 b보다 값이 엄격하게 작으면 a < b는 True예요 |
== |
"같다" |
a가 b와 값이 엄격하게 같으면 a == b는 True예요 |
>= |
"크거나 같다" |
a > b이거나 a == b이면 a >= b는 True예요 |
<= |
"작거나 같다" |
a < b이거나 a == b이면 a <= b는 True예요 |
!= |
"같지 않다" |
a == b가 False이면 a != b는 True예요 |
is |
"동일성" |
a와 b가 같은 _객체_일 때 오직 그럴 때만 a is b는 True예요 |
is not |
"부정된 동일성" |
a와 b가 같은 _객체_가 아닐 때 a is not b는 True예요 |
in |
"포함 검사" |
a가 b의 멤버, 부분집합, 또는 원소이면 a in b는 True예요 |
not in |
"부정된 포함 검사" |
a가 b의 멤버, 부분집합, 또는 원소가 아니면 a not in b는 True예요 |
이들은 모두 같은 우선순위를 가져요 (불리언 연산보다는 높지만, 산술 연산이나 비트 연산보다는 낮아요).
서로 다른 타입인 객체는 (숫자 타입을 제외하고는) 기본적으로 결코 같다고 비교되지 않아요.
class의 동일하지 않은 인스턴스 역시, class가 기본 object 비교 동작을 바꾸는 특별한 풍부한 비교 메서드를 정의하지 않는 한, 같다고 비교되지 않아요.
rich comparisons를 통한 커스터마이즈는 이어지는 연습 문제에서 다뤄요.
이 주제에 대한 (훨씬) 더 자세한 내용은 Python 문서의 값 비교를 참고해요.
숫자 타입은 (대부분) 이 타입 일치 규칙의 예외예요.
비교를 위해 타입이 암시적으로 변환될 수 있다면, integer는 float와 같다고 간주될 수 있어요 (또는 octal이 hexadecimal과 같을 수 있어요).
Python 표준 라이브러리의 다른 숫자 타입들(복소수, 실수, 분수)의 경우, 비교 연산자는 "말이 되는" 곳(암시적 변환이 결과를 바꾸지 않는 곳)에 정의되어 있지만, 기반 객체들이 비교를 위해 정확하게 변환될 수 없으면 TypeError를 발생시켜요.
python이 _숫자 변환_에 사용하는 규칙에 대한 더 자세한 내용은 Python 문서의 산술 변환을 참고해요.
>>> import fractions
# A string cannot be converted to an int.
>>> 17 == '17'
False
# An int can be converted to float for comparison.
>>> 17 == 17.0
True
# The fraction 6/3 can be converted to the int 2
# The int 2 can be converted to 0b10 in binary.
>>> 6/3 == 0b10
True
# An int can be converted to a complex number with a 0 imaginary part.
>>> 17 == complex(17)
True
# The fraction 2/5 can be converted to the float 0.4
>>> 0.4 == 2/5
True
>>> complex(2/5, 1/2) == complex(0.4, 0.5)
True
숫자를 NaN (not a number) 타입과 순서대로 비교하면 항상 False예요.
Python의 NaN 정의에서 헷갈리기 쉬운 부작용은 NaN이 NaN과 결코 같다고 비교되지 않는다는 점이에요.
>>> x = float('NaN')
>>> 3 < x
False
>>> x < 3
False
# NaN never compares equal to NaN
>>> x == x
False
숫자와 달리, 문자열(str)은 각각의 유니코드 코드 포인트를 사용해 사전순으로 비교해요 (str의 각 코드 포인트를 내장 함수 ord()에 넘긴 결과이며, 이 함수는 int를 반환해요).
두 문자열의 모든 코드 포인트가 일치하고 _같은 순서_라면, 두 문자열은 같다고 간주돼요.
이 비교는 '쌍별로' 이루어져요. 첫 번째끼리, 두 번째끼리, 이런 식으로요.
Python 3.x에서는 str과 bytes를 직접 강제 변환하거나 비교할 수 없어요.
>>> 'Python' > 'Rust'
False
>>> 'Python' > 'JavaScript'
True
# Examples with Mandarin.
# hello < goodbye
>>> '你好' < '再见'
True
# ord() of first characters
>>> ord('你'), ord('再')
(20320, 20877)
# ord() of second characters
>>> ord('好'), ord('见')
(22909, 35265)
# And with Korean words.
# Pretty < beautiful.
>>> '예쁜' < '아름다운'
False
>>> ord('예'), ord('아')
(50696, 50500)
비교 연산자는 임의로 연결할 수 있어요. 즉, 어떤 길이의 어떤 조합으로도 사용할 수 있어요.
표현식의 평가는 left에서 right 방향으로 일어나요.
예를 들어, x < y <= z는 x < y and y <= z와 동일해요. 단, y는 한 번만 평가돼요.
두 경우 모두, x < y가 False로 판명되면 z는 전혀 평가되지 않아요.
이를 흔히 short-circuit evaluation이라고 해요. 표현식의 진리값이 이미 결정되었다면 평가를 멈춰요.
Short circuiting은 다양한 불리언 연산자, 함수, 그리고 Python의 비교 연결에서도 지원돼요.
C, C++, C#, Java를 비롯한 많은 다른 프로그래밍 언어와 달리, Python에서 a < b < c 같은 연결된 표현식은 관례적인 수학적 해석과 우선순위를 가져요.
>>> x = 2
>>> y = 5
>>> z = 10
>>> x < y < z
True
>>> x < y > z
False
>>> x > y < z
False
연산자 is와 is not은 객체 _값_과는 대조적으로 객체 동일성을 검사해요.
객체의 동일성은 생성된 후 절대 변하지 않으며, id() 함수를 사용해 확인할 수 있어요.
<apple> is <orange>는 id(<apple>) == id(<orange>)일 때 오직 그럴 때만 True로 평가돼요.
<apple> is not <orange>는 그 반대를 나타내요.
싱글턴이라는 특성 때문에, None과 NotImplemented는 항상 is와 is not을 사용해 항목과 비교해야 해요.
이 관례에 대한 더 자세한 내용은 값 비교와 PEP8에 대한 Python 참조 문서를 참고해요.
>>> my_fav_numbers = [1, 2, 3]
>>> your_fav_numbers = my_fav_numbers
>>> my_fav_numbers is your_fav_numbers
True
# The returned id will differ by system and python version.
>>> id(my_fav_numbers)
4517478208
# your_fav_numbers is only an alias pointing to the original my_fav_numbers object.
# Assigning a new name does not create a new object.
>>> id(your_fav_numbers)
4517478208
>>> my_fav_numbers is not your_fav_numbers
False
>>> my_fav_numbers is not None
True
>>> my_fav_numbers is NotImplemented
False
연산자 in과 not in은 멤버십_을 검사해요.
<fish> in <soup>는 <fish>가 <soup>의 멤버일 때 (<fish>가 <soup>의 부분집합이거나 <soup> 안에 포함될 때_) True로 평가되고, 그렇지 않으면 False로 평가돼요.
<fish> not in <soup>는 <fish> in <soup>의 부정, 즉 _반대_를 반환해요.
문자열과 bytes 타입의 경우, <name> in <fullname>은 <name>이 <fullname>의 부분 문자열일 때 오직 그럴 때만 True예요.
# A set of lucky numbers.
>>> lucky_numbers = {11, 22, 33}
>>> 22 in lucky_numbers
True
>>> 44 in lucky_numbers
False
# A dictionary of employee information.
>>> employee = {'name': 'John Doe',
'id': 67826, 'age': 33,
'title': 'ceo'}
# Checking for the membership of certain keys.
>>> 'age' in employee
True
>>> 33 in employee
False
>>> 'lastname' not in employee
True
# Checking for substring membership
>>> name = 'Super Batman'
>>> 'Bat' in name
True
>>> 'Batwoman' in name
False
이번 연습 문제에서는 게임 진행 방식과 점수 계산 방식 등 블랙잭의 몇 가지 규칙을 구현해 봐요.
참고: 이 연습 문제에서 _A_는 에이스를, _J_는 잭을, _Q_는 퀸을, _K_는 킹을 의미해요.
조커는 제외해요.
표준 프렌치 수트 52장 카드 덱을 기준으로 하지만, 대부분의 게임에서는 여러 덱을 섞어서 사용해요.
블랙잭에서는 에이스가 1점인지 11점인지를 각 플레이어가 직접 정해요(이 부분은 나중에 더 자세히 다뤄요).
그림 카드(J, Q, K)는 10점으로 계산하고, 나머지 카드는 "핍"(숫자) 값만큼의 점수를 가져요.
매개변수 card를 가지는 value_of_card(<card>) 함수를 정의해요.
이 함수는 전달받은 카드 문자열의 _숫자 값_을 반환해야 해요.
에이스는 여러 값을 가질 수 있으므로(1 또는 11), 이 함수에서는 일단 에이스 카드의 값을 1로 고정해요.
나중에 기존 패가 주어졌을 때 에이스 카드의 값을 결정하는 함수를 구현하게 될 거예요.
>>> value_of_card('K')
10
>>> value_of_card('4')
4
>>> value_of_card('A')
1
매개변수 card_one과 card_two를 가지는 higher_card(<card_one>, <card_two>) 함수를 정의해요.
점수 계산에서 J, Q, K의 값은 10이에요.
이 함수는 점수 계산에서 어느 카드의 값이 더 큰지 반환해야 해요.
두 카드의 값이 같으면 둘 다 반환해요.
return문에서 쉼표를 사용하면 두 카드를 모두 반환할 수 있어요:
# Using a comma in a return creates a Tuple. Tuples will be covered in a later exercise.
>>> def returning_two_values(value_one, value_two):
return value_one, value_two
>>> returning_two_values('K', '3')
('K', '3')
에이스는 여러 값을 가질 수 있으므로, 이번 과제에서는 A 카드의 값을 1로 고정할게요.
>>> higher_card('K', '10')
('K', '10')
>>> higher_card('4', '6')
'6'
>>> higher_card('K', 'A')
'K'
앞에서 말했듯이, 에이스는 1점 또는 11점의 값을 가질 수 있어요. 플레이어는 21점을 넘지 않으면서 최대한 21점에 가까워지려고 해요(21점을 넘는 것을 "버스트"라고 해요).
매개변수 card_one과 card_two를 가지는 value_of_ace(<card_one>, <card_two>) 함수를 정의해요.
이 두 매개변수는 에이스 카드를 받기 전에 이미 패에 있는 두 장의 카드예요.
이 함수는 곧 받을 에이스가 1의 값을 가질지 11의 값을 가질지 결정하고, 그 값을 반환해야 해요.
기억해요: 에이스가 포함된 패의 값은 21을 넘지 않으면서 최대한 높아야 해요.
힌트: 이미 패에 에이스가 있다면, 곧 받을 에이스의 값은 1이 돼요.
>>> value_of_ace('6', 'K')
1
>>> value_of_ace('7', '3')
11
플레이어가 처음 두 장으로 에이스(A)와 10 카드(10, K, Q, J)를 받으면 21점이 돼요.
이것을 블랙잭 패라고 해요.
매개변수 card_one과 card_two를 가지는 is_blackjack(<card_one>, <card_two>) 함수를 정의해요. 이 두 매개변수는 한 쌍의 카드예요.
두 장으로 이루어진 패가 블랙잭인지 판별하고, 맞으면 불리언 True를, 아니면 False를 반환해요.
참고: 점수 _계산_은 여러 가지 방법으로 할 수 있어요. 하지만 가능하다면, 패의 값을 더하는 방식 대신 패 안에 에이스와 10 카드가 있는지(또는 특정 위치에 있는지) 확인해 봐요.
>>> is_blackjack('A', 'K')
True
>>> is_blackjack('10', '9')
False
패의 처음 두 카드가 같은 값이면(예를 들어 6 두 장이나 Q와 K), 플레이어는 이 둘을 각각 별개의 패로 나눠서 다룰 수 있어요.
이것을 "페어 스플릿"이라고 해요.
매개변수 card_one과 card_two를 가지는 can_split_pairs(<card_one>, <card_two>) 함수를 정의해요. 이 두 매개변수는 한 쌍의 카드예요.
이 두 장짜리 패를 두 쌍으로 나눌 수 있는지 판별해요.
패를 나눌 수 있으면 불리언 True를, 아니면 False를 반환해요
>>> can_split_pairs('Q', 'K')
True
>>> can_split_pairs('10', 'A')
False
처음 받은 두 카드의 합이 9, 10, 11점이면, 플레이어는 원래 베팅한 금액과 같은 금액을 추가로 베팅할 수 있어요. 이것을 "더블 다운"이라고 해요.
매개변수 card_one과 card_two를 가지는 can_double_down(<card_one>, <card_two>) 함수를 정의해요. 이 두 매개변수는 한 쌍의 카드예요.
두 장짜리 패에 "더블 다운"을 할 수 있는지 판별하고, 할 수 있으면 불리언 True를, 아니면 False를 반환해요.
>>> can_double_down('A', '9')
True
>>> can_double_down('10', '2')
False
Exercism에 가입하고 Python 트랙을 개념 17개연습 문제 146개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.