argparse에서 --flag False가 True가 되는 이유는 False가 불리언 값이 아니라 비어 있지 않은 문자열로 전달되기 때문입니다. 옵션이 존재하는지만 판단할 때는 store_true 또는 store_false를 사용하고, 외부 자동화가 true·false 값을 직접 보낼 때는 허용 문자열을 명시적으로 검증해야 합니다. 이 차이를 먼저 구분하면 CLI 옵션 설계가 훨씬 단순해집니다.
왜 type=bool에서 False가 True가 될까?
다음 정의는 얼핏 자연스러워 보입니다.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--flag", type=bool)
args = parser.parse_args()
print(args.flag)
하지만 --flag False를 실행해도 True가 나올 수 있습니다. 명령줄에서 받은 False는 파서에 문자열 'False'로 들어가고, bool()은 빈 문자열을 제외한 문자열을 True로 평가합니다. 따라서 bool('False')는 False라는 의미 변환이 아니라, 값이 비어 있지 않다는 판단입니다.
이 방식은 사람이 기대하는 “문자열 false를 불리언 False로 변환”하는 함수가 아닙니다. type=bool을 불리언 CLI 값의 변환기로 사용하지 않는 것이 안전합니다. 자세한 truth testing 규칙은 Python 공식 bool() 문서에서 확인할 수 있습니다.
옵션의 존재만 필요한 경우: store_true와 store_false
캐시를 끄거나 디버그 로그를 켜는 것처럼 “옵션이 있으면 동작을 바꾼다”는 요구라면 값을 받지 않는 스위치가 알맞습니다.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true", help="디버그 로그를 켭니다")
parser.add_argument("--no-cache", action="store_true", help="캐시를 사용하지 않습니다")
args = parser.parse_args()
if args.debug:
print("debug mode")
store_true는 옵션이 있으면 True, 없으면 False를 저장합니다. 반대로 기본값이 True이고 끄는 옵션이 필요하면 store_false를 사용할 수 있습니다.
parser.add_argument(
"--no-color",
dest="color",
action="store_false",
default=True,
help="색상 출력을 끕니다",
)
이때 --debug True처럼 값을 뒤에 붙이면 안 됩니다. 스위치는 값이 아니라 존재 여부를 읽으므로, 값 전달이 필요하다면 다음 방식으로 요구사항을 다시 정의해야 합니다. 공식 argparse 튜토리얼에도 store_true가 별도 값을 받지 않는 플래그로 설명되어 있습니다.
true·false 값을 직접 받아야 하는 경우
배치 스케줄러나 다른 프로그램이 --enabled true, --enabled false처럼 설정값을 명시적으로 전달해야 한다면 문자열을 제한한 뒤 변환하는 편이 분명합니다.
import argparse
def parse_bool(value: str) -> bool:
normalized = value.lower()
if normalized == "true":
return True
if normalized == "false":
return False
raise argparse.ArgumentTypeError("true 또는 false만 사용할 수 있습니다")
parser = argparse.ArgumentParser()
parser.add_argument("--enabled", type=parse_bool, default=True)
args = parser.parse_args()
print(args.enabled)
이렇게 하면 --enabled maybe 같은 입력을 조용히 True로 바꾸지 않고 즉시 거부할 수 있습니다. 대소문자를 구분하지 않을 필요가 없다면 value.lower()를 빼고 허용 문자열을 더 좁혀도 됩니다. 핵심은 bool()에 맡기지 않고 애플리케이션이 허용할 입력을 직접 정하는 것입니다.
간단한 선택지만 필요하다면 choices=["true", "false"]로 먼저 입력을 제한한 뒤, 프로그램 안에서 명시적으로 불리언으로 바꾸는 방법도 있습니다. choices는 허용하지 않은 값을 오류로 처리하므로 운영 자동화에서 오타를 빠르게 발견하는 데 도움이 됩니다.
Python 버전이 맞고 –foo·–no-foo가 필요할 때
서로 반대인 두 스위치를 한 옵션 이름 아래에서 제공하고 싶다면 BooleanOptionalAction도 선택지입니다.
parser.add_argument(
"--cache",
action=argparse.BooleanOptionalAction,
default=True,
help="캐시 사용 여부",
)
이 액션은 --cache와 --no-cache를 만들고 각각 True와 False를 저장합니다. Python 공식 argparse 문서에서는 이 기능이 Python 3.9에 추가된 것으로 설명하므로, 실행 환경의 Python 버전을 먼저 확인해야 합니다. 구버전 호환이 중요하면 store_true와 store_false를 별도 옵션으로 정의하는 편이 낫습니다. BooleanOptionalAction 공식 문서에서 동작을 확인할 수 있습니다.
어떤 방식을 선택할까?
판단 기준은 전달할 값의 의미입니다.
| 요구사항 | 권장 방식 | 호출 예시 |
|---|---|---|
| 옵션이 있으면 기능을 켠다 | store_true |
--debug |
| 옵션이 있으면 기본 기능을 끈다 | store_false |
--no-color |
| 켜짐·꺼짐을 두 스위치로 표현한다 | BooleanOptionalAction |
--cache, --no-cache |
| 외부 설정이 true·false 값을 직접 보낸다 | 명시적 변환 함수 또는 choices |
--enabled false |
저라면 먼저 “이 옵션은 값인가, 사건인가?”를 묻습니다. --debug처럼 사용자의 실행 의도를 표시하는 것은 스위치이고, 배포 설정 파일이나 스케줄러가 상태를 전달하는 것은 명시적 값에 가깝습니다. 둘을 섞어 type=bool 하나로 처리하면 False 문자열이 True가 되는 문제가 다시 생깁니다.
마무리
argparse에서 --flag False가 True가 되는 것은 argparse가 False를 무시해서가 아니라, False가 비어 있지 않은 문자열로 변환되기 때문입니다. 존재 여부만 필요한 옵션은 store_true·store_false, 두 방향 스위치는 BooleanOptionalAction, 명시적인 값을 받아야 하는 옵션은 허용 문자열을 검증하는 변환 함수를 선택하면 됩니다.
마지막으로 --help 출력에 옵션의 의미와 기본값이 드러나는지 확인해 보십시오. 운영 자동화에서는 호출하는 쪽이 어떤 형식을 보내야 하는지 help 메시지만 보고도 판단할 수 있어야 하며, 잘못된 값은 조용히 변환하기보다 초기에 오류로 알리는 편이 안전합니다.