[Rejected] PEP 3103 - A Switch/Case Statement
원문 링크: PEP 3103 - A Switch/Case Statement
상태: Rejected 유형: Standards Track 작성일: 25-Jun-2006
PEP 3103 – switch/case
문 제안 (거부됨)
거부 공지
PyCon 2007 기조연설 중 진행된 빠른 설문조사에서 이 제안이 대중적 지지를 받지 못했음이 확인되어, 결국 PEP 3103은 거부되었습니다.
개요
Python-dev 커뮤니티에서는 최근 switch
문 추가에 대한 활발한 논의가 있었습니다. 이 PEP는 다양한 제안들 속에서 Guido van Rossum 본인의 선호를 정리하고, 여러 대안을 논의하며 선택의 이유를 설명하고자 했습니다. 이 PEP는 PEP 275의 대안으로 제시되었으며, 구문 및 의미론의 다양한 측면에 대해 논의된 여러 변형들을 위한 표준 명칭(예: “alternative 1”, “school II”, “option 3” 등)을 도입하여 논의를 돕고자 했습니다.
도입 배경 (Rationale)
프로그래밍에서 흔히 사용되는 이디엄(idiom)은 특정 표현식의 값에 따라 다른 작업을 수행하는 것입니다. 이는 일반적으로 if/elif
조건문 체인으로 처리됩니다. 이러한 이디엄을 위한 새로운 구문(switch
문)을 도입하려는 주된 동기는 두 가지입니다.
- 반복성: 변수와 테스트 연산자(일반적으로
==
또는in
)가 각if/elif
분기에서 반복됩니다. - 비효율성: 표현식이 마지막 테스트 값(또는 어떤 테스트 값에도 해당하지 않는 경우)에 일치할 때, 앞선 모든 테스트 값과 비교됩니다.
이러한 불만 사항들은 비교적 경미하며, 다른 방식으로 작성한다고 해서 가독성이나 성능에서 크게 얻을 것이 많지는 않습니다. 하지만 많은 언어에서 switch
문이 발견되며, Python에 이를 추가하면 특정 코드를 더 깔끔하고 효율적으로 작성할 수 있을 것이라는 기대가 있었습니다. 물론, 케이스의 수가 정적으로 알려지지 않거나, 다른 클래스나 파일에 코드를 배치하는 것이 더 바람직한 경우에는 제안된 switch
문에 적합하지 않은 디스패치(dispatch) 형태도 있습니다.
기본 구문 (Basic Syntax)
PEP 275에서 처음 제안된 구문의 여러 변형이 고려되었습니다.
모든 대안에는 “암묵적인 break
” 속성이 있습니다. 즉, 특정 case
의 스위트(suite) 끝에서 제어 흐름은 전체 switch
문의 끝으로 이동합니다. C
언어와 달리, 하나의 case
에서 다른 case
로 제어를 전달하는 (fall-through
) 방법은 없습니다.
모든 대안에서 else
스위트는 선택 사항입니다. C
언어처럼 새로운 예약어 default
를 도입하기보다는 else
를 사용하는 것이 더 Pythonic하다고 여겨졌습니다.
다음은 제안된 기본 구문 대안들입니다.
- Alternative 1 (선호): PEP 275에서 선호하는 형태입니다.
switch EXPR: case EXPR: SUITE case EXPR: SUITE ... else: SUITE
주요 단점은 실제 로직이 있는 스위트가 두 단계 깊이로 들여쓰기된다는 점입니다. 이는
case
를 “반 단계” 들여쓰기하여 (예: 일반적인 들여쓰기 수준이 4칸일 때 2칸) 해결할 수 있습니다. - Alternative 2: Fredrik Lundh가 선호하는 형태이며,
case
가 들여쓰기되지 않습니다.switch EXPR: case EXPR: SUITE case EXPR: SUITE .... else: SUITE
자동 들여쓰기 에디터나 코드 폴딩 에디터 등에서 어려움이 예상되고, 사용자에게 혼란을 줄 수 있다는 단점이 있습니다. Python에서는 콜론으로 끝나는 줄 다음에 들여쓰기되지 않은 줄이 오는 경우가 현재 없습니다.
- Alternative 3: Alternative 2와 같지만
switch
뒤에 콜론이 없습니다.switch EXPR case EXPR: SUITE case EXPR: SUITE .... else: SUITE
일반적인 Python 인식 텍스트 에디터의 자동 들여쓰기 로직에 덜 혼란을 줄 것이라는 기대가 있었지만, 이상하게 보인다는 의견이 있었습니다.
- Alternative 4:
case
키워드가 중복된다는 이유로 생략됩니다.switch EXPR: EXPR: SUITE EXPR: SUITE ... else: SUITE
case
표현식을 들여쓰기해야만 한다는 문제가 발생합니다. 그렇지 않으면 파서가 들여쓰기되지 않은case
표현식(스위치 문을 계속하는)과 표현식처럼 시작하는 관련 없는 문(할당이나 함수 호출 등)을 구별하기 어려워집니다. 파서는 콜론을 본 후 되돌아가기(backtrack)할 만큼 똑똑하지 않습니다. 이는 가장 선호되지 않는 대안이었습니다.
확장 구문 (Extended Syntax)
때때로 두 개 이상의 값이 동일하게 처리되어야 할 필요가 있습니다. C
언어에서는 코드 없이 여러 case
레이블을 함께 작성하여 이를 처리하지만, Python switch
는 fall-through
의미론을 가지지 않으므로 다른 해결책이 필요했습니다.
- Alternative A:
- 단일 표현식 매칭:
case EXPR:
- 다중 표현식 매칭:
case EXPR, EXPR, ...:
이때(EXPR, EXPR, ...)
형태는 튜플로 해석되어,switch
표현식이 이 튜플 자체와 같아야 하며, 튜플의 요소 중 하나와 같은 것은 아닙니다. 이는 변수를 사용하여 여러case
를 나타낼 수 없다는 의미입니다.
- 단일 표현식 매칭:
- Alternative B:
- 단일 표현식 매칭:
case EXPR:
- 다중 표현식 매칭:
case in EXPR_LIST:
EXPR_LIST
가 단일 표현식이면in
키워드가 이를 이터러블(__contains__
를 지원하는 객체)로 해석하도록 강제합니다. 여러 표현식이면 각 표현식이 매칭을 위해 고려됩니다.
- 단일 표현식 매칭:
- Alternative C:
- 단일 표현식 매칭:
case EXPR:
- 다중 표현식 매칭:
case EXPR, EXPR, ...:
(Alternative A와 동일) - 이터러블 요소 매칭:
case *EXPR:
(값의 이터러블 요소를 매칭) 마지막 두 경우는 결합될 수 있어,case [*]EXPR, [*]EXPR, ...:
와 같은 구문이 될 수 있습니다.*
표기법은 가변 길이 매개변수 목록이나 인자 목록 전달에 사용되는 기존의*
와 유사합니다.
- 단일 표현식 매칭:
- Alternative D: Alternative B와 C의 혼합 형태입니다. 구문은 Alternative B와 유사하지만
in
키워드 대신*
를 사용합니다.- 단일 표현식 매칭:
case EXPR:
- 이터러블 요소 매칭:
case *EXPR:
하나의case
에서 여러 매치를 지정하려면case *(EXPR, EXPR, ...):
또는case * EXPR, EXPR, ...:
와 같이 작성할 수 있습니다.
- 단일 표현식 매칭:
논의 (Discussion)
Alternative B, C, D는 여러 case
를 동일하게 처리하기 위해 세트(보통 튜플)를 나타내는 변수를 사용하여 명시적으로 모두 나열하는 대신 지정할 수 있도록 하려는 동기에서 비롯되었습니다. 이는 동일한 case
세트에 대해 여러 switch
문이 있을 때 모든 대안을 매번 작성해야 하는 번거로움을 줄이기 위함입니다. 또한, Pascal의 “1..1000:” 표기법처럼 범위를 쉽고 효율적으로 지정할 수 있도록 하려는 동기도 있었습니다.
하지만 case
추가에 따른 복잡성을 고려할 때, 이러한 필요성이 충분하지 않다는 의견도 있었습니다. C
언어에도 범위 표현 방법이 없지만 널리 사용되고 있습니다. 또한, 딕셔너리 기반 디스패치 방식이 의미론으로 채택될 경우, 큰 범위는 비효율적일 수 있습니다.
Guido van Rossum의 선호도는 (가장 선호하는 것부터 가장 적게 선호하는 것 순으로) B, A, D’, C였습니다.
의미론 (Semantics)
switch
문의 의미론에 대해 여러 가지 주요 학파(schools of thought)가 있었습니다.
If/Elif
체인 vs. 딕셔너리 기반 디스패치
- School I:
switch
문을 동등한if/elif
체인으로 정의하되, 일부 최적화를 포함할 수 있다고 봅니다.- School Ia:
switch
문은 단순히 동등한if/elif
체인으로 번역되며, 최적화와는 무관하다고 주장합니다. Guido van Rossum은 최적화에 대한 힌트가 없다면 새로운 구문을 정당화할 만큼switch
문이 매력적이지 않다고 반대했습니다. - School Ib: 최적화가 중요하다고 동의하며, 컴파일러가 이를 위해 특정 자유를 가질 수 있도록 허용합니다. (예: PEP 275 Solution 1). 특히,
switch
및case
표현식의hash()
가 호출될 수도 있고 안 될 수도 있으므로 부작용이 없어야 합니다. 또한case
표현식이if/elif
체인처럼 매번 평가되지 않을 수 있으므로 부작용이 없어야 합니다. Guido van Rossum은hash()
나case
표현식에 부작용이 있을 경우, 최적화된 코드와 최적화되지 않은 코드가 다르게 동작할 수 있다는 점을 문제 삼았습니다.
- School Ia:
- School II: 일반적인 경우의 최적화가 쉽지 않다는 인식에서 출발했으며, 이를 정면으로 다루는 것이 낫다고 봅니다.
- 딕셔너리 기반 디스패치를 사용하여 최적화할 때,
switch
표현식이나case
표현식이 해시 가능하지 않은 경우(hash()
가 예외를 발생시키는 경우), School Ib는hash()
실패를 잡고if/elif
체인으로 폴백(fallback)해야 한다고 요구하지만, School II는 단순히 예외가 발생하도록 둡니다.hash()
예외를 잡는 것은 실제 버그를 숨길 수 있다는 문제가 있습니다. - 딕셔너리 기반 디스패치를 사용하여 최적화할 때, 관련된 어떤 표현식의
hash()
함수가 잘못된 값을 반환하면, School Ib에서는 최적화된 코드와 최적화되지 않은 코드가 다르게 동작하지만, School II에서는 적어도 일관되게 잘못된 결과가 생성되어 디버깅이 더 쉬울 수 있습니다. - School Ib는
case
표현식이 명명된 상수(named constants
)인 경우 좋은 최적화 전략이 없습니다. 컴파일러는 그 값을 확실히 알 수 없으며, 진정으로 상수인지도 알 수 없습니다. - 중복
case
(동일한 값으로 평가되는 매치 표현식을 가진 두 개 이상의case
) 처리에 대한 의견 차이도 있습니다. School I는if/elif
체인처럼 첫 번째 매치가 이기고 두 번째 매치의 코드는 도달할 수 없게 처리하길 원합니다. School II는 디스패치 딕셔너리가 동결될 때 이를 오류로 간주하여 죽은 코드(dead code)가 진단되지 않는 것을 방지하길 원합니다. - 하지만 중복
case
에 대한 유스 케이스도 있습니다. 예를 들어, 특정 OS에서 두 개의 다른 상수가 같은 값을 가질 때 (예: Unix의O_TEXT
와O_BINARY
), 중복case
를 오류로 플래그하면 해당 OS에서switch
가 작동하지 않을 수 있습니다. - Guido van Rossum은 개인적으로 School II, 특히 School IIb(중복
case
는 오류로 플래그하는 대신case
의 순서로 해결되어야 함)를 선호한다고 밝혔습니다.
- 딕셔너리 기반 디스패치를 사용하여 최적화할 때,
- School III: School I에 동의하여
switch
문의 정의는 동등한if/elif
체인 관점에서 이루어져야 한다고 보지만, 최적화 진영에 동의하여 관련된 모든 표현식이 해시 가능해야 한다고 주장합니다.
디스패치 딕셔너리 동결 시점 (When to Freeze the Dispatch Dict)
School II 지지자들에게는 switch
에 사용되는 딕셔너리를 언제 생성할지(freezing the dict
)가 다음 주요 쟁점이었습니다. 주된 문제는 Python에 컴파일 타임 상수가 없다는 점입니다. 개념적으로 상수인 re.IGNORECASE
같은 것도 컴파일러에게는 변수이며, 악의적인 코드가 그 값을 수정하는 것을 막을 방법이 없습니다.
-
Option 1 (컴파일러 동결):
case
표현식이 모두 리터럴이거나 리터럴과 컴파일러가 의미론을 아는 연산자만 포함하는 컴파일 타임 표현식이어야 합니다. 이는 너무 제한적이라는 의견이 많았습니다. 이 접근 방식의 주요 옹호자는 Raymond Hettinger였습니다. Guido van Rossum은 명명된 상수를 허용하지 않으면 프로그래머가 좋은 습관을 포기하게 만든다고 지적했습니다. -
Option 2 (첫 실행 시 동결):
switch
가 처음 실행될 때 디스패치 딕셔너리를 동결합니다. 이 시점에는case
표현식으로 사용되는 모든 명명된 “상수”가 정의되어 있다고 가정할 수 있습니다.switch
가 여러 번 실행될 경우, 첫 실행 시의 추가 작업이 나중의 빠른 디스패치 시간으로 빠르게 보상될 수 있습니다. 단점으로는 디스패치 딕셔너리를 저장할 명확한 객체가 없다는 점(코드 객체는 불변, 함수 객체는 여러 개 생성 가능), 그리고 멀티스레드 환경에서 올바른 의미론을 보장하기 위해 복잡한 락(locking)이 필요하다는 점이 있었습니다. -
Option 3 (함수 정의 시 동결):
switch
를 포함하는 가장 안쪽 함수가 정의될 때 딕셔너리를 동결합니다.switch
딕셔너리는 매개변수 기본값과 마찬가지로 함수 객체에 저장되며,case
표현식도 매개변수 기본값과 동일한 시점과 범위에서 평가됩니다. 이 옵션은 락 필요성, 불변 코드 객체 또는 여러 인터프리터에 대한 걱정을 없애는 등 Option 2에서 필요한 많은 섬세한 부분들을 피할 수 있다는 장점이 있습니다. 또한case
표현식에서 지역 변수를 참조할 수 없는 이유에 대한 명확한 설명도 제공합니다. 단점으로는 중첩 함수 내의switch
에 대한 디스패치 딕셔너리가 중첩 함수가 정의될 때마다 재계산되어야 한다는 점이 있습니다. 함수 외부에switch
가 있는 경우 디스패치 딕셔너리가 언제 동결되는지에 대한 명확한 순간이 없다는 문제도 있었습니다. -
Option 4 (새로운 언어 구성체): 함수 정의 시 미리 계산된 값의 개념을 일반적으로 사용할 수 있도록 하는 언어 구성체를 추가하자는 제안입니다.
const
,static
,only
,cached
등의 키워드가 제안되었습니다. 이 제안들은 PEP 3103의 범위를 벗어나지만, 만약 이러한 제안이 받아들여진다면switch
문은case
표현식을 컴파일 타임 상수 또는 미리 계산된 값으로 요구하거나, 미리 계산된 값을 기본 평가 모드로 만들 수 있다는 이점이 있습니다.
결론 (Conclusion)
결정하기에는 너무 이릅니다. 미리 계산된 값에 대한 최소한 하나의 완성된 제안이 나온 후에 결정하고 싶습니다. 그동안 Python은 switch
문 없이도 잘 작동하고 있으며, 아마도 switch
문을 추가하는 것이 실수라고 주장하는 사람들이 옳을지도 모릅니다.
⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.
Comments