PEP 308 – 조건부 표현식
- Author:
- Guido van Rossum, Raymond Hettinger
- Status:
- Final
- Type:
- Standards Track
- Created:
- 07-Feb-2003
- Python-Version:
- 2.5
- Post-History:
- 07-Feb-2003, 11-Feb-2003
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
조건부 표현식 추가
2005년 9월 29일, Guido는 “X if C else Y” 형식의 조건 표현식을 추가하기로 결정했습니다. [1]
동기가 된 사용 사례는 같은 효과를 얻기 위해 “and”와 “or”를 사용하는 오류가 발생하기 쉬운 시도가 만연했다는 점이었습니다. [2]
조건부 표현식을 추가하려던 이전 커뮤니티의 노력은 최선의 구문에 대한 합의가 부족하여 좌절되었습니다. 이 문제는 단순히 BDFL의 최선의 판단에 맡김으로써 해결되었습니다.
이 결정은 표준 라이브러리 전반에 구문을 적용했을 때 어떻게 작동하는지 검토하여 검증되었습니다(이 검토는 다양한 배경을 가진 여러 프로그래머가 작성한 다양한 애플리케이션의 실제 사용 사례를 표본으로 추출한 것에 가깝습니다). [3]
다음과 같이 문법이 변경됩니다. (or_test 기호는 새롭고, 나머지는 수정됩니다.)
test: or_test ['if' or_test 'else' test] | lambdef
or_test: and_test ('or' and_test)*
...
testlist_safe: or_test [(',' or_test)+ [',']]
...
gen_for: 'for' exprlist 'in' or_test [gen_iter]
새로운 구문은 사소한 구문상의 하위 호환성 문제를 거의 일으킬 뻔했습니다. 이전 Python 버전에서는 다음이 유효합니다.:
[f for f in lambda x: x, lambda x: x**2 if f(1) == 1]
(즉, ‘in’ 다음의 시퀀스가 괄호로 묶이지 않은 람다들의 나열인 리스트 컴프리헨션을 말하며, 심지어 람다 하나만 있는 경우도 해당합니다.)
Python 3.0에서는 람다식들의 나열을 괄호로 묶어야 합니다. 예를 들면 다음과 같습니다.:
[f for f in (lambda x: x, lambda x: x**2) if f(1) == 1]
이는 lambda가 if-else 표현식보다 덜 긴밀하게 결합하기 때문이지만, 이 문맥에서는 lambda 뒤에 그보다도 더 느슨하게 결합하는 ‘if’ 키워드가 이미 올 수 있습니다(자세한 내용은 위에 제시된 문법 변경 사항을 참조하십시오).
그러나 Python 2.5에서는 약간 다른 문법을 사용하므로 하위 호환성이 더 높지만, 이 위치에서 사용되는 람다의 본문에 괄호로 묶이지 않은 조건 표현식이 포함되지 않도록 람다 문법을 제한합니다. 예:
[f for f in (1, lambda x: x if x >= 0 else -1)] # OK
[f for f in 1, (lambda x: x if x >= 0 else -1)] # OK
[f for f in 1, lambda x: (x if x >= 0 else -1)] # OK
[f for f in 1, lambda x: x if x >= 0 else -1] # INVALID
참고 자료
PEP 초기 초안 소개(역사적 목적을 위해 보존됨)
if-then-else(“삼항”) 표현식에 대한 요청은 comp.lang.python에서 계속해서 제기되고 있습니다. 이 PEP에는 상당히 Python다운 구문에 대한 구체적인 제안이 담겨 있습니다. 이것은 커뮤니티에 주어진 한 번의 기회입니다. 이 PEP가 명확한 과반수로 승인되면 Python 2.4에 구현됩니다. 그렇지 않다면 PEP에는 거부 사유 요약이 추가되며, 이 주제를 다시 꺼내서는 안 됩니다. BDFL이 이 PEP의 공동 저자이기는 하지만, 이 제안에 찬성하지도 반대하지도 않으며, 결정은 커뮤니티에 달려 있습니다. 커뮤니티가 결정하지 못하면 BDFL이 PEP를 거부합니다.
전례 없는 커뮤니티의 반응이 있은 후(찬성과 반대 양쪽 모두에서 매우 훌륭한 주장이 제시되었습니다), 이 PEP는 Raymond Hettinger의 도움으로 개정되었습니다. 전체 개정 이력을 살펴보지 않고 말하자면, 주요 변경 사항은 서로 다른 제안 구문, 제안된 대안의 개요, 현재 논의의 진행 상황, 그리고 단락 평가 동작에 대한 논의입니다.
논의에 이어 투표가 진행되었습니다. if-then-else 표현식에 어떤 형태로든 관심이 전반적으로 있었지만, 어느 형식도 과반수의 지지를 얻지 못했습니다. 따라서 변경에 대한 압도적인 다수의 지지가 부족하여 PEP가 거부되었습니다. 또한 어떤 방향을 택해야 할지 의문이 있을 때 현 상태를 선호하는 것이 Python 설계 원칙 중 하나였습니다.
제안
제안된 구문은 다음과 같습니다.:
(if <condition>: <expression1> else: <expression2>)
바깥쪽 괄호는 선택 사항이 아니라는 점에 유의하십시오.
결과 표현식은 다음과 같이 평가됩니다.
- 먼저 <condition>을 평가합니다.
- <condition>이 참이면 <expression1>을 평가하며, 이것이 전체 표현식의 결과가 됩니다.
- <condition>이 거짓이면 <expression2>를 평가하며, 이것이 전체 표현식의 결과가 됩니다.
이 구문의 자연스러운 확장은 하나 이상의 ‘elif’ 부분을 허용하는 것입니다.:
(if <cond1>: <expr1> elif <cond2>: <expr2> ... else: <exprN>)
제안이 수락되면 이를 구현합니다.
이 제안의 단점은 다음과 같습니다.
- 필수 괄호
- 문장 구문과 혼동될 가능성
- 콜론의 추가적인 의미 부담
<expression1>과 <expression2> 중 최대 하나만 평가된다는 점에 유의하십시오. 이를 “단락 평가 표현식”이라고 하며, 첫 번째 피연산자가 참 또는 거짓일 때만 ‘and’ / ‘or’의 두 번째 피연산자를 평가하는 방식과 유사합니다.
if-then-else 표현식을 모방하는 일반적인 방법은 다음과 같습니다.:
<condition> and <expression1> or <expression2>
그러나 이는 같은 방식으로 작동하지 않습니다. <expression1>이 거짓이면 <expression2>를 반환합니다! 작동하는 대안은 FAQ 4.16을 참조하십시오. 하지만 이러한 대안은 상당히 보기 흉하고 이해하는 데 훨씬 더 많은 노력이 필요합니다.
대안
Holger Krekel은 새롭고 최소한의 변경만 필요한 변형을 제안했습니다.:
<condition> and <expression1> else <expression2>
그 배경에 있는 개념은 and/or를 사용하면 거의 완성된 삼항 연산자가 이미 존재하며, 이 제안이 이를 완성하는 가장 최소한의 변경이라는 것입니다. 뉴스그룹의 많은 응답자는 이것이 가장 마음에 드는 대안이라고 생각했습니다. 그러나 몇몇 응답자는 정신적으로 구문 분석하기 어려운 예제를 게시할 수 있었습니다. 이후 이 구성은 “else”가 기존 “and”의 의미를 변경하도록 함으로써 작동한다는 점이 지적되었습니다.
그 결과, 동일한 개념에 대한 Christian Tismer의 제안 변형에 대한 지지가 점점 커지고 있습니다.:
<condition> then <expression1> else <expression2>
장점은 간단한 시각적 구문 분석, 필수 괄호가 없음, 기존 키워드의 의미 변경이 없음, 제안보다 문장 구문으로 혼동될 가능성이 낮음, 콜론에 추가적인 부담을 주지 않음입니다. 단점은 새 키워드를 도입하는 데 따르는 구현 비용입니다. 그러나 다른 새 키워드와 달리 “then”이라는 단어는 기존 프로그램에서 이름으로 사용되었을 가능성이 낮아 보입니다.
—
많은 C 계열 언어가 이 구문을 사용합니다:
<condition> ? <expression1> : <expression2>
Eric Raymond는 이를 구현하기까지 했습니다. BDFL은 여러 이유로 이를 거부했습니다: 콜론은 이미 파이썬에서 여러 용도로 쓰이고 있으며(물음표가 짝이 되는 콜론을 요구하므로 실제로는 모호하지 않겠지만); C 계열 언어에 익숙하지 않은 사람에게는 이해하기 어렵다는 것입니다.
—
이 PEP의 원래 버전은 다음 구문을 제안했습니다:
<expression1> if <condition> else <expression2>
순서가 뒤바뀐 배치는 논의에 참여한 많은 사람에게 지나치게 불편하다고 여겨졌습니다. 특히 <expression1>이 길 경우, 훑어볼 때 조건을 놓치기 쉽습니다.
—
일부는 언어의 구문을 확장하는 대신 새로운 내장 함수를 추가할 것을 제안했습니다. 예를 들어:
cond(<condition>, <expression1>, <expression2>)
이 방식은 함수가 호출되기 전에 expression1과 expression2가 모두 평가되어야 하므로 구문 확장과 같은 방식으로는 동작하지 않습니다. 표현식 평가를 단락(short-circuit) 처리할 방법이 없습니다. ‘cond’(또는 다른 이름)를 키워드로 만들면 동작할 수 있겠지만, 이는 새로운 키워드를 추가하는 것의 모든 단점에 더해 혼란스러운 구문까지 갖게 됩니다: 함수 호출처럼 보이기 때문에, 대충 읽는 독자는 <expression1>과 <expression2>가 모두 평가될 것이라고 예상할 수 있습니다.
현재 논의 상태 요약
그룹은 세 진영 중 하나로 나뉘고 있습니다:
- 구두점 문자를 사용하여 만든 삼항 연산자를 채택:
<condition> ? <expression1> : <expression2>
- 새 키워드 또는 기존 키워드를 사용하여 만든 삼항 연산자를 채택합니다. 대표적인 예는 다음과 같습니다:
<condition> then <expression1> else <expression2> (if <condition>: <expression1> else: <expression2>)
- 아무것도 하지 않습니다.
처음 두 입장은 비교적 유사합니다.
일부는 어떤 형태의 구두점이든 언어를 더 난해하게 만든다고 여깁니다. 다른 이들은 구두점 스타일이 문(statement)보다는 표현식에 적합하며, “3 plus 4 times 5”와 같은 COBOL 스타일을 피하는 데 도움이 된다고 여깁니다.
기존 키워드를 활용하는 방식은 명시적인 의미와 더 정돈된 외관을 통해 구두점 방식을 개선하려는 시도입니다. 단점은 구두점 연산자가 제공하는 표현의 경제성이 다소 손실된다는 것입니다. 또 다른 단점은 키워드의 두 가지 의미와 두 가지 용법 사이에 어느 정도 혼동을 일으킨다는 것입니다.
이러한 어려움은 구현하는 데 더 많은 노력이 필요한 새로운 키워드를 도입하는 방안으로 극복됩니다.
마지막 입장은 아무것도 하지 않는 것입니다. 찬성 논거로는 언어를 단순하고 간결하게 유지한다는 점, 하위 호환성을 유지한다는 점, 그리고 모든 사용 사례가 이미 “if”와 “else”로 표현될 수 있다는 점이 있습니다. 람다 표현식은 조건을 별도의 함수 정의로 분리해야 하므로 예외입니다.
아무것도 하지 않는 것에 반대하는 논거는, 다른 선택지들이 더 큰 표현의 경제성을 제공한다는 점과, 현재 관행이 “and”, “or”의 오용이나 그보다 더 복잡하면서도 시각적으로 덜 거슬리는 우회 방법 중 하나를 사용하는 경향을 보인다는 점입니다.
단락(Short-Circuit) 동작
삼항 연산자와 cond() 함수의 주된 차이점은, 후자가 표현식 형태를 제공하지만 단락 평가는 제공하지 않는다는 것입니다.
단락 평가는 세 가지 경우에 바람직합니다:
- 표현식에 부작용(side-effect)이 있을 때
- 표현식 중 하나 또는 둘 다 자원을 많이 소모할 때
- 조건이 표현식의 유효성을 보장하는 경우입니다.
# Example where all three reasons apply
data = isinstance(source, file) ? source.readlines()
: source.split()
readlines()는 파일 포인터를 이동시킵니다- 긴 소스의 경우, 두 대안 모두 시간이 걸립니다
split()은 문자열에만 유효하고readlines()는 파일 객체에만 유효합니다.
cond() 함수를 지지하는 사람들은 단락 평가(short-circuit evaluation)의 필요성이 드물다는 점을 지적합니다. 기존 코드 디렉터리를 훑어본 결과, if/else가 자주 나타나지 않았으며, 그중에서도 cond()나 삼항 연산자로 도움이 될 만한 표현식을 포함한 경우는 소수였고, 그 대부분은 단락 평가가 필요하지 않았다는 사실을 발견했습니다. 따라서 cond()는 대부분의 필요를 충족하며, 언어의 구문을 바꾸는 수고를 덜어줄 것입니다.
더 나아가, C 코드베이스를 조사한 결과 삼항 연산자가 (코드 줄 수 대비 비율로) 매우 드물게 사용된다는 것이 추가 증거로 나타났습니다.
이 분석에 대한 반론은, 삼항 연산자를 사용할 수 있다면 부작용을 찾아볼 필요가 없어지므로 모든 경우에 프로그래머에게 도움이 되었으리라는 것입니다. 나아가, 부작용을 유발하는 원거리 수정으로 인한 오류를 방지할 수 있었을 것입니다. 후자의 경우는 속성 접근에조차 부작용을 부여할 수 있는 프로퍼티(property)의 등장으로 더욱 현실화되었습니다.
BDFL의 입장은, if-then-else 구문이 언어에 추가되려면 단락 평가 동작이 필수적이라는 것입니다.
투표 상세 결과
Votes rejecting all options: 82
Votes with rank ordering: 436
---
Total votes received: 518
ACCEPT REJECT TOTAL
--------------------- --------------------- -----
Rank1 Rank2 Rank3 Rank1 Rank2 Rank3
Letter
A 51 33 19 18 20 20 161
B 45 46 21 9 24 23 168
C 94 54 29 20 20 18 235
D 71 40 31 5 28 31 206
E 7 7 10 3 5 32
F 14 19 10 7 17 67
G 7 6 10 1 2 4 30
H 20 22 17 4 10 25 98
I 16 20 9 5 5 20 75
J 6 17 5 1 10 39
K 1 6 4 13 24
L 1 2 3 3 9
M 7 3 4 2 5 11 32
N 2 3 4 2 11
O 1 6 5 1 4 9 26
P 5 3 6 1 5 7 27
Q 18 7 15 6 5 11 62
Z 1 1
--- --- --- --- --- --- ----
Total 363 286 202 73 149 230 1303
RejectAll 82 82 82 246
--- --- --- --- --- --- ----
Total 363 286 202 155 231 312 1549
선택지 키
A. x if C else y
B. if C then x else y
C. (if C: x else: y)
D. C ? x : y
E. C ? x ! y
F. cond(C, x, y)
G. C ?? x || y
H. C then x else y
I. x when C else y
J. C ? x else y
K. C -> x else y
L. C -> (x, y)
M. [x if C else y]
N. ifelse C: x else y
O. <if C then x else y>
P. C and x else y
Q. any write-in vote
기명 투표(write-in vote)와 그 순위에 대한 세부 사항
3: Q reject y x C elsethenif
2: Q accept (C ? x ! y)
3: Q reject ...
3: Q accept ? C : x : y
3: Q accept (x if C, y otherwise)
3: Q reject ...
3: Q reject NONE
1: Q accept select : (<c1> : <val1>; [<cx> : <valx>; ]* elseval)
2: Q reject if C: t else: f
3: Q accept C selects x else y
2: Q accept iff(C, x, y) # "if-function"
1: Q accept (y, x)[C]
1: Q accept C true: x false: y
3: Q accept C then: x else: y
3: Q reject
3: Q accept (if C: x elif C2: y else: z)
3: Q accept C -> x : y
1: Q accept x (if C), y
1: Q accept if c: x else: y
3: Q accept (c).{True:1, False:2}
2: Q accept if c: x else: y
3: Q accept (c).{True:1, False:2}
3: Q accept if C: x else y
1: Q accept (x if C else y)
1: Q accept ifelse(C, x, y)
2: Q reject x or y <- C
1: Q accept (C ? x : y) required parens
1: Q accept iif(C, x, y)
1: Q accept ?(C, x, y)
1: Q accept switch-case
2: Q accept multi-line if/else
1: Q accept C: x else: y
2: Q accept (C): x else: y
3: Q accept if C: x else: y
1: Q accept x if C, else y
1: Q reject choice: c1->a; c2->b; ...; z
3: Q accept [if C then x else y]
3: Q reject no other choice has x as the first element
1: Q accept (x,y) ? C
3: Q accept x if C else y (The "else y" being optional)
1: Q accept (C ? x , y)
1: Q accept any outcome (i.e form or plain rejection) from a usability study
1: Q reject (x if C else y)
1: Q accept (x if C else y)
2: Q reject NONE
3: Q reject NONE
3: Q accept (C ? x else y)
3: Q accept x when C else y
2: Q accept (x if C else y)
2: Q accept cond(C1, x1, C2, x2, C3, x3,...)
1: Q accept (if C1: x elif C2: y else: z)
1: Q reject cond(C, :x, :y)
3: Q accept (C and [x] or [y])[0]
2: Q reject
3: Q reject
3: Q reject all else
1: Q reject no-change
3: Q reject deliberately omitted as I have no interest in any other proposal
2: Q reject (C then x else Y)
1: Q accept if C: x else: y
1: Q reject (if C then x else y)
3: Q reject C?(x, y)
Copyright
This document has been placed in the public domain.