C먼저 확인해보면
C언어에서는 bool 타입이 따로 존재하지 않는다. 0이면 false, 그렇지 않으면 true이다. 심지어는 float도 피연산자로 참여할 수 있다. 그래도 어쨌든 논리연산자의 결과값은 0 아니면 1이 되는 1byte 정수형을 리턴한다.
예를 들어
int a = 10;
float b = 3.14;
printf("%d, %d\n", a && b, sizeof(a && b));
위의 코드에서 출력은 1, 1 이다.
파이썬의 and, or
그런데 파이썬은 조금 더 변태같다.
a = 10
b = 20
print(a and b, a or b)
결과는 20, 10이다. 어째써? and와 or는 다음과 같이 구현되어 있다.
a or b => if a is true, return a, else return b
and의 경우 a가 참이면 b를 리턴한다. 결과적으로 a and b가 참인지 거짓인지는 b에 달린 상황이다. 만약 a가 거짓이면 그냥 a를 리턴한다. a가 거짓인 줄 알고 리턴하니까 a and b도 거짓이 된다. 이 때 b는 체크하지 않는다.
or의 경우 a 가 참이면 그냥 a를 리턴한다. a가 참이니까 a or b 도 참이다. 이 때 b는 체크하지 않는다. 만약 a가 거짓이면 b를 리턴한다. a or b가 참인지 거짓인지는 b에 달렸다.
한 번 더 정리하자면 a and b 에서 a 가 거짓이면 b를 체크하지 않고 a를 리턴한다. a or b에서 a 가 참이면 b를 체크하지 않고 a를 리턴한다. a and b에서 a가 참이면 b를 리턴하고, a or b에서는 a가 거짓이면 b를 리턴한다.
C언어의 논리연산자는 정수형 입력을 받아서 정수형 출력을 한다. 파이썬의 논리연산자는 입력 타입이 정해져 있지 않다. 그래서 입력 타입이 부울이 아닌 경우 엉뚱한 결과가 발생한다.
bool이 아닌 값들의 참, 거짓
뭐 당연하겠지만 또 웃기는 것은 True의 타입은 <class 'bool'>인데 산술연산에 참여할 때는 정수 1처럼 여겨진다는 것이다.
print(True + True)
결과는 2이다.
또 한 가지 팁이라면 파이썬에서는 None을 거짓으로 취급한다. 반대로 리스트나 기타 오브젝트는 참으로 취급한다. 아래 예제에서
a = [1, 2, 3]
print(a or None)
print(None and True)
결과는 [1, 2, 3], 그리고 None이다.
그런데 비어있는 리스트, 문자열, 딕셔너리, 셋, 튜플 따위는 거짓으로 취급한다.
print( [] or dict() or set() or tuple() or '' or 10 )
결과는 10이다. 10 앞에 나열한 모든 것들은 전부 거짓으로 취급된다.
bool로 변환해야 True가 된다
여기 또 다른 함정이 있다. 예를 들어 10은 bool로 형변환하면 True가 되긴 하지만 그렇다고 1이 True인 것은 아니다.
a = 10
print(a is True)
print(a == True)
결과는 둘 다 False이다. a는 정수 10일 뿐, bool도 아니고 True도 아니다.
if문을 쓰지 않고 a가 참인지 체크하려면 간단히 bool로 형변환해보면 된다.
print(bool(a))
결과는 당연히 True이다.
마지막으로 아래 식을 확인해보자.
print(type(a and True))
print(type(True and a))
결과는 <class 'bool'>, <class 'int'> 이다. and 연산의 리턴 타입을 보면서 파이썬의 함수는 리턴 타입이 정해져있지 않다는 사실이 새삼 현실로 다가온다.
and, or 응용하기
파이썬의 이러한 특성을 이용하면 변태같은 코딩이 가능하다.
if a < b : func() #이 문장은
a < b and func() #요렇게 바꿀 수 있다.
if not (a < b) : func()
a < b or func()
조건문 대신 and와 or를 쓸 수 있다. 영 and와 or가 어색하다면 and 대신 then, or 대신 otherwise로 읽어보면 조금 더 자연스럽다. 어셈블리어를 해봤으면 매우 자연스러울 수 있다, 점프처럼 생각하면 되니까.
여기에 보태서 None이 거짓으로 인식되는 점을 이용해 None 체크를 할 수 있다.
if a is not None: b = a
else: b = somethingDefault #이 두 문장은
b = a or somethingDefault #요 한 문장으로 바꿀 수 있다.
a를 b에 대입하는데 만약 None이면 미리 정해준 기본값을 대입하도록 한 것이다. 같은 원리로 비어있는 리스트를 체크할 수도 있다.
myList = []
x = min(myList or [-1])
x = min(myList) #에러 발생
만약 myList == [] 즉 비어있는 리스트라면 에러가 발생한다. 이걸 try 로 잡아서 어쩌구 해도 되지만 어차피 비어있을 경우 x에 기본값 -1을 넣을 작정이라면 위와 같이 or를 이용할 수 있다.
결론
당연히 논리연산자의 피연산자는 bool로 묵시적 형변환이 일어나는 줄 알았고, 당연히 출력은 무조건 bool인줄 알았는데 입력 타입을 그대로 살려서 출력한다니 좀 어이없긴 하다. 파이썬의 철학이 단 하나의 목적을 위한 단 하나의 아름다운 코드 아닌가? 이런 변태같은 부분은 파이썬의 기본 철학을 정면으로 부정하는 것이다. 통일성과 간편함을 동시에 추구하는 것이 그렇게 쉬운 일은 아닌 셈이다.
파이썬의 이런 점이 마음에 들지 않는다, 혹은 헷갈려서 공부 못하겠다 싶으면 정수나 기타 이상한 타입은 일절 논리 연산에 참여시키지 말아야 한다. 반드시 bool(a) 또는 a != 0 과 같은 코드를 써서 명시적으로 bool로 변환해주는 것이 좋다. C를 제외한 대부분의 언어, 그러니까 Java와 C#에서는 당연히 이렇게 한다. 논리연산에는 bool만 참여할 수 있기 때문에 반드시 명시적 형변환이 필요하다. 차라리 이게 나은 것 같은데.
0 comments:
댓글 쓰기