Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 285 – bool 타입 추가

Author:
Guido van Rossum <guido at python.org>
Status:
Final
Type:
Standards Track
Created:
08-Mar-2002
Python-Version:
2.3
Post-History:
08-Mar-2002, 30-Mar-2002, 03-Apr-2002

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

이 PEP는 False와 True라는 두 상수를 갖는 새로운 내장 타입 bool의 도입을 제안합니다. bool 타입은 (C 수준에서) int 타입의 단순한 서브타입이 되며, False와 True 값은 repr()과 str()을 제외하면 대부분의 측면에서 0과 1처럼 동작합니다(예를 들어 False==0과 True==1은 참이 됩니다). 개념적으로 불리언 결과를 반환하는 모든 내장 연산은 0이나 1 대신 False나 True를 반환하도록 변경됩니다. 비교, “not” 연산자, isinstance() 같은 술어가 그 예입니다.

검토

평생 쓰고도 남을 만큼 피드백을 모았으므로, 검토 기간이 공식적으로 끝났음을 선언합니다. 오늘 중국 음식을 먹었는데, 포춘 쿠키에 “강하고 신랄한 말은 명분이 약하다는 것을 나타낸다.”라고 쓰여 있었습니다. 이 PEP에 반대하는 몇몇 글이 떠올랐습니다… :-)

아무튼, 다음은 저의 BDFL 선언입니다. (요약: 아무것도 바꾸지 않습니다. 모든 변형안은 기각합니다.)

  1. 이 PEP를 수락해야 합니까?

    => 예.

    이 PEP에 반대하는 인자도 많이 제기되었습니다. 그중 상당수는 오해에 근거한 것이었습니다. 가장 흔한 오해 몇 가지는 아래 PEP 본문에서 해명하고자 했습니다. 제게 조금이라도 무게가 있는 유일한 문제는, 초보자들이 “if x”로 충분한 곳에 “if x == True”라고 쓰는 경향입니다. 이에 대해서도 아래에서 더 설명합니다. 저는 이것이 PEP를 기각할 충분한 이유가 되지 않는다고 생각합니다.

  2. str(True)는 “True”를 반환해야 합니까, 아니면 “1”을 반환해야 합니까? “1”은 하위 호환성 문제를 줄일 수도 있지만 이상해 보입니다. (repr(True)는 항상 “True”를 반환합니다.)

    => “True”.

    거의 모든 검토자가 이에 동의합니다.

  3. 상수의 이름은 (None과 비슷하게) ‘True’와 ‘False’여야 합니까, 아니면 (C++, Java, C99에서처럼) ‘true’와 ‘false’여야 합니까?

    => True와 False.

    대부분의 검토자는 다른 언어와의 일관성보다 Python 내부의 일관성이 더 중요하다는 데 동의합니다.

  4. 앞으로 적절한 경고를 통해 bool에 대한 불리언이 아닌 연산을 없애서, 예를 들어 True+1이 결국(Python 3000에서) 허용되지 않도록 노력해야 하겠습니까?

    => 아닙니다.

    산술 연산을 전혀 지원하지 않는 “교과서적인” bool을 원하는, 수는 적지만 목소리가 큰 소수가 있습니다. 그러나 대부분의 검토자는 bool이 항상 산술 연산을 허용해야 한다는 제 의견에 동의합니다.

  5. operator.truth(x)는 int를 반환해야 합니까, 아니면 bool을 반환해야 합니까?

    => bool.

    Tim Peters는 int를 반환해야 한다고 생각하지만, 그 밖의 거의 모든 검토자는 bool을 반환해야 한다는 데 동의합니다. 제 근거는 이렇습니다. operator.truth()는 인자에 불리언 컨텍스트를 강제하려고 존재합니다(이 함수는 C API PyObject_IsTrue())를 호출합니다. 결과를 int로 보고하든 bool로 보고하든 부차적인 문제입니다. bool이 있다면 그것을 쓰지 않을 이유가 없습니다. (이 PEP에 따라 operator.truth()는 이제 bool()의 별칭이 되는데, 그래도 괜찮습니다.)

  6. bool은 int를 상속해야 합니까?

    => 예.

    이상적인 세계라면 bool은 혼합 모드 산술을 수행할 줄 아는 별도의 정수 타입으로 구현하는 편이 더 나았을지도 모릅니다. 그러나 bool이 int를 상속하게 하면 구현이 엄청나게 쉬워집니다(PyInt_Check()를 호출하는 모든 C 코드가 계속 동작한다는 점도 그 이유 중 하나입니다 – 이 함수는 int의 서브클래스에 대해서도 참을 반환합니다). 또한 저는 이것이 대체 가능성 측면에서도 옳다고 생각합니다. int를 요구하는 코드에 bool을 넘겨도 0이나 1과 똑같이 동작합니다. bool을 요구하는 코드는 int가 주어지면 동작하지 않을 수 있습니다. 예를 들어 3 & 4는 0이지만, 3과 4는 진릿값으로 간주하면 둘 다 참입니다.

  7. ‘bool’이라는 이름을 바꿔야 합니까?

    => 아니요.

    일부 리뷰어는 bool 대신 boolean을 써야 한다고 주장했는데, 이렇게 하면 이해하기 더 쉽다는 이유(초보자는 불리언 대수를 들어 봤더라도 그것을 bool과 연결하지 못할 수 있음)나 축약어를 싫어한다는 이유였습니다. 제 생각은 이렇습니다: Python은 약어를 신중하게 사용하며(‘def’, ‘int’, ‘dict’처럼), 저는 이것들이 이해에 부담이 된다고 생각하지 않습니다. 초보자에게는 그것이 와플이라고 불리든 bool이라고 불리든 상관없습니다. 어차피 새로운 단어이고, 그 뜻은 금방 익히게 됩니다.

    한 검토자는 이름을 ‘truth’로 하자고 주장했습니다. 저는 이것이 매력적이지 않은 이름이라고 생각하며, 실제로는 이 용어를 (문서에서) Python에 이미 존재하는 진릿값이라는 더 추상적인 개념을 위해 남겨 두고 싶습니다. 예를 들면 다음과 같습니다. “컨테이너가 진릿값으로 해석될 때, 빈 컨테이너는 거짓으로 간주되고 비어 있지 않은 컨테이너는 참으로 간주됩니다.”

  8. 앞으로는 (“if”, “and”, “not” 같은) 불리언 연산이 인자로 bool을 요구하도록 하여, 예를 들어 “if []:”가 허용되지 않고 “if bool([]):”로 작성해야 하도록 만들기 위해 노력해야 합니까???

    => 아니요!!!

    일부 사람들은 교과서적인 불리언 타입을 갖춘 언어라면 이렇게 동작해야 한다고 믿습니다. 이 이야기가 나왔기 때문에, 다른 사람들은 제가 이 입장에 동의할지도 모른다고 걱정했습니다. 이에 대한 제 입장을 아주 분명히 밝히겠습니다. 이것은 이 PEP의 동기에 포함되지 않으며, 저는 이러한 변경을 할 생각이 없습니다. (아래의 “Clarification” 절도 참고하십시오.)

근거

대부분의 언어는 결국 불리언 타입을 갖추게 됩니다. 심지어 C99(새롭게 개선된 C 표준으로, 아직 널리 채택되지는 않았습니다)에도 불리언 타입이 있습니다.

많은 프로그래머가 불리언 타입의 필요성을 느끼는 것으로 보입니다. 대부분의 Python 문서에는 불리언 타입이 없는 것에 대한 약간의 해명이 담겨 있습니다. 저는 맨 위에 “False=0”과 “True=1”(또는 비슷한) 상수를 정의하고 그것을 사용하는 모듈을 많이 보았습니다. 문제는 사람마다 이를 다르게 한다는 점입니다. 예를 들어 “FALSE”, “false”, “False”, “F”, 심지어 “f” 중 무엇을 써야 합니까? 그리고 거짓은 값 0이어야 합니까, None이어야 합니까, 아니면 “true”나 “false”로 출력되는 다른 타입의 진릿값이어야 합니까? 언어에 표준 bool 타입을 추가하면 이러한 문제가 해결됩니다.

(데이터베이스나 RPC 패키지 같은) 일부 외부 라이브러리는 불리언 값과 정수 값을 구별할 수 있어야 하며, 보통은 해결책을 만들어 낼 수 있지만 언어가 표준 불리언 타입을 제공한다면 더 쉬울 것입니다. 이는 Jython에도 해당합니다. 일부 Java 클래스에는 int 인자와 불리언 인자에 대해 별도로 오버로드된 메서드나 생성자가 있습니다. bool 타입을 사용하면 불리언 변형을 선택할 수 있습니다. (일부 COM 인터페이스도 마찬가지인 것으로 보입니다.)

표준 bool 타입은 값을 강제로 불리언으로 해석하게 하는 수단으로도 쓰일 수 있으며, 이를 이용해 불리언 값을 정규화할 수 있습니다. 불리언 값을 두 값 중 하나로 정규화해야 할 때, bool(x)는 “not not x”보다 훨씬 명확하고 다음보다 훨씬 간결합니다.

if x:
    return 1
else:
    return 0

다음은 Python을 가르치면서 얻은 몇 가지 인자입니다. 대화형 셸에서 사람들에게 비교 연산자 등을 보여 줄 때, 다음은 다소 보기 좋지 않다고 생각합니다:

>>> a = 13
>>> b = 12
>>> a > b
1
>>>

만약 이것이 다음과 같다면:

>>> a > b
True
>>>

0이나 1이 출력될 때마다 생각하는 데 드는 시간이 1밀리초는 줄어들 것입니다.

또한 (한동안 이 언어를 떠나 있던 숙련된 파이썬 개발자조차 당황하는 것을 본 적이 있는) 문제도 있습니다. 다음을 보면:

>>> cmp(a, b)
1
>>> cmp(a, a)
0
>>>

cmp()도 진릿값을 반환한다고 믿고 싶어질 수 있지만, 실제로는 (-1, 0, 1)이라는 세 가지 다른 값을 반환할 수 있습니다. (일반적으로) 불리언 결과를 나타내는 데 정수를 사용하지 않는다면, 이것은 완전히 다른 것으로서 훨씬 더 뚜렷하게 눈에 띌 것입니다.

명세

다음 Python 코드는 새 타입의 속성 대부분을 명시합니다:

class bool(int):

    def __new__(cls, val=0):
        # This constructor always returns an existing instance
        if val:
            return True
        else:
            return False

    def __repr__(self):
        if self:
            return "True"
        else:
            return "False"

    __str__ = __repr__

    def __and__(self, other):
        if isinstance(other, bool):
            return bool(int(self) & int(other))
        else:
            return int.__and__(self, other)

    __rand__ = __and__

    def __or__(self, other):
        if isinstance(other, bool):
            return bool(int(self) | int(other))
        else:
            return int.__or__(self, other)

    __ror__ = __or__

    def __xor__(self, other):
        if isinstance(other, bool):
            return bool(int(self) ^ int(other))
        else:
            return int.__xor__(self, other)

    __rxor__ = __xor__

# Bootstrap truth values through sheer willpower
False = int.__new__(bool, 0)
True = int.__new__(bool, 1)

False와 True 값은 None과 마찬가지로 싱글턴이 됩니다. 이 타입은 값이 두 개이므로, 어쩌면 이를 “더블턴(doubleton)”이라고 불러야 하지 않을까요? 실제 구현에서는 bool의 다른 인스턴스를 생성할 수 없습니다.

True와 False는 피클링과 마셜링을 거쳐도 올바르게 원래 값으로 복원됩니다. 예를 들어 pickle.loads(pickle.dumps(True))는 True를 반환하며, marshal.loads(marshal.dumps(True))도 마찬가지입니다.

불리언 결과를 반환하도록 정의된 모든 내장 연산은 0이나 1 대신 False나 True를 반환하도록 변경됩니다. 특히 이 변경은 비교 연산(<, <=, ==, !=, >, >=, is, is not, in, not in), 단항 연산자 ‘not’, 내장 함수 callable(), hasattr(), isinstance()와 issubclass(), 딕셔너리 메서드 has_key(), 문자열 및 유니코드 메서드 endswith(), isalnum(), isalpha(), isdigit(), islower(), isspace(), istitle(), isupper(), startswith(), 유니코드 메서드 isdecimal()과 isnumeric(), 그리고 파일 객체의 ‘closed’ 속성에 영향을 줍니다. operator 연산자 모듈의 술어 함수도 operator.truth()를 포함하여 bool을 반환하도록 변경됩니다.

bool은 int를 상속하므로 True+1은 유효하며 2와 같습니다. 다른 경우도 마찬가지입니다. 이는 하위 호환성을 위해 중요합니다. 현재 비교 연산 등은 정수 값을 반환하므로, 기존 애플리케이션이 이 값들을 어떤 용도로 사용하는지 알 방법이 없기 때문입니다.

시간이 지나면서 표준 라이브러리는 적절한 곳에서 False와 True를 사용하도록 갱신될 것으로 예상됩니다(다만 이전에 int가 허용되던 곳에서 bool 인자 타입을 요구하도록 바뀌지는 않습니다). 이 변경은 추가적인 문제를 일으키지 않을 것이며, 이 PEP에서 자세히 명시하지 않습니다.

C API

헤더 파일 “boolobject.h”는 bool 타입의 C API를 정의합니다. 이 파일은 “Python.h”에 포함되어 있으므로 직접 포함할 필요가 없습니다.

기존 이름 Py_False와 Py_True는 유일한 bool 객체인 False와 True를 참조합니다(이전에는 값이 0과 1인 정적 int 객체를 참조했으며, 이 객체들은 int 값 중에서 유일하지 않았습니다).

새 API인 PyObject *PyBool_FromLong(long)은 C long int 인자를 받아서 Py_False (인자가 0일 때) 또는 Py_True (인자가 0이 아닐 때)에 대한 새 참조를 반환합니다.

객체가 bool인지 확인하려면 PyBool_Check() 매크로를 사용할 수 있습니다.

bool 인스턴스의 타입은 PyBoolObject *입니다.

bool 타입 객체는 PyBool_Type으로 사용할 수 있습니다.

명확화

이 PEP는 거의 모든 객체 타입을 진릿값으로 사용할 수 있다는 사실을 바꾸지 않습니다. 예를 들어 if 문에서 사용될 때 빈 리스트는 거짓이고 비어 있지 않은 리스트는 참입니다. 이는 바뀌지 않으며, 앞으로도 바꿀 계획이 전혀 없습니다.

바뀌는 것은 진릿값을 명시적으로 반환하거나 대입할 때 이를 나타내는 데 선호되는 값뿐입니다. 이전에는 이러한 선호되는 진릿값이 0과 1이었습니다. 이 PEP는 선호되는 값을 False와 True로 바꾸고, 내장 연산이 이 선호되는 값을 반환하도록 변경합니다.

호환성

하위 호환성 때문에 bool 타입에는 일부 사람들이 원하는 여러 속성이 빠져 있습니다. 예를 들어, bool 인자가 하나 또는 두 개인 산술 연산이 허용되며, 이때 False는 0으로, True는 1로 취급됩니다. 또한 bool은 시퀀스 인덱스로 사용할 수 있습니다.

저는 이것을 문제로 보지 않으며, 언어를 이 방향으로 발전시키고 싶지도 않습니다. “불리언성(Booleanness)”을 더 엄격하게 해석한다고 해서 언어가 조금이라도 더 명확해진다고 생각하지 않습니다.

호환성 요구 사항의 또 다른 결과로, 표현식 “True and 6”의 값은 6이고, 마찬가지로 표현식 “False or None”의 값은 None입니다. “and”와 “or” 연산자는 결과를 결정하는 첫 번째 인자를 반환하도록 유용하게 정의되어 있으며, 이는 바뀌지 않습니다. 특히 이 연산자들은 결과를 강제로 bool로 만들지 않습니다. 물론 두 인자가 모두 bool이면 결과는 항상 bool입니다. 또한 예를 들어 “bool(x and y)”처럼 작성하면 결과를 쉽게 bool로 강제 변환할 수 있습니다.

해결된 문제

(위의 검토 절도 참고하십시오.)

  • bool 값의 repr()이나 str()은 int 값의 것과 다르기 때문에, 일부 코드(예를 들어 doctest 기반 단위 테스트, 그리고 어쩌면 “%s” % truth 같은 것에 의존하는 데이터베이스 코드)가 실패할 수 있습니다. 이 문제는 (bool 타입을 명시적으로 참조하지 않고도) 쉽게 우회할 수 있으며, 쉽게 고칠 수 있는 아주 적은 양의 코드에만 영향을 줄 것으로 예상됩니다.
  • 다른 언어(C99, C++, Java)에서는 이 상수들의 이름을 모두 소문자로 “false”와 “true”라고 짓습니다. Python에서는 모두 CapitalizedWords 방식을 사용하는 기존 내장 상수인 None, Ellipsis, NotImplemented (그리고 모든 내장 예외)가 보여 준 선례를 따르는 편을 선호합니다. Python의 내장 네임스페이스에서는 함수와 타입에만 모두 소문자를 사용합니다.
  • 사용자의 기대를 충족시키기 위해, 불리언 문맥에서 참으로 간주되는 모든 x에 대해 표현식 x == True가 참이어야 하고, 마찬가지로 x가 거짓으로 간주되면 x == False가 참이어야 한다는 제안이 있었습니다. 특히 불리언 변수에 대해 막 배운 초보자들은 다음과 같이 작성하기 쉽습니다.
    if x == True: ...
    

    올바른 형태인 다음 코드 대신 말입니다.

    if x: ...
    

    많은 사람이 처음에는 후자의 형태를 불편하게 여기는 데에는 강한 심리적, 언어적 이유가 있는 것 같지만, 저는 해결책이 언어를 불구로 만드는 것이 아니라 교육에 있어야 한다고 생각합니다. 결국 ==는 일반적으로 추이적인 연산자로 여겨지며, 이는 a==b와 b==c로부터 a==c를 추론할 수 있다는 뜻입니다. 하지만 True와의 비교가 다른 피연산자가 어떤 타입이든 참인 값일 때 동등하다고 보고한다면, 6==True==7 같은 끔찍한 식이 참이 될 것이고, 여기서 6==7이라는 거짓을 추론할 수 있게 됩니다. 이는 받아들일 수 없습니다. (게다가 하위 호환성도 깨뜨릴 것입니다. 하지만 그렇지 않더라도, 앞서 말한 이유로 저는 여전히 이에 반대할 것입니다.)

    또한 초보자들에게 다음과 같이 작성할 이유는 전혀 없다는 점을 상기시켜야 합니다.

    if bool(x): ...
    

    “if”에 bool이 암시되어 있기 때문입니다. 여기서는 명시적인 것이 암시적인 것보다 낫지 않습니다. 덧붙인 말이 가독성을 해치고, 다른 해석의 여지도 없기 때문입니다. 하지만 때로는 다음과 같이 작성할 이유가 있습니다.

    b = bool(x)
    

    이는 임의의 객체 x에 대한 참조를 유지하는 것이 바람직하지 않거나, 다른 이유로 정규화가 필요한 경우에 유용합니다. 때로는 다음과 같이 작성하는 것이 적절한 경우도 있습니다

    i = int(bool(x))
    

    이는 bool을 값이 0 또는 1인 int로 변환합니다. 이는 이후로 그 값을 int로 사용하겠다는 의도를 전달합니다.

구현

C로 작성된 완전한 구현이 SourceForge 패치 관리자에 업로드되었습니다: https://bugs.python.org/issue528022

이는 곧 python 2.3a0을 위해 CVS에 체크인될 예정입니다.