파이썬 argparse에서 –flag False가 True가 되는 이유와 불리언 인자 선택법

argparse에서 type=bool로 받은 False가 True가 되는 이유는 False가 불리언 값으로 변환되기 전에 비어 있지 않은 문자열로 처리되기 때문입니다. 옵션의 존재만 표현한다면 store_true 또는 store_false를 쓰고, 사용자가 true·false 값을 직접 입력해야 한다면 허용 문자열을 검증한 뒤 변환하는 방식이 맞습니다. 먼저 이 두 경우를 나누면 자동화 명령의 실행 의도와 기본값을 헷갈리지 않게 설계할 수 있습니다.

왜 type=bool에서 False가 True가 될까요?

명령줄에서 입력한 값은 처음에는 문자열입니다. 따라서 다음 코드에서 value에 들어오는 것은 불리언 False가 아니라 문자열 'False'입니다.

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--flag", type=bool, default=False)
args = parser.parse_args()

print(args.flag)

Python의 bool()은 false로 평가되지 않는 값을 True로 변환합니다. 비어 있지 않은 문자열 'False'는 false가 아니므로 bool('False')의 결과는 True입니다. 문자열의 뜻을 읽어 False로 바꾸는 기능이 bool() 안에 있는 것은 아닙니다.

운영 중인 자동화 명령에서 --flag False가 켜지는 현상을 만났다면, 먼저 type=bool을 제거하고 이 옵션의 의미부터 확인하는 편이 좋습니다. 옵션이 “이름이 있으면 켠다”인지, 아니면 “사용자가 값을 입력한다”인지에 따라 정의가 달라집니다.

존재 여부만 필요한 스위치라면 store_true

--verbose, --dry-run처럼 옵션이 명령줄에 나타났는지만 알면 되는 경우에는 값 자체를 받지 않는 플래그로 정의합니다.

import argparse

parser = argparse.ArgumentParser()
parser.add_argument(
    "--dry-run",
    action="store_true",
    help="변경 없이 실행 계획만 표시합니다.",
)
args = parser.parse_args()

if args.dry_run:
    print("dry-run mode")

--dry-run이 없으면 기본값은 False이고, 옵션을 넣으면 True가 됩니다. 이 정의에서 --dry-run False는 “False라는 값을 받는 문법”이 아닙니다. 플래그가 값을 소비하지 않으므로 별도의 인자가 남아 오류가 납니다. 호출하는 사람이 값을 입력할 필요가 없다는 점을 help 메시지에도 분명히 적어 두는 것이 좋습니다.

반대로 기본적으로 켜져 있고 옵션이 있으면 끄는 형태라면 store_false를 사용합니다.

parser.add_argument(
    "--no-cache",
    dest="use_cache",
    action="store_false",
    help="캐시를 사용하지 않습니다.",
)

이때 --no-cache가 없을 때 use_cache는 True이고, 옵션을 넣으면 False가 됩니다. 이름과 저장되는 변수의 의미가 서로 어긋나지 않도록 dest와 help 문장을 함께 확인해야 합니다.

true·false 값을 직접 받아야 한다면 문자열을 검증합니다

설정 파일이나 배치 실행 환경처럼 호출자가 명시적으로 값을 전달해야 한다면, 허용할 표현을 정하고 그 안에서만 변환하는 함수를 사용할 수 있습니다.

import argparse


def parse_bool(value: str) -> bool:
    normalized = value.strip().lower()
    if normalized in {"true", "yes", "1"}:
        return True
    if normalized in {"false", "no", "0"}:
        return False
    raise argparse.ArgumentTypeError(
        "true, false, yes, no, 1, 0 중 하나를 입력하세요."
    )


parser = argparse.ArgumentParser()
parser.add_argument(
    "--enabled",
    type=parse_bool,
    default=True,
    metavar="BOOL",
    help="기능 사용 여부를 true 또는 false로 지정합니다.",
)
args = parser.parse_args()

이 방식은 False라는 문자열의 의미를 애플리케이션이 직접 정의한다는 점이 핵심입니다. 허용 목록을 좁게 유지하면 t, f처럼 팀원이 해석하기 어려운 입력을 실수로 받아들이는 일을 줄일 수 있습니다. 허용 목록을 더 단순하게 만들고 싶다면 true와 false만 남겨도 됩니다.

choices를 함께 쓰는 방법도 있습니다. 다만 choices는 허용된 값을 확인하는 기능이지 문자열을 불리언으로 바꾸는 기능은 아니므로, 변환 함수와 함께 사용해야 합니다.

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.add_argument(
    "--enabled",
    type=parse_bool,
    choices=[True, False],
    default=False,
)

argparse는 type 변환 뒤에 choices 검사를 수행하므로 이 조합에서는 검사 대상이 불리언 값입니다. 단순히 choices=["true", "false"]만 지정하면 값은 문자열로 남습니다. 이후 코드가 실제 불리언을 기대한다면 이 차이를 놓치지 않아야 합니다.

어떤 방식을 선택할지 확인하는 순서

운영용 Python 자동화 CLI를 추가할 때는 다음 순서로 판단하면 됩니다.

  1. 옵션이 명령줄에 있느냐만 중요하면 store_true 또는 store_false를 선택합니다.
  2. --enabled False처럼 값이 꼭 필요하면 변환 함수를 만들고 허용 입력을 문서화합니다.
  3. 기본값이 실제 업무의 안전한 방향인지 확인합니다. 예를 들어 실행·전송·삭제처럼 영향이 있는 동작은 기본값과 옵션 이름을 함께 읽었을 때 오해가 없어야 합니다.
  4. --help에서 값의 형식과 기본 동작이 드러나는지 확인합니다.

저라면 처음에는 옵션의 이름보다 호출 문장을 소리 내어 읽어 봅니다. --dry-run은 “이 옵션이 있으면 미리 보기”라는 스위치에 가깝고, --enabled false는 “상태 값을 전달”하는 옵션입니다. 두 문장이 다르다면 코드의 action과 type도 달라져야 합니다.

정리

argparse에서 type=bool은 문자열의 단어 뜻을 해석하지 않습니다. 비어 있지 않은 'False' 문자열이 True가 되는 것은 Python의 일반적인 truth testing 결과입니다.

따라서 옵션의 존재만 표현할 때는 store_true·store_false를 사용하고, 값을 직접 받을 때는 허용 문자열을 검증하는 변환 함수를 두는 것이 안전합니다. 마지막으로 기본값, --help 설명, 실제 호출 문장을 함께 점검하면 자동화 작업이 의도와 반대로 켜지는 문제를 줄일 수 있습니다.

참고 자료

Leave a Comment