Python의 조건식 평가에 대한 고찰

Python의 조건식 평가에 대한 고찰

2019, Nov 19    

0. Index

  1. 들어가며
  2. 조건식이란?
  3. Python의 독특한 조건식 평가방식
  4. 논리 이해하기
  5. 마치며
  6. 자료 출처


1. 들어가며


Python(이하 “파이썬”)도 여타 다른 언어처럼 boolean 자료형을 제공한다. 이들은 참, 거짓의 양값으로 나뉘어 논리를 실행하는지, 또는 안 하는지에 영향을 미치기에 거의 모든 프로그래밍 언어에서 필수적인 역할을 한다.

파이썬은 다른 언어 사용자들에게 익숙한 조건문 평가방식을 완벽히 제공하는 한편 자신만의, 다른 표현으로는 Pythonic한 평가방식을 제공하기도 하는데 오늘 포스트는 파이썬이 어떻게 조건식을 평가하는지에 대한 이야기를 하려고 한다. 파이썬은 과연 어떻게 조건식을 평가하는가? 다시 말해 내가 작성한 이 식이 조건문에서 어떻게 True 또는 False가 되는가?

먼저 조건식이 무엇인지에 대해 짧게 살핀다. 이 장은 인트로 정도의 느낌의 장으로 개념을 잡는다. 그리고 파이썬이 다른 언어와 다른 형태의 조건식을 사용할 수 있다는 것에 대해 살필 것이다. 이 내용들은 PEP 008에서도 표준적인 코딩 방식으로 공식적으로 권고되고 있는 내용이기에 파이썬을 공부하는 사람이라면 잘 알아야하는 내용이기도 하다. 다음으로는 이런 방식이 어떻게 작동하는지 살피는데 먼저 파이썬의 내부 문법적으로 살펴본다. 이후에는 파이썬만의 방식을 집합론을 사용해 개념적으로 이해해보려고 한다. 집합론으로 논리를 만들어나가는 것이 유용하다는 것을 알게 될 것이다.

이번 포스트는 굉장히 가벼운 주제가 될 것 같다. 시작해보자.



2. 조건식이란?


먼저 논의의 중심이 되는 조건식(conditional expression)이 무엇인지 알고 가자. 먼저 이란 영어로 ‘expression’으로 하나의 값으로 치환 또는 환원(reduce)될 수 있는 문장 단위를 말한다. 프로그래밍에서는 코드가 된다. 예를 들어 \(2 + 3\)은 \(5\)라는 값으로 치환될 수 있다. \((2 + 3) * 7\)도 마찬가지로 식이고 또 수식뿐 아니라 어떤 값을 반환하는 함수도 expression이 될 수 있다. 예를 들어 배열의 원소들의 합을 반환하는 sum 함수를 실행한 것도 하나의 숫자를 반환하기 때문에 곧 ‘expression이다’라고 해도 된다.

# sum(arr) 자체도 식이라고 할 수 있다.
# 실행결과를 15로 치환할 수 있다.
print(sum([1, 2, 3, 4, 5]))

15

여기서 조건식은 식 중에서 평가 또는 치환한 결과 참 또는 거짓의 결과로 귀결되는 식을 총칭한다. 앞서 참, 거짓을 결정하는 boolean 값은 프로그래밍에서 매우 중요하다고 이야기했다. 파이썬에서는 참, 거짓에 해당되는 값을 True, False라는 Capitalized된 표기로 지정하며 C, Java, Javascript 등에서는 true, false와 같이 소문자로 표기한다.

그럼 조건식의 대표적인 예가 뭐가 있을까? 바로 논리 연산자를 사용하는 식이다. 그렇다. 우리가 프로그래밍을 처음 배울 때 여러 종류의 연산자와 그들의 연산 순서 등을 공부하게 되는데 이중 논리 연산자를 사용한 식은 조건식이 된다.

# 다음은 모두 True 또는 False를 반환하기에 조건식이 된다.
print(1 == 2)
print(16 != sum([1, 2, 3, 4, 5]))
print(1 < 100)
print(not True)
print(1 > 2 and 3 < 4)
print(1 > 2 or 3 < 4)

False
False
True
False
False
True

각 식은 모두 논리 연산자를 사용한 조건식이다. 세 번째까지는 타 언어 사용자들에게도 익숙한 대소 비교, 동치를 확인하는데 밑에는 파이썬만의 연산자다. not 예약어는 True 또는 False를 상대값으로 반전시키는 ! 연산자의 파이썬 형태고, and&&, or||의 파이썬 논리 연산자다. 구체적인 표기는 달라도 하는 일은 똑같다. 그 표기는 달라도 의미는 같은 것이다.

이런 조건식은 if 조건문을 사용할 때 매우 중요하다.

age = int(input("당신의 나이는? : "))

if age >= 30:
    print("당신의 나이의 제곱의 최소값은 900")
else:
    print("당신의 나이의 10진수 표기의 앞 두 자리의 곱의 최대값은 18")


당신의 나이는? : 28
당신의 나이의 10진수 표기의   자리의 곱의 최대값은 18

이렇게 논리 연산자를 사용하는 조건문 사용 방식이 타 언어 사용자나 많은 파이썬 사용자들에도 익숙한 가장 일반적인, 또는 조건식을 만드는 유일한 방식이다.

하지만 파이썬은 다른 형식의 조건식을 지원하며 이는 파이썬이라는 프로그래밍 언어의 주요한 특징이 된다.



3. Python의 독특한 조건식 평가방식


파이썬은 논리 연산자를 사용하는 조건식말고도 다른 형태의 조건식도 제공한다. 그것은 바로 객체나 값 자체를 조건식으로 활용할 수 있다는 뜻이 된다. 다시 말하면 논리 연산자를 사용하지 않고, if문에 객체나 값 자신을 넣는 것이 가능하다. 이 말을 듣자마자 바로 반론을 제기하는 독자들이 있을 수 있다. ‘Java 등의 언어에서도 값 자체를 조건식으로 쓸 수 있는데?’ 맞다. 정확히는 매우 제한적으로 맞다. 예를 들면 C에는 조건문에 1이나 0을 넣어 true나 false로 평가 가능하다는 것을 우리는 안다. Javascript에서 확인해보자.

if(1) {
  console.log('yes boy');
}

yes boy

하지만 이외에 다른 값, 예를 들어 문자열이나 배열 같은 객체를 조건식에 그대로 넣는 것은 일반적이지 않거나 경고를 내거나 돌아가지 않는다. C 언어에서 조건식으로 배열 자체를 사용했을 때의 결과는 다음과 같다.

#include <stdio.h>

int main(void) {
  int data[100] = {1};
  
  if(data) {
      printf("Hello World!!!\n");
  }
  return 0;
}


Hello World!!!
main.c:5:6: warning: address of array 'data' will always evaluate to
      'true' [-Wpointer-bool-conversion]
  if(data) {

실행은 되는데 경고가 나오며 ‘data’라는 배열이 ‘true’로 평가될 것이라고 경고하고 있다. 이런 경고가 뜨는 이상 이런 조건식 활용이 C 언어의 일반 사용자가 동의하는 컨벤션은 아닐 것이라고 무난히 예상할 수 있다.


그런데 파이썬은 단순 정수뿐 아니라 문자열 등의 immutable한 값이나 list, set 등의 자료구조를 조건식에 쓰는 것이 가능하다.

some_array = [1, 2, 3, 4, 5]

if some_array:
    print("Array Hurrah your age~")


Array Hurrah your age~

그리고 이런 활용은 파이썬의 코딩 컨벤션으로 공식적으로 권장되고 있다. 파이썬 공식 홈페이지의 PEP(Python Enhancement Proposal) 0008은 모든 파이썬 유저들에게 권장되는 파이썬 컨벤션에 대해 길게 언급하고 있는데 그중 자료구조를 조건식에서 위에서처럼 그대로 사용하는 것에 관한 내용이 있다. 이때 빈 리스트, 빈 튜플 등 비어 있는 자료구조는 False를 반환하고, 조금이라도 값이 들어 있으면 True를 반환한다. 따라서 배열이 비어 있는지 확인할 때는 파이썬에서는 이렇게 쓰는 것이 바람직하다.

some_list = [1, 2, 3]

# 가능하지만 권장은 아님
if len(some_list) >= 1:
    print("I'm not empty")


# 바람직한 방법
if some_list:
    print("I'm not empty")


이렇게 값이나 자료구조 자체를 조건식으로 사용할 수 있고 장려하고 있다는 것이 파이썬의 흥미로운 점이다. 그 말은 ‘조건문에서는 자료구조가 True나 False의 bool 값으로 평가된다는 것’과 같은 말인데 다음 장에서는 ‘그러면 어떤 기준으로 True 또는 False을 판단하나‘에 대해 이야기할 것이다.



4. 논리 이해하기


앞서 확인했듯이 파이썬 사용자들에게는 값 자체를 조건식으로 사용하는 것이 매우 익숙하고 Pythonic한 방법이다. 모든 조건문에 입력된 조건식은 반드시 true 또는 false의 값으로 결정된다. ‘그 외’ 혹은 ‘둘 다’ 혹은 ‘무결정’은 없다. 로고의 네오가 파란색 알약과 빨간색 알약 중 하나를 반드시 선택해야 한 것과 같이… 네오라는 객체는 어떤 값으로 평가됐을까?

그러면 이번 장에서는 값 자체를 조건식으로 입력받았을 때 어떤 방법이나 기준으로 양가 사이의 값을 선택하는지 알아볼 것이다. 방법은 크게 두 가지로, 먼저 파이썬의 문법을 통해 이해한다. 거의 공식과 같아서 이 방법을 통하면 어떤 자료구조 또는 값이 조건식으로 입력되었을 때 True가 될지 False가 될지 바로 알 수 있다. 하지만 이해하지 않고 공식만 외우는 것은 정말 공허한 일이니, 그 다음에는 문법이 어떻게 동작하는 것인지 개념적으로 살펴보자. 집합론을 사용해 이해한다.


4.1. 문법적 이해

파이썬으로 알고리즘을 풀다보면 int라는 클래스의 생성자를 수없이 활용하게 된다. 가령 배열의 크기를 입력받았을 때 표기는 숫자지만 실제 값의 타입은 str 이기 때문에 int 생성자로 이를 정수로 변환해줘야 한다. 이를 유식한 말로는 Type casting 이라고 한다.

size = input("배열의 크기 입력: ")
print(type(size))

print("변환!!!!!!!!!")

size = int(size)
print(type(size))


배열의 크기 입력: 5
<class 'str'>
변환!!!!!!!!!
<class 'int'>

이렇게 값을 정수로 변환시켜줘야 반복문 등에서 활용할 수 있다. 이때 기억할 것은 int는 함수가 아니다. 내장 클래스로서 파이썬 내의 모든 정수값들은 이 클래스의 인스턴스가 된다. isinstance 함수를 활용해 확인해볼 수 있는데 이 함수는 첫 인자로 인스턴스, 두 번째 인자로 클래스를 받아 첫 인자가 두 번째 인자의 인스턴스가 맞는지 여부를 반환한다.

print(isinstance(3, int))
print(isinstance(3.0, int))

True
False

3은 정수지만 3.0은 실수 클래스(float)의 인스턴스기 때문에 False가 반환됐다.

int의 생성자 함수를 호출해 값을 입력하면 가능하다면 그 객체의 정수형 인스턴스를 반환할 수 있다. 이게 일반적인 활용이다. 이렇게 어떤 객체를 내장 클래스의 생성자 함수에 입력해서 해당 클래스의 인스턴스로 변환하는 것은 파이썬의 type casting의 주요한 방식이며, 또 자주 쓰이는 예로는 str(3) 같은 예가 있다.


하지만 사람들은 bool이라는 내장 클래스로 같은 일을 할 수 있다는 것은 잘 모른다. bool 클래스는 예상했다시피 True, False 인스턴스의 클래스로서, 이 둘은 bool 내장 클래스의 유이(唯二)한 인스턴스다.

print(isinstance(True, bool))
print(isinstance(False, bool))

True
True

이 bool 클래스도 값이나 객체를 입력받아 자신의 인스턴스로 반환한다.

print(bool(1))
print(bool(0))
print(bool([]))
print(bool('123'))
print(bool({'a': 1}))
print(bool(sum))


True
False
False
True
True
True

그리고 어떤 객체가 조건식으로 평가될 때 True 또는 False가 될지는 이 결과로 알 수 있다. 위의 결과에서 ‘0’과 ‘[]’ 입력만 False가 되었고 나머지는 True가 되었다. 그렇다면 이 값들이 if 문에서 조건식으로 쓰였을 때 그 블락이 실행될지 안 될지도 이를 따라가게 된다.

if 1: print("'1'은 True가 되나 봅니다.")
if 0: print("'0'은 True가 되나 봅니다.")
if []: print("'[]'는 True가 되나 봅니다.")
if '123': print("''123''은 True가 되나 봅니다.")
if sum: print("'sum' 내장 함수는 True가 되나 봅니다.")


'1' True가 되나 봅니다.
''123'' True가 되나 봅니다.
'sum' 내장 함수는 True가 되나 봅니다.

앞서 bool의 생성자 함수에 입력했을 때 True가 반환됐던 경우의 조건식이 사용된 if 문만 실행되었다.

이렇게 어떤 객체가 조건식으로 사용되었을 때 어떤 boolean 값을 반환할지 확인하려면 bool 내장 클래스의 생성자 함수에 객체를 입력하면 된다.


4.2. 개념적 이해

앞서 이해한 내용은 지극히 문법적인 내용으로 이렇게 끝내는 것은 이해 없이 수학 공식 달달 외우는 것처럼 무식한 일이다. 그리고 저런 방식은 현실 개발에서 큰 도움이 안 된다. 그렇기 때문에 대부분의 파이썬 유저들이 bool 이라는 내장 클래스를 써본 적이 없는 것이다.

이번에는 어떤 객체가 True가 되고 False가 되는지 개념적으로 이해해보자. 이걸 집합론으로 설명하려고 준비했는데 사실 이 포스트를 쓴 이유는 단순히 조건식을 논하고 싶었다기 보다는 내가 떠올린 이 방법을 글로 남기고 싶었던 것이 크다.


먼저 논의를 한 개 이상의 객체를 담을 수 있는 자료구조 객체에 한정해서 논의하자. list, set, tuple, dict 등이 있을 것이다.

우리가 고등학생 때 집합의 개념을 배웠다. 기억하는지? 그리고 아직도 기억나는 것이 어떤 집합을 주고 그 집합의 부분집합을 모두 구한다든가(보통 써야 해서 귀찮았다.), 개수를 구한다든가(이건 적지 않아서 좀 낫다.) 하는 작업을 많이 했다. 이때 처음에 이해가 잘 안 됐던 것이 공집합(empty set, 기호로는 \(\emptyset\))도 부분집합이 된다는 것이다. 공집합은 원소가 없는 집합으로 정의상 공집합은 모든 집합의 부분집합이 된다.

자, 이제 list, set, tuple, dict 등에서 아무거나 선택하자. 여기서는 list를 선택하겠다. 이때 만들 수 있는 모든 list들을 list 집합이라고 하자. [1, 2, 3] 도 list고 [[], []] 등도 list 집합이다. 이때 공집합이 아닌 모든 집합은 조건식에서 쓰이면 True로 평가되며, []처럼 원소가 없는 공집합은 무조건 False로 평가된다. 이게 파이썬에서의 객체 조건식 평가 기준이다. 이때 객체는 집합에 사영(射影)되며 모든 객체는 유일한 집합에 일대일 대응된다. 즉, 객체는 집합으로 변환할 수 있다는 뜻이 된다.

다시 말하지만 공집합은 모든 집합의 부분집합이다. tuple로 만든 집합의 공집합((,))도, dict로 만든 집합의 공집합({})도 조건식으로 사용되면 모두 False가 된다. 그외의 모든 집합은 True가 된다.

if []:
    print("Empty list")
if [[]]:
    print("List having one list")
if {}:
    print("empty dict")


List having one list


주목할만한 다른 예로는 문자열(str)이 있다. 문자열을 ‘문자들의 모음’이라고 하면 ‘abc’, ‘abcde’ 등도 문자 집합이다. 이때 원소, 다시 말해 문자가 하나도 없는 문자열 공집합 ''은 파이썬에서 조건식으로 쓰면 False, 그외 문자열은 True가 된다.

if '':
    print("Something is here?")


그리고 이번엔 데이터를 한 개 이상 포함하는 자료구조가 아니라 데이터 자체를 조건식으로 쓸 때를 보자. 대표적으로 정수, 실수 등이 있는데 여기에 공집합의 개념을 쓸 수는 없어보인다. 파이썬에서는 이런 숫자 상수(numerical literal)은 0이 아닌 경우 True, 0 이면 False가 된다. 이는 다른 여타 언어의 예와 같다.


정리하면 파이썬에서는 모든 객체 자신을 조건식처럼 평가할 수 있는데 자료구조의 경우는 공집합이 아닌 자료구조는 모두 True, 공집합, 다시 말해 원소가 전혀 없는 자료구조는 False로 평가된다. 그외 정수, 실수, 복소수 등에서 0은(0, 0.0, 0 + 0j) False, 그 외에는 True가 된다고 하겠다.



5. 마치며


분명 파이썬에서 모든 객체를 조건식으로 쓸 수 있는 방식은 흥미롭다. 나에게는 이런 철학적 질문을 남기기 때문이다. ‘무엇이 참이고 거짓인가?’, ‘파이썬은 객체의 실존 여부를 bool로 평가하는가?’, ‘실존이란 무엇인가? []은 실존하지 않는 값인가?’ 등등. 이런 이야기를 포스트에 더 담고 싶다면 내가 더 노력하는 수밖에 없다. 솔직히 아직 이런 개념까지 버무리지는 못하겠다. 다만 이 독특한 평가방식을 왠지 공집합 개념을 사용해 설명할 수 있겠다는 생각을 했을 뿐이다.

다음 포스트는 개인적으로 기대가 되는 포스트다. 내 일상의 경험을 녹여낸 에세이가 될텐데 알고리즘에 대해 이야기한다. 정말 오랜만에 일주일 두 포스트를 할까 하는데 성공했으면 좋겠다. 일단 오늘은 여기까지.

이상 포스트를 마칩니다.



6. 자료 출처