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

Python 개선 제안 한국어 번역

PEP 531 – 존재 검사 연산자

Author:
Alyssa Coghlan <ncoghlan at gmail.com>
Status:
Withdrawn
Type:
Standards Track
Created:
25-Oct-2016
Python-Version:
3.7
Post-History:
28-Oct-2016

Table of Contents

번역·라이선스 안내

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

초록

이 PEP는 PEP 505 및 관련 논의에서 영감을 받아 Python에 두 가지 새로운 제어 흐름 연산자를 추가할 것을 제안합니다:

  • 존재 검사 전제 조건(“exists-then”): expr1 ?then expr2
  • 존재 검사 대체(“exists-else”): expr1 ?else expr2

또한 흔히 쓰이는 존재 검사 표현식과 문에 대한 다음 축약형도 제안합니다:

  • 존재 검사 속성 접근: obj?.attr (obj ?then obj.attr의 축약형)
  • 존재 검사 서브스크립션: obj?[expr] (obj ?then obj[expr]의 축약형)
  • 존재 검사 대입: value ?= expr (value = value ?else expr의 축약형)

이 새로운 연산자 정의들에 공통으로 쓰인 ? 기호는 이 연산자들이 if 문, while 루프, 컴프리헨션, 제너레이터 표현식, 조건 표현식, 논리곱, 논리합에서 사용하는 기존의 진릿값 검사 프로토콜이 아니라 새로운 “존재 검사” 프로토콜을 사용한다는 것을 나타냅니다.

이 새로운 프로토콜은 operator.exists로 제공되며, 다음과 같은 특성을 가집니다:

  • 타입은 새로운 __exists__ 매직 메서드(Python) 또는 tp_exists 슬롯(C)을 정의하여 기본 동작을 재정의할 수 있습니다. 이 선택적 메서드는 __bool__과 동일한 시그니처와 가능한 반환값을 가집니다.
  • operator.exists(None)은 False를 반환합니다
  • operator.exists(NotImplemented)는 False를 반환합니다
  • operator.exists(Ellipsis)는 False를 반환합니다
  • float, complex, decimal.Decimal은 NaN 값이면 False를, 그 밖의 값(0 값 포함)이면 True를 반환하도록 존재 검사를 재정의합니다
  • 그 밖의 모든 타입에 대해서는 operator.exists(obj)가 기본적으로 True를 반환합니다. 가장 중요한 점은 진릿값 검사 문맥에서 False로 평가되는 값(0, 빈 컨테이너)도 존재 검사 문맥에서는 여전히 True로 평가된다는 것입니다

PEP 철회

이 PEP를 python-ideas [4]에 올려 논의를 요청하면서, 저는 검토자들에게 이 구문 제안의 구체적인 내용을 검토하기 전에 먼저 세 가지 고수준 설계 질문을 고려해 달라고 요청했습니다:

1. “존재 검사(existence checking)”가 소프트웨어 개발에 존재하는 유용하고 일반적인 개념이며 “참 검사(truth checking)”라는 개념과 구별된다는 데 우리 모두 동의합니까? 2. 우리 모두 언어 정의, 표준 라이브러리, 사용자 정의 코드에서 정의된 것들을 포함하여 서로 다른 “데이터 누락” 표시자 전반에 걸쳐 알고리즘(특히 단락 평가 알고리즘)을 일반화할 수 있게 해 주는 존재 검사 프로토콜이 Python 생태계에 도움이 될 것이라는 데 동의합니까? 3. 우리 모두 진릿값 검사 제어 흐름 연산자인 “and”와 “or”에 대응하는 존재 검사 연산자가 제공된다면 그러한 프로토콜을 효과적으로 사용하기가 더 쉬워질 것이라는 데 동의합니까?

첫 번째 질문에 대한 답변은 대체로 긍정적이었지만, 두 번째 질문에 대한 답이 “아니요”라는 것이 곧 분명해졌습니다.

Steven D’Aprano가 [5]에서 반대 인자를 잘 설명했지만, 일반적인 생각은 “누락된 데이터” 센티널을 검사할 때 아무 센티널 값이 아니라 거의 언제나 특정한 센티널 값을 찾는다는 것입니다.

예를 들어 NotImplemented가 존재하는 이유는 None이 오버로드된 산술 연산자의 정당한 결과일 수 있고, 예외 처리는 런타임 오버헤드가 너무 커서 피연산자 강제 변환에 쓰기에는 적합하지 않기 때문입니다.

마찬가지로 Ellipsis는 다차원 슬라이싱을 지원하기 위해 존재하는데, 슬라이싱 문맥에서 None은 이미 다른 의미(기본 시작 또는 끝 인덱스나 기본 스텝 크기를 사용한다는 의미)를 가지고 있기 때문입니다.

수학에서 NaN의 가치는 프로그래밍적으로는 해당 타입의 일반 값처럼 동작하면서(예: 일반적인 속성과 메서드를 모두 노출함) 산술적으로는 NaN 값을 처리하는 수학 규칙에 따라 동작한다는 데 있습니다.

이 핵심 설계 개념이 무효화되었으므로 제안 전체가 성립하지 않으며, 이에 따라 이 제안을 철회합니다.

하지만 이 제안에 대한 논의를 계기로, 새로운 문법을 도입하지 않고도 기존의 and, or, if-else 연산자를 더 유연하게 만드는 프로토콜 기반 접근 방식의 가능성을 검토하게 되었으므로 [6], 이를 PEP 505에 대한 또 다른 대안으로 작성할 예정입니다.

다른 PEP와의 관계

이 PEP는 Mark Haase가 PEP 505를 작성하며 이룬 훌륭한 작업에서 영감을 받았고 이를 토대로 삼고 있지만, 이 기능에 대해 제안된 문법과 의미의 세부 사항에 상당한 차이가 있으므로 결국 그 PEP와 경쟁하는 관계입니다.

또한 이 PEP는 애플리케이션 및 서비스 개발 활동에서 일반적으로 요구되는 사항이 실제로 변화하고 있다는 점에 주목하여, 기존 Python 사용자에게 돌아가는 이점에 초점을 맞춤으로써 이 변경의 근거에 대해 다른 관점을 제시합니다. 비슷한 기능이 현재 여러 프로그래밍 언어에 등장하고 있는 것은 우연이 아니며, 다른 언어 설계자들이 이 문제를 어떻게 다루고 있는지 배우는 것은 좋은 생각이지만, 다른 곳에서 세워지고 있는 선례는 애초에 이것이 우리가 해결해야 할 문제인지 여부보다는 이 문제를 어떻게 해결할 것인지와 더 관련이 있습니다.

근거

존재 검사 표현식

현대 소프트웨어 개발에서 점점 더 흔해지는 요구 사항은 “반구조화 데이터”를 다루어야 한다는 것입니다. 반구조화 데이터란 데이터의 구조는 미리 알려져 있지만 그 일부가 런타임에 누락될 수 있는 데이터를 말하며, 이러한 데이터를 다루는 소프트웨어는 완전히 실패하는 대신 (예를 들어 누락된 데이터에 의존하는 결과를 생략하는 방식으로) 우아하게 기능을 축소하여 동작할 것으로 기대됩니다.

이 문제가 발생하는 특히 흔한 경우는 다음과 같습니다:

  • 선택적인 애플리케이션 구성 설정과 함수 매개변수 처리
  • 분산 시스템에서 외부 서비스 장애 처리
  • 일부 불완전한 레코드를 포함하는 데이터 집합 처리

이 PEP의 주된 동기는 뒤의 두 경우입니다. 선택적인 구성 설정과 매개변수를 처리해야 하는 것은 적어도 Python 자체만큼 오래된 설계 요구 사항이지만, 퍼블릭 클라우드 인프라의 부상, 분산 서비스들의 협업 네트워크로서 소프트웨어 시스템을 개발하는 방식, 그리고 분석에 사용할 수 있는 대규모 공공 및 민간 데이터 집합의 등장으로 인해, 부분적인 서비스 장애나 부분적인 데이터 가용성 상황에서도 작업을 우아하게 축소하여 수행하는 능력이 현대 프로그래밍 환경의 필수 기능이 되어 가고 있습니다.

현재 Python으로 이러한 소프트웨어를 작성하는 일은 정말로 번거로울 수 있는데, 코드가 다음과 같은 표현식으로 뒤덮이게 되기 때문입니다:

  • value1 = expr1.field.of.interest if expr1 is not None else None
  • value2 = expr2["field"]["of"]["interest"] if expr2 is not None else None
  • value3 = expr3 if expr3 is not None else expr4 if expr4 is not None else expr5

이런 표현식이 가끔씩만 나온다면 완전한 문장 형태로 펼쳐 쓰는 것이 가독성 향상에 도움이 될 수 있지만, 이런 표현식이 4~5개 연달아 있다면(데이터 변환 파이프라인에서는 꽤 흔한 상황입니다) 이를 16~20줄의 조건 로직으로 대체하는 것은 사실상 전혀 도움이 되지 않습니다.

위의 세 예제를 그런 식으로 펼쳐 보면 다음을 잘 보여 줄 수 있을 것입니다:

if expr1 is not None:
    value1 = expr1.field.of.interest
else:
    value1 = None
if expr2 is not None:
    value2 = expr2["field"]["of"]["interest"]
else:
    value2 = None
if expr3 is not None:
    value3 = expr3
else:
    if expr4 is not None:
        value3 = expr4
    else:
        value3 = expr5

이 PEP의 제안들을 모두 합친 효과로, 위의 예제 표현식들을 대신 다음과 같이 작성할 수 있게 됩니다:

  • value1 = expr1?.field.of.interest
  • value2 = expr2?["field"]["of"]["interest"]
  • value3 = expr3 ?else expr4 ?else expr5

이러한 형태에서는 독자에게 제시되는 정보의 거의 전부가 “이 코드는 무엇을 하는가?”라는 질문과 곧바로 관련되며, 누락된 데이터를 출력으로 그대로 전달하거나 대체 입력으로 폴백하여 처리하는 보일러플레이트 코드는 ? 기호 두 번과 ?else 키워드 두 번으로 줄어들었습니다.

처음 두 예제에서는 31자짜리 보일러플레이트 절 if exprN is not None else None(한 글자 변수 이름의 경우 최소 27자)이 단일 ? 문자로 대체되어, 각 줄의 신호 대 패턴 잡음 비율이 크게 향상되었습니다(특히 이것이 표현식을 간결하게 하려는 목적만으로 변수와 필드 이름을 짧게 만드는 대신 더 의미 있는 이름을 사용하도록 장려한다면 더욱 그렇습니다).

마지막 예제에서는 21자짜리 보일러플레이트 if exprN is not None(최소 17자) 두 개가 각각 단일 문자로 대체되어, 역시 신호 대 패턴 잡음 비율이 크게 향상되었습니다.

게다가 5개의 “잠재적 관심 대상 하위 표현식” 각각이 정확히 한 번씩만 포함되므로, 그중 4개가 존재하는지 먼저 검사하기 위해 중복해서 쓰거나 이름 있는 변수로 뽑아낼 필요가 없습니다.

존재 검사 전제 조건 연산자는 주로 존재 검사 속성 접근 연산자와 첨자 연산자에 명확한 개념적 기반을 제공하기 위해 정의됩니다:

  • obj?.attr는 obj ?then obj.attr와 대략 동등합니다
  • obj?[expr]는 obj ?then obj[expr]와 대략 동등합니다

축약형과 그에 대응하는 확장형 사이의 주요한 의미 차이는, 축약형에서는 존재 검사 연산자 왼쪽에 있는 공통 하위 표현식이 단 한 번만 평가된다는 점입니다(복합 대입문이 제공하는 이점과 유사합니다).

존재 검사 대입

존재 검사 대입은 이 PEP의 개념을 비교적 간단하게 확장하여 흔히 쓰이는 구성 처리 관용구까지 다루도록 하기 위해 제안됩니다:

  • value = value if value is not None else expensive_default()

이를 다음과 같이 줄여 쓸 수 있도록 하는 것입니다:

  • value ?= expensive_default()

이는 주로 대상이 첨자 연산이나 하위 어트리뷰트일 때 유용합니다. 이 특정한 변경이 없더라도 이 PEP는 이 관용구를 다음과 같이 바꿔 쓸 수 있도록 허용하기 때문입니다:

  • value = value ?else expensive_default()

이 형식을 추가하는 데 반대하는 주된 인자는 이 형식이 모호하다고 볼 여지가 있으며 다음 중 어느 쪽으로도 해석될 수 있다는 점입니다:

  • value = value ?else expensive_default(); 또는
  • value = value ?then value.subfield.of.interest

두 번째 형식은 전혀 유용하지 않지만, 복합 대입 기능을 유지하면서도 이 우려를 해소할 만큼 중요하다고 판단된다면 문법에 전체 키워드를 포함할 수 있습니다:

  • value ?else= expensive_default()

또는 복합 대입을 현재 제안에서 완전히 제외하고, 추후에 다시 검토할 수도 있습니다.

존재 검사 프로토콜

존재 검사 프로토콜을 이 제안에 포함한 주된 목적은 프록시 객체(예: 원격 자원의 로컬 표현)와 테스트에 쓰이는 모의 객체가, 프록시나 모의 객체 자체는 None이 아니더라도 대상 자원이 존재하지 않음을 올바르게 나타낼 수 있도록 하는 것입니다.

그런데 이 프로토콜을 정의하고 나면, 이를 확장하여 숫자 타입에서 NaN 값을 타입에 독립적인 방식으로 검사할 수 있도록 하는 것이 자연스러워 보입니다 - 현재는 다루고 있는 정확한 데이터 타입(예: 내장 float, 내장 복소수, decimal 모듈)을 알고 있어야 하며, 그에 맞는 연산(예: 각각 math.isnan, cmath.isnan, decimal.getcontext().is_nan())을 사용해야 합니다

마찬가지로, 다른 자리 표시용 내장 싱글턴인 Ellipsis와 NotImplemented도 데이터를 나타낸다기보다는 데이터의 부재를 나타내는 객체에 해당한다고 선언하는 것이 합리적으로 보입니다.

제안하는 기호 표기법

Python에는 역사적으로 단 한 종류의 암묵적 불리언 컨텍스트, 즉 진릿값 검사만 있었으며, 이는 bool() 내장 함수를 통해 직접 호출할 수 있습니다. 이 PEP는 진릿값 검사가 아닌 존재 검사에 기반한 새로운 종류의 제어 흐름 연산을 제안하므로, 진릿값 검사가 아닌 존재 검사가 사용되고 있을 때 코드에서 이를 직접 상기시켜 주는 표시가 있는 것이 가치 있다고 봅니다.

존재 단언을 나타내는 수학 기호는 U+2203 ‘THERE EXISTS’입니다: ∃

따라서 이 PEP에서 제안하는 문법 추가에 대한 한 가지 가능한 접근 방식은 이미 정의된 이 수학 표기법을 사용하는 것입니다:

  • expr1 ∃then expr2
  • expr1 ∃else expr2
  • obj∃.attr
  • obj∃[expr]
  • target ∃= expr

하지만 이 접근 방식에는 두 가지 큰 문제가 있습니다. 하나는 실용적인 문제이고, 다른 하나는 교육적인 문제입니다.

실용적인 문제는 흔히 있는 것으로, 대부분의 키보드가 기본 산술에 쓰이는 기호 외의 수학 기호를 입력할 쉬운 방법을 제공하지 않는다는 것입니다(이 PEP에 나오는 기호들조차 결국 직접 입력하지 않고 [3]에서 복사해 붙여 넣었습니다).

교육적인 문제는 존재 단언 기호(∃)와 전칭 단언 기호(∀)가 기본 산술 연산자만큼 대부분의 사람에게 익숙하지 않을 것이므로, ∃를 채택한다고 해서 제안된 문법이 실제로 더 이해하기 쉬워지지는 않는다는 것입니다.

반면 ?는 Python 문법에서 아직 사용되지 않은 몇 안 되는 ASCII 구두점 문자 중 하나이므로, “이 제어 흐름 연산은 진릿값 검사가 아니라 존재 검사에 기반합니다”를 나타내는 문법적 표지의 후보로 사용할 수 있습니다.

이 방향을 택하면 유사한 기능을 제공하는 다른 언어의 대응 문법과 Python 문법을 일치시킬 수 있다는 이점도 있습니다.

PEP 505에 있는 기존 요약과 “안전 탐색 연산자 [1] 및 “널 병합 연산자” [2]에 관한 위키백과 문서를 참고하면 다음과 같습니다:

  • ?. 존재 검사 속성 접근 문법은 다음과 정확히 일치합니다:
    • C#의 “안전 탐색(safe navigation)” 속성 접근 연산자 (?.)
    • Swift의 “옵셔널 체이닝(optional chaining)” 연산자 (?.)
    • Groovy의 “안전 탐색(safe navigation)” 속성 접근 연산자 (?.)
    • Dart의 “조건부 멤버 접근(conditional member access)” 연산자 (?.)
  • ?[] 존재 검사 속성 접근 문법은 다음과 정확히 일치합니다:
    • C#의 “안전 탐색(safe navigation)” 첨자 연산자 (?[])
    • Swift의 “옵셔널 첨자(optional subscript)” 연산자 (?[].)
  • ?else 존재 검사 대체 문법은 의미상 다음과 일치합니다:
    • C#의 “널 병합(null-coalescing)” 연산자 (??)
    • PHP의 “널 병합(null-coalescing)” 연산자 (??)
    • Swift의 “nil 병합(nil-coalescing)” 연산자 (??)

분명히 해 두자면, 다른 언어에서 이 연산자들을 이렇게만 표기하는 것은 아니지만 이것이 가장 흔한 표기이며, ? 기호는 단연 가장 흔한 구문 표지입니다(아마도 C 스타일 조건 표현식에서 “then” 절을 시작할 때 ?를 쓰는 데서 비롯되었을 것이며, 이들 언어 중 상당수가 이 조건 표현식도 제공합니다).

제안하는 키워드

? 기호 표지가 있으므로, 존재 검사 전제 조건 연산과 대체 연산을 진릿값 검사에 대응하는 연산과 같은 키워드로 표기하더라도 구문상 모호하지 않을 것입니다:

  • expr1 ?and expr2 (expr1 ?then expr2 대신)
  • expr1 ?or expr2 (expr1 ?else expr2 대신)

그러나 이 방식은 작성된 코드에서 구문상 모호하지는 않지만, 코드를 발음하기가 매우 어렵고(“?”는 어떻게 발음합니까?) 설명하기도 어렵습니다(키워드를 재사용하므로 “존재 검사 전제 조건(?and)”과 “존재 검사 대체(?or)”를 “논리곱(and)” 및 “논리합(or)”과 구별해 줄 명확한 약칭이 없습니다).

사람들에게 ? 기호를 “exists”로 발음하도록 권장하여 약칭을 “exists-and 표현식”과 “exists-or 표현식”으로 삼을 수도 있겠지만, 코드에 쓰인 것만 보고서는 그 이름을 짐작할 방법이 없을 것입니다.

대신 이 PEP는 제안된 기호 문법을 활용하여 새로운 키워드(?then)를 도입하고 기존 키워드(?else)를 차용함으로써, 사람들이 “then 표현식”과 “else 표현식”을 모호함 없이 지칭할 수 있도록 합니다.

이 키워드들은 제안된 표현식과 의미상 동등한 조건 표현식과도 잘 들어맞습니다.

?else 표현식의 경우, expr1 ?else expr2는 다음과 동등합니다.:

_lhs_result = expr1
_lhs_result if operator.exists(_lhs_result) else expr2

축약형과 확장형 모두 끝에 else expr2가 나타나므로 여기서는 대응 관계가 분명합니다.

?then 표현식의 경우, expr1 ?then expr2는 다음과 동등합니다.:

_lhs_result = expr1
expr2 if operator.exists(_lhs_result) else _lhs_result

여기서는 Python의 “then” 절이 전통적으로 이름 없이 쓰여 왔기 때문에(if 문에서는 :로 시작되고, 조건 표현식에서는 뒤에 if가 붙습니다) 대응 관계가 곧바로 드러나지는 않지만, 조건부 제어 흐름에 대한 “if-then-else” 설명에 이미 익숙하다면 여전히 충분히 명확합니다.

위험과 우려 사항

가독성

새로운 문법을 효과적으로 읽고 쓰는 법을 익히려면 주로 두 가지 개념을 체득해야 합니다:

  • ?를 포함하는 표현식은 존재 검사를 포함하며 단락(short circuit)될 수 있습니다
  • None 또는 다른 “존재하지 않는” 값이 예상되는 입력이고 이를 결과로 전파하는 것이 올바른 처리라면, 아마도 존재 검사 연산자가 바로 여러분이 원하는 것입니다

현재 이러한 개념은 언어 수준에서 명시적으로 표현되지 않으므로, 조건 표현식과 조건문에 기반한 다양한 관용적 패턴을 알아보고 사용하는 법을 익혀야 합니다.

마법 같은 문법

구문 요소로서의 ?에는 본질적으로 is not None이나 operator.exists를 연상시키는 점이 전혀 없습니다. 현재 Python 코드에서 ? 기호는 주로 IPython 환경에서 후행 접미사로 쓰이며, 직전 표현식의 결과에 대한 도움말 정보를 요청합니다.

그러나 존재 검사라는 개념은 이를 진릿값 검사와 구별해 주는 일관된 시각적 표지가 있을 때 실제로 큰 이점을 얻으며, 이를 도입한다면 단일 문자로 된 기호 문법이 필요합니다.

개념적 복잡성

이 제안은 현재 임시방편적이고 비공식적인 “존재 검사” 개념을 명확하게 정의된 연산자 프로토콜을 갖춘 구문 수준의 언어 기능으로 격상합니다.

여러 면에서 이 제안은 실제로 언어의 전반적인 개념적 복잡성을 줄일 것입니다. bool(expr)을 사용하는 진릿값 검사와 operator.exists(expr)을 사용하는 존재 검사 사이에서는, 현재 진릿값 검사와 expr is not None을 사용하는 존재 검사(또는 피연산자 강제 변환 맥락에서의 expr is not NotImplemented, 혹은 수학 라이브러리의 다양한 NaN 검사 연산) 사이에서보다 훨씬 더 많은 기대가 올바르게 대응되기 때문입니다.

이 PEP가 도입하는 새로운 대응 관계의 간단한 예로서 다음을 비교해 보십시오:

all_are_true = all(map(bool, iterable))
at_least_one_is_true = any(map(bool, iterable))
all_exist = all(map(operator.exists, iterable))
at_least_one_exists = any(map(operator.exists, iterable))

설계 논의

존재 검사 표현식 연쇄의 미묘한 점

논리 연산자를 연쇄할 때 이미 있는 것과 비슷한 미묘한 점이 존재 검사 표현식을 연쇄할 때도 발생합니다. 연쇄에 포함된 표현식 중 하나의 우변 자체가 존재하지 않는 값을 반환하면 동작이 예상과 다를 수 있습니다.

따라서 value = arg1 ?then f(arg1) ?else default()는 value = cond and expr1 or expr2가 의심스러운 것과 본질적으로 같은 이유로 의심스럽습니다. 후자가 불리언 맥락에서 expr1이 False로 평가되면 expr2를 평가하는 것처럼, 전자는 f(arg1)이 None을 반환하면 default()를 평가합니다.

조건 표현식과의 모호한 상호 작용

현재 작성된 제안에서는 다음이 문법 오류입니다:

  • value = f(arg) if arg ?else default

반면 다음은 첫 번째 조건이 단순히 거짓인 경우가 아니라 존재하지 않는 경우에 두 번째 조건을 검사하는 유효한 연산입니다:

  • value = expr1 if cond1 ?else cond2 else expr2

위에서 설명한 표현식 연쇄 문제 때문에, 첫 번째 연산이 오히려 다음과 동등해야 한다는 인자를 제시할 수 있습니다:

  • value = f(arg) if operator.exists(arg) else default

이 경우 두 번째 연산은 더 명확하다고 볼 수 있는 다음 형태로 작성해야 합니다:

  • value = expr1 if (cond1 ?else cond2) else expr2

또는 첫 번째 형식을 문법 오류로 유지하고, 존재 검사 기호를 대신 if 키워드에 붙일 수도 있습니다:

  • value = expr1 if? cond else expr2

다른 진릿값 검사 맥락에서의 존재 검사

진릿값 검사 프로토콜은 현재 다음 구문 구조에서 사용됩니다:

  • 논리곱(and 표현식)
  • 논리합(or 표현식)
  • 조건 표현식(if-else 표현식)
  • if 문
  • while 루프
  • 컴프리헨션과 제너레이터 표현식의 필터 절

현재 PEP에서 and와 or를 사용하는 진릿값 검사에서 존재 검사로 전환하려면 적절한 위치에 새 키워드인 ?then과 ?else를 대입하기만 하면 됩니다.

그 밖의 진릿값 검사 맥락에 대해서는 operator.exists API를 임포트하여 사용하거나, expr is not None(또는 맥락에 맞는 동등한 표현)을 명시적으로 검사하는 현재의 관용구를 계속 사용할 것을 제안합니다.

이와 관련하여 가장 단순한 개선 방안은 제안된 exists() API를 operator(연산자) 모듈 함수에서 새로운 내장 함수로 격상하는 것입니다.

또는 ? 존재 검사 기호를 if와 while 키워드의 수식어로 지원하여, 참 검사가 아닌 존재 검사를 사용한다는 것을 나타내도록 할 수도 있습니다.

그러나 두 제안 중 어느 쪽이든 얻을 수 있는 일관성상의 잠재적 이점이 추가로 생기는 혼란을 정당화할 만큼인지는 전혀 분명하지 않으므로, 현재는 이 제안에서 제외했습니다.

__bool__과 __exists__ 사이에 기대되는 불변 조건 관계 정의

이 PEP는 현재 기존의 모든 타입에서 __bool__의 정의를 수정하지 않고 그대로 둡니다. 이 덕분에 제안 전체가 하위 호환성을 유지하지만, 그 결과 bool(obj)는 True를 반환하는데 제안된 operator.exists(obj)는 False를 반환하는 다음과 같은 경우가 생깁니다:

  • float, complex, decimal.Decimal의 NaN 값
  • Ellipsis
  • NotImplemented

이것들을 변경할 수도 있다는 주된 인자는, 존재 검사 맥락에서 자신이 존재하지 않는다고 나타내는 값은 진릿값 검사 맥락에서도 스스로를 False로 보고해야 한다는 권장 불변 조건이 마련되어 있으면 코드의 잠재적 동작을 추론하기가 더 쉬워진다는 점입니다.

이러한 불변 조건을 정의하지 않으면 float("NaN") ?else 0.0은 0.0을 반환하는 반면 float("NaN") or 0.0은 NaN을 반환하는 것처럼, 이상하다고 할 만한 결과가 생깁니다.

제약 사항

임의의 센티널 객체

이 제안은 “센티널 객체” 관용구에 대한 문법적 지원을 제공하려 하지 않습니다. 이 관용구에서는 None이 허용되는 명시적 값이므로, 누락된 값을 나타내기 위해 별도의 센티널 객체를 정의합니다:

_SENTINEL = object()
def f(obj=_SENTINEL):
    return obj if obj is not _SENTINEL else default_value()

이것은 존재 프로토콜의 정의를, 정의하는 쪽과 사용하는 쪽 모두에서 상당히 복잡하게 만드는 대가를 치르면 지원할 수도 있습니다:

  • Python 계층에서는 operator.exists와 __exists__ 구현이 존재하지 않음을 나타낼 때는 빈 튜플을 반환하고, 그렇지 않을 때는 존재 검사의 결과로 사용할 객체에 대한 참조를 담은 단일 요소 튜플을 반환합니다
  • C 계층에서는 tp_exists 구현이 존재하지 않음을 나타낼 때는 NULL을 반환하고, 그렇지 않을 때는 존재 검사의 결과로 PyObject * 포인터를 반환합니다

이렇게 변경하면 센티널 객체 관용구를 다음과 같이 다시 작성할 수 있습니다:

class Maybe:
  SENTINEL = object()
  def __init__(self, value):
      self._result = (value,) is value is not self.SENTINEL else ()
  def __exists__(self):
      return self._result

def f(obj=Maybe.SENTINEL):
    return Maybe(obj) ?else default_value()

그러나 제안된 3가지 표준 센티널 값(즉, None, Ellipsis, NotImplemented)을 사용할 수 없는 경우가, 추가되는 프로토콜 복잡성과 __bool__과 __exists__ 사이의 대칭성 상실을 감수할 가치가 있을 만큼 흔하리라고는 전혀 생각하지 않습니다.

명세

초록에서 이미 제안의 요지를 설명했고, 근거에서 몇 가지 구체적인 예를 들었습니다. 기본 아이디어에 충분한 관심이 있다면, 완전한 명세에서는 제안된 문법적 설탕과 그 기반이 되는 조건 표현식 사이의 정확한 대응 관계를 참조 구현을 만드는 데 지침이 될 만큼 충분히 제시해야 합니다.

…미정…

구현

PEP 505와 마찬가지로, 실제 구현은 이러한 연산자를 추가한다는 아이디어에 대한 원칙적인 관심이 확인될 때까지 보류되었습니다. 이 제안들에서 어려운 부분은 구현이 아니라, 새로운 Python 사용자와 기존 Python 사용자에게 돌아가는 장기적인 이점이 더 넓은 생태계(다른 구현의 개발자, 언어 교육과정 개발자, 기타 Python 관련 교육 자료의 저자 포함)가 이 변경에 적응하는 데 드는 단기적인 비용보다 큰 변경인지를 판단하는 것입니다.

…미정…

참고 문헌