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의 이전 초안 소개(기록 보존을 위해 남겨 둠)
comp.lang.python에서는 if-then-else(“삼항”) 표현식에 대한 요청이 계속해서 제기되고 있습니다. 이 PEP는 상당히 Python다운 문법에 대한 구체적인 제안을 담고 있습니다. 이번이 커뮤니티에 주어진 단 한 번의 기회입니다. 이 PEP가 확실한 과반수로 승인되면 Python 2.4에 구현될 것입니다. 그렇지 않으면 이 PEP에 거부 사유의 요약이 추가될 것이며, 이 주제는 다시 거론되지 않는 편이 좋을 것입니다. BDFL이 이 PEP의 공동 저자이기는 하지만, 그는 이 제안에 찬성하지도 반대하지도 않습니다. 결정은 커뮤니티에 달려 있습니다. 커뮤니티가 결정을 내리지 못하면 BDFL이 이 PEP를 거부할 것입니다.
커뮤니티에서 전례 없는 반응이 있은 후(찬반 양측에서 매우 훌륭한 인자가 제시되었습니다) 이 PEP는 Raymond Hettinger의 도움을 받아 개정되었습니다. 전체 개정 이력을 일일이 살펴보지 않고 요약하면, 주요 변경 사항은 다르게 제안된 문법, 제안된 대안의 개요, 현재 논의 상황, 그리고 단락 평가(short-circuit) 동작에 대한 논의입니다.
논의에 이어 투표가 진행되었습니다. 어떤 형태로든 if-then-else 표현식을 도입하는 데에 전반적인 관심은 있었지만, 어느 한 형식도 과반수의 지지를 얻지 못했습니다. 이에 따라 변경을 지지하는 압도적 다수가 없다는 이유로 이 PEP는 거부되었습니다. 또한 Python의 설계 원칙 중 하나는 어느 길을 택할지 의문이 있을 때는 언제나 현状을 유지하는 쪽을 택하는 것이었습니다.
제안
제안하는 문법은 다음과 같습니다:
(if <condition>: <expression1> else: <expression2>)
둘러싸는 괄호는 생략할 수 없다는 점에 유의하십시오.
결과 표현식은 다음과 같이 평가됩니다:
- 먼저 <condition>이 평가됩니다.
- <condition>이 참이면 <expression1>이 평가되고, 그것이 전체의 결과가 됩니다.
- <condition>이 거짓이면 <expression2>가 평가되고, 그것이 전체의 결과가 됩니다.
이 문법을 자연스럽게 확장하면 하나 이상의 ‘elif’ 부분을 허용할 수 있습니다:
(if <cond1>: <expr1> elif <cond2>: <expr2> ... else: <exprN>)
제안이 수락되면 이것도 구현될 것입니다.
이 제안의 단점은 다음과 같습니다:
- 괄호가 필수라는 점
- 문(statement) 문법과 혼동될 수 있다는 점
- 콜론에 의미가 추가로 얹힌다는 점
<expression1>과 <expression2> 중 많아야 하나만 평가된다는 점에 유의하십시오. 이것을 “단락 표현식(short-circuit expression)”이라고 합니다. 이는 ‘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은 몇 가지 이유로 이를 거부했습니다. 콜론은 Python에서 이미 여러 용도로 쓰이고 있으며(물음표에는 짝이 되는 콜론이 필요하므로 실제로 모호하지는 않겠지만), 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”를 잘못 사용하거나 더 복잡하고 시각적으로 보기 좋지 않은 우회 방법 중 하나를 사용하는 경향이 있다는 점입니다.
단락 평가 동작
삼항 연산자와 cond() 함수의 주된 차이점은 후자가 표현식 형태를 제공하지만 단락 평가는 제공하지 않는다는 것입니다.
단락 평가는 다음 세 가지 경우에 바람직합니다.
- 표현식에 부작용이 있을 때
- 표현식 중 하나 또는 둘 모두가 자원을 많이 사용할 때
- 조건이 표현식의 유효성을 보장하는 가드 역할을 할 때
# Example where all three reasons apply
data = isinstance(source, file) ? source.readlines()
: source.split()
readlines()는 파일 포인터를 이동시킵니다- 소스가 긴 경우 두 대안 모두 시간이 걸립니다
split()은 문자열에만 유효하고readlines()는 파일 객체에만 유효합니다.
cond() 함수를 지지하는 사람들은 단락 평가가 필요한 경우가 드물다고 지적합니다. 이들은 기존 코드 디렉터리를 조사한 결과 if/else가 자주 나타나지 않았고, 그중 cond()나 삼항 연산자로 개선할 수 있는 표현식을 담은 경우는 소수에 불과했으며, 또 그 대부분은 단락 평가가 필요하지 않았다는 것을 발견했습니다. 따라서 cond()만으로도 대부분의 요구를 충족할 수 있고, 언어의 문법을 바꾸는 수고도 덜 수 있습니다.
C 코드베이스를 조사한 결과도 이를 뒷받침하는데, C의 삼항 연산자는 (코드 줄 수 대비 비율로 볼 때) 매우 드물게 사용되었습니다.
이 분석에 대한 반론은, 삼항 연산자가 있으면 부작용을 찾아볼 필요가 없어지므로 모든 경우에 프로그래머에게 도움이 되었다는 것입니다. 또한 멀리 떨어진 곳에서 부작용을 끌어들이는 수정 때문에 생기는 오류도 막을 수 있습니다. 후자의 경우는 속성 접근에조차 부작용을 부여할 수 있는 프로퍼티가 등장하면서 더욱 현실적인 문제가 되었습니다.
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
직접 기입한 투표와 그 순위의 세부 내용
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.