HOWTO · Python

Python에서 문자열을 부울로 변환

Python에서 bool(), 명시적인 참/거짓 규칙, 리스트 컴프리헨션, map(), strtobool(), json.loads()를 사용해 문자열을 부울로 변환하고, bool("False")가 True를 반환하는 이유와 eval()이 안전하지 않은 이유를 알아보세요.

이 페이지의 내용

파일, 구성, 환경 변수 또는 사용자 입력에서 도착하는 텍스트는 부울 값을 "true" 및 "false"와 같은 문자열로 표기하며, 그 텍스트를 실제 True 또는 False로 다시 변환하는 방법은 처한 상황에 따라 달라집니다. 때로는 텍스트가 존재하는지 여부라는 진리값(truthiness)만 필요합니다. 때로는 텍스트가 미리 나열할 수 있는 고정된 토큰 집합에서 옵니다. 때로는 텍스트가 엄격한 자체 표기를 가진 JSON 리터럴입니다. 잘못된 변환을 선택하는 것은 고전적인 버그 원인입니다. 가장 먼저 떠올리는 도구인 bool()은 텍스트를 전혀 읽지 않기 때문입니다. bool("False")는 True를 반환합니다.

이 튜토리얼은 변환 방법을 그 순서대로 살펴봅니다. 진리값을 위한 bool(), 알려진 토큰을 위한 명시적 규칙, 컴프리헨션 또는 map()으로 리스트에 적용하는 동일한 규칙, 가용성과 반환값의 정확한 범위를 가진 레거시 strtobool(), JSON 텍스트를 위한 json.loads(), 그리고 마지막으로 eval()에 대한 경고입니다.

bool()로 문자열을 부울로 변환하기

bool() 함수는 Python의 진리값 규칙을 문자열에 적용합니다. 비어 있지 않은 모든 문자열은 True이고, 빈 문자열은 False입니다. 문자열의 내용은 아무런 역할을 하지 않습니다.

print(bool("False"))
print(bool("True"))
print(bool(""))
print(bool("hello"))

출력:

True
True
False
True

bool()은 존재 여부가 문제일 때만 사용하세요. 예를 들어 선택적 설정이 제공되었는지 여부입니다. 위의 "False"처럼 텍스트 자체가 의미를 담게 되면, bool()은 조용히 잘못된 답을 주며 대신 명시적 규칙이 필요합니다.

명시적 규칙으로 알려진 텍스트 값 변환하기

입력이 포함할 수 있는 토큰을 알면, 텍스트를 정확히 그 토큰들과 비교하세요. 먼저 대소문자와 주변 공백을 정규화하고, 인식할 수 없는 것에 대해서는 추측 대신 오류를 발생시키세요. 그래야 구성 파일의 오타가 조용히 False가 되지 않고 즉시 드러납니다.

def parse_bool(value):
    text = value.strip().lower()
    if text in ("true", "yes", "on", "1"):
        return True
    if text in ("false", "no", "off", "0"):
        return False
    raise ValueError(f"unrecognized boolean string: {value!r}")


print(parse_bool("True"))
print(parse_bool(" off "))
print(parse_bool("YES"))

출력:

True
False
True

허용 집합 밖에 있는 입력은 오류를 발생시키며, 호출자는 이를 잡아서 보고할 수 있습니다.

try:
    parse_bool("maybe")
except ValueError as error:
    print(error)

출력:

unrecognized boolean string: 'maybe'

두 토큰 집합을 데이터 소스에 맞게 조정하세요. CSV 내보내기에는 "true"와 "false"만 포함될 수 있지만, 명령줄 플래그는 "on"과 "off"도 허용할 수 있습니다. 집합이 명시적이라는 점이 이 접근법을 bool()보다 안전하게 만듭니다.

리스트 컴프리헨션으로 문자열 리스트 변환하기

비교는 이미 부울을 생성하므로, 리스트 컴프리헨션은 각 요소에 비교를 적용하여 전체 리스트를 변환합니다. 이는 이 튜토리얼의 원래 버전 예시를 같은 데이터로 직접 발전시킨 것입니다.

words = ["False", "True", "False", "False", "True"]
flags = [word == "True" for word in words]
print(flags)

출력:

[False, True, False, False, True]

리스트에 여러 토큰 표기가 포함될 수 있을 때는, 단일 비교 대신 이전 섹션의 명시적 변환기를 재사용하세요.

flags = [parse_bool(word) for word in ["true", "False", "yes", "off"]]
print(flags)

출력:

[True, False, True, False]

parse_bool을 사용하면 인식할 수 없는 요소가 ValueError를 발생시켜 잘못된 값을 식별합니다. 조용히 False로 결과에 들어가지 않습니다.

리스트 변환의 대안으로 map() 사용하기

map()은 이터러블의 각 요소에 함수를 적용하므로, 하나의 인자를 받는 함수를 전달하면 동일한 변환을 수행합니다. lambda를 사용하면 컴프리헨션을 그대로 반영합니다.

words = ["False", "True", "False", "False", "True"]
flags = list(map(lambda word: word == "True", words))
print(flags)

출력:

[False, True, False, False, True]

결과는 리스트 컴프리헨션과 동일합니다. 여기처럼 변환이 인라인으로 작성될 때는 컴프리헨션을 선호하세요. 변환기가 이미 이름 있는 함수로 존재할 때는 map()이 더 읽기 좋습니다. 예를 들어 list(map(parse_bool, words))는 lambda 없이 명시적 규칙을 각 요소에 적용합니다.

strtobool()을 레거시 또는 의존성에 특화된 선택지로 다루기

오래된 코드는 표준 라이브러리의 distutils.util 모듈에 있는 strtobool()을 자주 사용합니다. 그 범위는 정확하며, 의존하기 전에 중요합니다. 이 함수는 Python 3.11까지 표준 라이브러리의 일부였습니다. distutils는 PEP 632에 따라 Python 3.10에서 사용 중단(deprecated)되었고 Python 3.12에서 제거되었으므로, 현재 인터프리터에서는 setuptools가 자체 복사본을 제공하지 않는 한 가져오기가 실패합니다(아래 참조). Python 3.11 문서는 동작을 정확히 정의합니다. 입력은 소문자로 변환되고, 참 값은 'y', 'yes', 't', 'true', 'on', '1'이며, 거짓 값은 'n', 'no', 'f', 'false', 'off', '0'이고, 그 외의 값은 ValueError를 발생시킵니다. 표준 라이브러리 버전은 부울이 아닌 정수 1과 0을 반환하므로, 실제 부울이 필요할 때는 결과를 bool()로 감싸세요.

from distutils.util import strtobool

print(strtobool("Yes"))
print(bool(strtobool("Yes")))
print(strtobool("off"))

출력:

1
True
0

이 함수는 많은 프로젝트가 이미 의존하는 setuptools 내부에 포함된(vendored) 복사본으로도 남아 있습니다. 동일한 값을 허용하고 동일한 ValueError(예: invalid truth value 'abc')를 발생시키지만, 현재 setuptools 릴리스는 직접 True와 False를 반환합니다 — setuptools 84.2.1로 검증했습니다.

from setuptools._distutils.util import strtobool

print(strtobool("Yes"))

출력:

True

반환 타입과 심지어 모듈의 출처까지도 표준 라이브러리 복사본, setuptools 복사본, 그리고 setuptools가 있을 때 설치하는 distutils 심(shim) 사이에서 다르므로, strtobool()을 레거시 또는 의존성에 특화된 선택지로 취급하세요. 범위가 정해진 런타임에서 이미 그것을 사용하는 코드를 유지보수할 때는 허용할 수 있지만, 새 코드에서는 위의 명시적 규칙 함수에 의존성이 없으며 모든 Python 버전에서 동일하게 동작합니다.

json.loads()로 JSON 부울 리터럴 파싱하기

문자열이 JSON 데이터에서 올 때는 JSON 파서가 변환하게 하세요. JSON은 부울 리터럴을 소문자로만 표기하며, json.loads()는 그것들에 대해 실제 부울을 반환합니다.

import json

print(json.loads("true"))
print(json.loads("false"))

출력:

True
False

Python식 대문자 표기는 유효한 JSON이 아니므로, "True"에 대한 같은 호출은 변환 대신 실패합니다.

import json

json.loads("True")

출력:

json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

json.loads()는 부울만이 아니라 모든 JSON 값을 파싱한다는 점을 유념하세요. 입력이 신뢰할 수 없다면, 결과를 부울로 사용하기 전에 그것이 실제로 부울인지 확인하세요.

eval()이 문자열 변환에 안전하지 않은 이유

eval()은 문자열을 Python 식으로 평가하고 True와 False가 유효한 식이므로, 문자열을 변환하는 것처럼 보입니다.

print(eval("False"))

출력:

False

바로 그것이 문제입니다. eval()은 문자열을 변환하지 않고 실행합니다. "__import__('os').getcwd()"와 같은 값은 거부되지 않고 프로그램의 권한으로 실행되며, 적대적인 값은 무엇이든 실행할 수 있습니다. 파일, 네트워크 또는 사용자로부터의 텍스트를 절대로 eval()에 전달하지 마세요. 문자열이 Python 리터럴 "True"와 "False"임이 보장된다면, ast.literal_eval()은 아무것도 실행하지 않고 그것들을 파싱하며, 식은 거부합니다.

import ast

print(ast.literal_eval("True"))
print(ast.literal_eval("False"))
try:
    ast.literal_eval("__import__('os').getcwd()")
except ValueError:
    print("ValueError")

출력:

True
False
ValueError

실제로는 토큰 텍스트에는 명시적 규칙을, JSON 텍스트에는 json.loads()를, Python 리터럴임이 보장되는 텍스트에만 ast.literal_eval()을 사용하고, eval()은 변환 코드에서 완전히 배제하세요.