Python, Go 및 Rust는 왜 삼항 연산자를 사용하지 않을까요?
Lukas Schneider
DevOps Engineer · Leapcell

프로그래밍을 할 때 종종 조건부 판단을 내리고 결과에 따라 다른 코드 블록을 실행해야 합니다. 많은 프로그래밍 언어에서 이를 수행하는 가장 일반적인 방법은 삼항 연산자를 사용하는 것입니다. 그러나 Python은 삼항 연산자를 지원하지 않습니다. 흥미롭게도 가장 인기 있는 신흥 언어인 Go와 Rust도 이를 지원하지 않습니다!
Python은 왜 삼항 연산자를 지원하지 않을까요? 이 기사에서는 Python의 조건부 구문 설계 배경을 주로 분석하여 고유한 구현을 채택한 이유를 설명합니다. 동시에 다른 언어들이 기존의 삼항 연산자를 버린 이유도 살펴보겠습니다.
삼항 연산자란 무엇일까요?
삼항 연산자는 일반적으로 ?:
를 나타내며, 구문은 다음과 같습니다: condition ? expression1 : expression2
. 조건이 참이면 expression1
로 평가되고, 그렇지 않으면 expression2
로 평가됩니다.
간단한 형태인 a ? b : c
는 "조건 a
가 참이면 b
를 반환하고, 그렇지 않으면 c
를 반환한다"로 읽을 수 있습니다.
삼항 연산자는 표준 if-else 구조의 단순화된 버전이며, 조건부 검사를 수행하고 단일 문에서 값을 반환하는 데 일반적으로 사용됩니다.
// 일반적인 if-else if (a > b) { result = x; } else { result = y; } // 단순화된 버전 result = a > b ? x : y;
C, C#, C++, Java, JavaScript, PHP, Perl, Ruby, Swift 등 많은 프로그래밍 언어가 이 구문 설계를 채택했습니다. 의심할 여지 없이 오랫동안 프로그래밍 언어의 주류 설계 방식이었습니다 (그리고 지금도 그렇습니다).
이 구문은 매우 간결하고 효율적이며, 가독성도 뛰어납니다 (익숙해지면). 따라서 매우 인기가 있습니다.
그러나 결점이 없는 것은 아닙니다. Python은 이 설계 방식에 대한 가장 잘 알려진 도전장입니다. Python이 왜 다른 길을 선택했는지 살펴보겠습니다.
Python 커뮤니티 투표
Python은 1991년에 출시되었지만, 이후 15년 동안 조건문에 대한 if-else 구문만 있었고, 삼항 연산자나 다른 조건부 표현식을 지원하지 않았습니다. 조건부 표현식이 2006년에 도입되기 전에 커뮤니티는 길고 복잡한 토론에 참여했습니다. 이 구문 기능은 최종적으로 확정하기 어려운 기능이었다고 할 수 있습니다.
원래 if-then-else (삼항) 표현식을 추가하라는 반복적인 요청으로 인해, 2003년 2월에 PEP 308 – 조건부 표현식이 제안되었습니다. 목표는 대부분의 커뮤니티가 지지할 수 있는 솔루션을 선택하는 것이었습니다.
곧, 아무것도 하지 않는 것을 선호하는 소규모 그룹을 제외하고, 커뮤니티 내에서 몇 가지 제안이 나타났습니다.
(1) 구두점 기반 삼항 연산자
이는 일반적인 삼항 연산자로, 앞에서 언급한 것과 동일한 구문을 사용합니다.
<condition> ? <expression1> : <expression2>
이 제안은 꽤 인기가 있었고, 일부 개발자는 구현 코드를 제출하기도 했습니다. 그러나 Guido는 두 가지 이유로 이를 거부했습니다. 콜론은 이미 Python에서 많은 용도로 사용되고 있었고 (물론 물음표가 일치하는 콜론을 요구하기 때문에 모호성이 있을 가능성은 적었지만), C와 유사한 언어에 익숙하지 않은 사람들에게는 이 구문이 이해하기 어려울 수 있었습니다.
(2) 기존 및 새 키워드로 구성
새로운 then
키워드를 도입하고 기존의 else
와 결합합니다.
<condition> then <expression1> else <expression2>
장점으로는 명확성, 괄호 불필요, 기존 키워드의 의미 변경 없음, 문으로 오인될 위험이 낮음, 콜론 오버로딩 없음 등이 있습니다. 단점은 새 키워드를 추가하는 데 구현 측면에서 비용이 많이 든다는 것입니다.
(3) 기타 제안
이러한 제안은 두 번째 접근 방식과 정신적으로 유사했지만, 그다지 많은 지지를 얻지 못했습니다.
(if <condition>: <expression1> else: <expression2>) <condition> and <expression1> else <expression2> <expression1> if <condition> else <expression2> cond(<condition>, <expression1>, <expression2>)
특히 (if <condition>: <expression1> else: <expression2>)
형식은 일반적인 if-else 구문의 평탄화된 버전으로, 이해하기 쉽지만 괄호가 필요합니다. 이는 제너레이터 표현식과 혼동을 일으킬 수 있으며, 인터프리터에서 콜론을 특별히 처리해야 합니다.
또한 <expression1> if <condition> else <expression2>
도 주목할 만하며, 이는 원래 PEP 308의 초기 버전에서 권장되었던 제안입니다. 그러나 일부 사람들은 조건이 처음에 나오지 않는다는 이유로 이 형식을 싫어했습니다. 또한 expression1
이 길면 조건을 완전히 간과하기 쉽습니다.
당시 투표에 부쳐진 모든 옵션은 다음과 같습니다.
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
일반적으로 개발자들은 어떤 형태의 if-then-else 표현식을 도입하기를 원했지만, 투표 후에는 어떤 옵션도 명확한 과반수를 얻지 못했습니다. 분쟁은 구두점을 사용할지, 키워드를 재사용할지, 괄호를 재사용할지, 새 키워드를 도입할지, 새 구문을 추가할지... 등으로 귀결되었습니다.
투표가 너무 분산되었기 때문에 PEP는 당시 거부되었습니다. PEP는 "Python의 설계 원칙 중 하나는 불확실성에 직면했을 때 현 상태를 유지하는 것입니다."라고 밝혔습니다.
조건부 선택에 and-or
를 사용할 때의 문제점
위의 투표 이벤트는 2004년 3월에 열렸지만, 이 주제에 대한 논의는 PEP 308이 거부된 후에도 사그라지지 않았습니다. 개발자들은 여전히 if-else
를 대체할 간결한 방법을 찾고 있었습니다.
2005년 9월, 메일링 리스트에서 누군가가 Python 3.0에서 and
및 or
연산자의 논리를 변경할 것을 제안했습니다. 이 아이디어는 and
및 or
가 마지막으로 평가된 피연산자를 반환하는 대신 항상 부울 값을 반환하도록 단순화하는 것이었습니다.
이 제안의 이유는 작성자가 조건부 선택을 수행하기 위해 <condition> and <expression1> or <expression2>
형식을 사용했기 때문입니다. 그러나 이와 관련한 Python의 동작은 다른 언어와 다르며, 부주의하게 사용하면 버그로 이어질 수 있습니다!
다음 두 가지 예를 살펴보십시오. 어떤 결과가 예상되나요?
a = True and True or "Python" b = True and False or "Python"
<condition> and <expression1> or <expression2>
의 경우, 조건이 거짓이면 직접 평가되어 expression2
를 반환합니다. 조건이 참이면 expression1
을 평가합니다. expression1
도 참이면 expression2
를 평가하지 않습니다. 그러나 expression1
이 참이 아니면 평가되어 expression2
를 반환합니다.
따라서 위의 예에서 a
는 True
로 평가되고, b
는 "Python"
으로 평가됩니다.
Python은 진리값을 특별한 방식으로 처리합니다. 예를 들어, 해당 이메일의 작성자는 expression1
이 복소수 0+4i
인 상황에 직면했는데, 이는 진리값에서 False
로 평가됩니다. 결과적으로 최종 반환은 의도한 expression1
이 아니라 expression2
였습니다!
더 나은 해결책이 도입되기 전에 and-or
패턴은 조건부 선택을 수행하는 비교적 일반적인 방법이었습니다. PEP 308은 또한 이 접근 방식을 언급하면서 expression1
이 거짓으로 평가되면 문제가 발생할 수 있다고 지적했습니다. 심지어 이 패턴을 추악하고 혼란스럽다고 불렀습니다.
그 이메일은 조건부 구문에 대한 커뮤니티 논의를 다시 불러일으켰고, 저명한 개발자들이 의견을 내기 시작했습니다.
오늘날의 관점에서 볼 때 개발자들이 if-else
의 현 상태에 불만을 가지고 있었음이 분명합니다. 그러나 인기 있는 and-or
해결 방법은 충분히 좋지 않았으므로 Python이 이 문제점을 해결하기 위해 새롭고 표준화된 구문을 도입하려는 강한 열망이 있었습니다.
독특한 조건부 표현식
10일간의 이메일 논의 끝에 Guido van Rossum은 결국 X if C else Y
구문을 사용하여 조건부 표현식을 도입하기로 결정했습니다. 결과적으로 PEP 308이 다시 열리고 업데이트되었으며, 이 기능은 다음 해에 Python 2.5에 구현되었습니다.
이것은 우리가 이전에 언급한 일부 사람들이 불편하게 생각했던 구문입니다. 조건이 처음에 배치되지 않기 때문입니다.
그렇다면 왜 이것이 최종 승리한 디자인이 되었을까요? 최적의 선택이었을까요?
부인할 수 없이 결정적인 요인은 Guido 자신이었습니다. 1년 반 전에 커뮤니티 투표에서 과반수 합의가 나오지 않았기 때문에 그는 자신이 가장 좋은 솔루션이라고 생각하는 것을 선택하기 위해 BDFL (Benevolent Dictator For Life)로서의 권한을 행사했습니다.
X if C else Y
는 이해하기 매우 쉽고 가독성이 높습니다. 잠재적으로 혼란스러울 수 있는 구두점 대신 직관적인 자연어와 같은 if-else
를 사용하여 "명시적인 것이 암시적인 것보다 낫다"는 Python의 스타일을 따릅니다. 이는 &&
및 ||
와 같은 기호 대신 and
및 or
를 사용하는 Python과 유사합니다.
이 구문의 조정된 순서에 익숙해지는 데 시간이 걸릴 수 있지만, 구현에는 상당한 이점이 있습니다. 첫째, then
또는 when
과 같은 새 키워드나 추가 구문 요소를 도입하지 않고 기존 키워드인 if
와 else
만 재사용합니다. 또한 (if <condition>: <expression1> else: <expression2>)
형식보다 덜 번거롭습니다.
둘째, X if C else Y
의 효율성을 검증하기 위해 Guido는 표준 라이브러리에서 and-or
조합의 모든 용도를 검토한 결과 C and X or Y
의 각 인스턴스를 X if C else Y
로 대체할 수 있음을 발견했습니다. 표준 라이브러리의 상태는 새 구문이 실행 가능하다는 것을 입증했습니다.
이 역사를 되돌아보면 명확한 맥락을 확인할 수 있습니다. Python은 명확성과 간결성을 강조하는 언어의 특성과 일치하지 않기 때문에 ?:
삼항 연산자를 채택하지 않았습니다. 대신 and-or
패턴의 함정을 제거하기 위해 X if C else Y
표현식을 도입했습니다. 이 디자인은 간결하고 읽기 쉬우며 매우 유용합니다.
전반적으로 Python 설계자는 가독성과 유지 관리성을 매우 중요하게 생각합니다. 삼항 연산자를 채택하지 않고 대신 새로운 조건부 표현식을 만든 것은 공개 토론, 신중한 평가 및 균형 잡힌 절충의 결과였습니다.
Go와 Rust는 왜 삼항 연산자를 지원하지 않을까요?
Python의 설계 결정을 탐구한 후 "반대 진영"에서 가장 인기 있는 두 가지 언어를 살펴보겠습니다.
먼저 Go입니다. Go 언어의 공식 FAQ에는 "Go에는 ?:
연산자가 없는 이유는 무엇입니까?"라는 특정 질문이 있습니다.
Go는 ?:
연산자를 지원하지 않고 대신 기본 if-else
구문을 사용하는 것이 좋습니다. 설명서에서는 단 하나의 단락으로 간략한 설명을 제공합니다.
Go에는
?:
연산자가 없습니다. 설계자는 이 연산자가 혼란스럽고 복잡한 표현식을 만드는 데 사용되는 것을 보았기 때문입니다.if-else
가 더 길지만 의심할 여지 없이 더 명확합니다. 언어에는 조건부 제어 흐름 구조가 하나만 필요합니다.
다음은 Rust입니다. 공식 문서에서는 삼항 연산자가 지원되지 않는 이유를 설명하지 않는 것 같습니다. 그러나 더 자세히 조사한 결과 흥미로운 배경 이야기가 있습니다. 2011년 6월에 Rust는 실제로 삼항 연산자를 도입했습니다(#565
). 그러나 6개월 이내에 설계자들은 그것이 중복된다는 것을 깨닫고 제거했습니다(#1698
, #4632
)!
Rust에서 삼항 연산자가 중복되는 것으로 간주된 이유는 무엇일까요? 다른 많은 언어와 달리 Rust의 if
는 문이 아니라 표현식이기 때문입니다. 즉, if
표현식의 결과를 변수에 직접 할당할 수 있습니다.
// 조건이 참이면 5를 얻고, 그렇지 않으면 6을 얻습니다. let number = if condition { 5 } else { 6 };
이 구문은 간단하고 명확합니다. 익숙한 if-else
를 할당에 사용하는 것과 같습니다. 삼항 연산자로 바꾸는 것은 불필요하고 심지어 과도할 것입니다.
또한 Rust는 중괄호를 사용하여 코드 블록을 정의하므로 중괄호 안의 내용에는 여러 표현식이 포함될 수 있으며 다음 예와 같이 줄 바꿈도 지원할 수 있습니다.
let x = 42; let result = if x > 50 { println!("x is greater than 50"); x * 2 // 이것은 표현식이며 해당 값이 `result`에 할당됩니다. } else { println!("x is less than or equal to 50"); x / 2 // 이것도 표현식이며 반환되어 `result`에 할당됩니다. };
이러한 종류의 사용법은 Python에서는 불가능합니다. 가장 중요한 차이점은 Rust의 if
가 문이 아닌 표현식이라는 사실에 있습니다.
두 개념의 차이점은 다음과 같습니다.
- 표현식: 변수, 상수, 연산자 등으로 구성된 코드 조각으로, 값을 평가하고 다른 표현식이나 문 내에서 사용할 수 있습니다.
- 문: 할당, 조건 또는 루프와 같은 작업을 수행하는 단일 명령 또는 명령 그룹입니다. 문은 값을 반환하지 않으며 (또는 아무것도 반환하지 않음) 표현식에서 사용할 수 없습니다.
Rust 외에도 if
가 표현식인 다른 프로그래밍 언어가 있습니다(예: Kotlin, Scala, F#, Swift). 이론적으로 이러한 언어에는 삼항 연산자가 필요하지 않습니다. (여담: Swift는 예외입니다. 삼항 연산자가 있습니다. Kotlin에도 ?:
연산자가 있지만 두 기호가 연결되어 있습니다. 예를 들어 val result = a ?: b
는 a
가 null이 아니면 result
에 할당하고 그렇지 않으면 b
를 할당함을 의미합니다.)
이러한 언어 수준의 설계 차이로 인해 Rust와 Python/Go는 본질적으로 다른 출발점에서 삼항 연산자 질문에 접근합니다. 이 차이점을 이해하면 프로그래밍 언어에 대한 더 명확한 관점을 얻을 수 있습니다.
그렇다면 원래 질문으로 돌아가서 일부 프로그래밍 언어는 왜 주류 삼항 연산자 구문을 채택하지 않을까요?
?:
가 간결하고 유용한 디자인이라는 것은 부인할 수 없습니다. 그러나 구두점을 사용하는 단점은 if-else
보다 지나치게 추상적이고 가독성이 떨어질 수 있다는 것입니다. 또한 다양한 언어 철학과 사용 습관이 다른 선택으로 이어집니다.
Python은 많은 우여곡절 끝에 결국 자체적인 고유한 조건부 표현식 구문을 만들었습니다. Go는 삼항 연산자를 지원하지 않는다고 명시적으로 밝혔습니다. Rust는 그것을 도입했다가 제거했습니다. 주로 if
를 표현식으로 사용하는 근본적인 이유 때문입니다.
Leapcell은 Rust 프로젝트 호스팅을 위한 최고의 선택입니다.
Leapcell은 웹 호스팅, 비동기 작업 및 Redis를 위한 차세대 서버리스 플랫폼입니다.
다국어 지원
- Node.js, Python, Go 또는 Rust로 개발하십시오.
무제한 프로젝트를 무료로 배포
- 사용량에 대해서만 지불하십시오. 요청도 없고, 요금도 없습니다.
비교할 수 없는 비용 효율성
- 유휴 요금 없이 사용량에 따라 지불합니다.
- 예: $25는 평균 응답 시간 60ms에서 694만 건의 요청을 지원합니다.
간소화된 개발자 경험
- 손쉬운 설정을 위한 직관적인 UI
- 완전 자동화된 CI/CD 파이프라인 및 GitOps 통합
- 실행 가능한 통찰력을 위한 실시간 메트릭 및 로깅
손쉬운 확장성 및 고성능
- 쉬운 동시성 처리를 위한 자동 확장
- 제로 운영 오버헤드 - 빌드에만 집중하십시오.
설명서에서 자세히 알아보십시오!
X에서 팔로우하세요: @LeapcellHQ