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

Python 개선 제안 한국어 번역

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

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 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>가 모두 평가될 것이라고 예상할 수 있습니다.

현재 논의 상황 요약

그룹들은 다음 세 진영 중 하나로 나뉘고 있습니다.

  1. 구두점 문자를 사용해 만든 삼항 연산자를 채택합니다:
    <condition> ? <expression1> : <expression2>
    
  2. 새 키워드나 기존 키워드를 사용해 만든 삼항 연산자를 채택합니다. 대표적인 예는 다음과 같습니다.:
    <condition> then <expression1> else <expression2>
    (if <condition>: <expression1> else: <expression2>)
    
  3. 아무것도 하지 않기

처음 두 입장은 비교적 비슷합니다.

어떤 사람들은 어떤 형태의 구두점이든 언어를 더 난해하게 만든다고 봅니다. 다른 사람들은 구두점 스타일이 문(statement)보다는 표현식에 적합하며, 3 plus 4 times 5 같은 COBOL 스타일을 피하는 데 도움이 된다고 봅니다.

기존 키워드를 활용하는 방식은 명시적인 의미와 더 깔끔한 외관을 통해 구두점 방식을 개선하려는 시도입니다. 단점은 구두점 연산자가 제공하는 표현의 경제성이 어느 정도 사라진다는 것입니다. 또 다른 단점은 키워드의 두 가지 의미와 두 가지 용법 사이에 어느 정도 혼란이 생긴다는 것입니다.

이러한 어려움은 새 키워드를 도입하는 선택지로 극복할 수 있지만, 이 선택지는 구현하는 데 더 많은 노력이 듭니다.

마지막 입장은 아무것도 하지 않는 것입니다. 찬성 측 인자로는 언어를 단순하고 간결하게 유지한다는 점, 하위 호환성을 유지한다는 점, 그리고 모든 사용 사례를 이미 “if”와 “else”로 표현할 수 있다는 점이 있습니다. 람다 표현식은 예외인데, 조건식을 별도의 함수 정의로 분리해야 하기 때문입니다.

아무것도 하지 않는 것에 반대하는 인자는, 다른 선택지들이 더 경제적인 표현을 가능하게 한다는 점과 현재의 관행을 보면 “and”, “or”를 잘못 사용하거나 더 복잡하고 시각적으로 보기 좋지 않은 우회 방법 중 하나를 사용하는 경향이 있다는 점입니다.

단락 평가 동작

삼항 연산자와 cond() 함수의 주된 차이점은 후자가 표현식 형태를 제공하지만 단락 평가는 제공하지 않는다는 것입니다.

단락 평가는 다음 세 가지 경우에 바람직합니다.

  1. 표현식에 부작용이 있을 때
  2. 표현식 중 하나 또는 둘 모두가 자원을 많이 사용할 때
  3. 조건이 표현식의 유효성을 보장하는 가드 역할을 할 때
#  Example where all three reasons apply
data = isinstance(source, file)  ?  source.readlines()
                                 :  source.split()
  1. readlines()는 파일 포인터를 이동시킵니다
  2. 소스가 긴 경우 두 대안 모두 시간이 걸립니다
  3. 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)