Python의 미리 선언된 여섯 상수가 서로 어떻게 다르게 동작하는지 살펴봅니다.
Python에는 미리 선언된 여섯 개의 "상수"가 있다. True, False, None, __debug__, Ellipsis(또는 동등하게 ...), 그리고 NotImplemented다. 그런데 어째서인지 이들은 모두 조금씩 다르게 동작한다.
True, False, NoneTrue, False, None은 키워드다. 이들은 식별자가 아니라, 그 자체로 별도의 어휘 토큰이다. 정말 이상하다. Python에서는 다른 어떤 것도 이렇지 않다. 보통 항목은 어휘 분석기 자체가 아니라 일반적인 이름 해석 과정에서 해석된다.
이로 인한 흥미로운 부작용은 x.True 같은 표현식이 SyntaxError를 일으킨다는 점이다. 이 결정에 근거가 있었는지, 있었다면 무엇이었는지 궁금하다.
이 상수들에는 더 흥미로운 점이 있지만, 다른 상수들과 연결되므로 나중에 다루겠다.
__debug____debug__는 불리언 상수다. 보통은 True이지만, -O와 함께 실행하면 False다. 그 발상은 디버그가 아닌 빌드에서 assert가 비활성화되는 방식과 비슷하다. 검사 비용이 "최적화된" 빌드에서는 너무 클 경우 등에 코드를 if __debug__로 감쌀 수 있다.
그런데 __debug__는 정말 흥미롭다. True, False, None과 달리 일반 식별자이면서도, 언어에서 대입할 수 없는 유일한 식별자이기 때문이다.
debug = 67 File "", line 1 SyntaxError: cannot assign to debug 속성으로도 대입할 수 없다.
x.debug = 67 File "", line 1 SyntaxError: cannot assign to debug 다시 말하지만, 다른 어떤 식별자도 이렇게 동작하지 않는다. 이것은 진정한 특수 사례다.
하지만 키워드는 아니므로 True, False, None과는 조금 다르게 동작한다.
x.__debug__는 문법적으로는 유효하지만 존재하지 않는 속성을 찾으려 하기 때문에, SyntaxError가 아니라 AttributeError를 일으킨다.
흥미롭게도 __debug__를 삭제하려 할 때에는 특별한 오류 메시지도 있다. 특수 사례가 아니었다면 어차피 NameError를 일으켰을 텐데도 말이다. 하지만 __debug__라는 이름의 속성을 삭제할 때에는 이것이 적용되지 않는다.
del debug File "", line 1 SyntaxError: cannot delete debug >>> del x.debug Traceback (most recent call last): File "", line 1, in NameError: name 'x' is not defined
x가 정의되어 있었다면 대신AttributeError가 일어났을 것이다. 어느 경우든, 어째서인지 대입과 달리SyntaxError는 아니다.
SyntaxError는 거짓말이다오류 이야기가 나왔으니 말인데, __debug__에 대입하는 것은 실제로는 문법이 잘못되지 않았는데도 SyntaxError가 일어나는, 내가 아는 몇 안 되는 경우 중 하나다. 여기서는 직접 확인할 수 있다.
assert (debug := 67) 디버그 빌드에서 그 단언문을 실행하면
SyntaxError가 일어나지만, -O에서는 단언문이 아예 컴파일되지 않으므로 예외도 일어나지 않는다.
다른 두 사례는 함수 밖에서 yield나 await를 사용하는 것이다.
assert (yield)>>> assert (await 67)
Ellipsis와 NotImplementedEllipsis와 NotImplemented는 레퍼런스의 "상수" 절에 문서화되어 있지만, 다른 네 상수와 달리 "진짜" 상수는 아니다. 그저 일반 내장이므로 전역 이름으로 가릴 수 있다.
NotImplemented = 67>>> NotImplemented 67 다시 말하지만, 여기에도 근거가 무엇인지 궁금하다. 왜 이들은 특별하지 않은데 다른 상수들은 특별한가?
여기 흥미로운 점이 있다. True, False, None은 어휘 토큰임에도 일반 내장으로도 존재한다.
import builtins>>> getattr(builtins, 'True') True >>> getattr(builtins, 'False') False >>> getattr(builtins, 'None') is None True
getattr를 쓰지 않고 이들에 직접 접근할 방법은 없다.
하지만 여기서 정말 흥미로워진다. setattr도 작동한다!
setattr(builtins, 'True', 67)>>> getattr(builtins, 'True') 67 하지만 이것은 어휘 토큰으로 접근했을 때의 값을 바꾸지 않는다.
True True 그런데
__debug__도 같은 방식으로 동작한다!
setattr(builtins, 'debug', 67)>>> builtins.debug 67 >>> debug True 따라서
__debug__에는 어느 정도 대입할 수 있지만, 어휘 토큰은 아닌데도True,False,None처럼 특수 처리된다. 내장 모듈의 변경은 그 값에 영향을 주지 않는다. 그러므로 정말 상수인 셈이다!
반면 Ellipsis와 NotImplemented는 다시 한번 실제 상수가 아니다.
setattr(builtins, 'Ellipsis', 67)>>> Ellipsis 67 하지만 이것은
...의 값을 바꾸지는 않는다.
... Ellipsis 그러니 어떤 의미에서는
...가 진짜 상수이고Ellipsis는 아니다. 이상하지 않은가?