모던 파이썬 기초 #2 변수/기본 타입과 타입 힌트
#1 시작과 uv 셋업에서 만든 프로젝트 위에서 그대로 이어집니다. 이번 글의 주제는 변수와 기본 타입, 그리고 타입 힌트입니다.
파이썬은 동적 타입 언어입니다. 변수에 타입을 적지 않아도 됩니다. 그런데도 모던 파이썬에서는 처음부터 타입을 적는 흐름이 표준이 됐습니다. 왜 그래야 하고, 어떻게 적는지를 정리합니다.
변수 — 선언이 따로 없다 #
다른 언어와 가장 크게 다른 부분입니다. let, var, int x 같은 키워드가 없습니다. 그냥 이름에 값을 대입하면 그게 변수입니다.
name = "커티스"
age = 30
height = 175.5
is_admin = True이름은 snake_case가 관례입니다. JavaScript의 camelCase와 다른 부분이라 처음에 어색합니다. 상수처럼 쓸 값은 대문자 + 언더스코어 (MAX_RETRY = 3).
기본 타입 — 4 + 1 #
가장 자주 쓰는 다섯 개부터 잡으면 됩니다.
i: int = 42
f: float = 3.14
s: str = "hello"
b: bool = True
n: None = None여기서 : int 부분이 타입 힌트입니다. 변수 이름 뒤에 콜론과 타입을 적습니다. 동작에는 영향이 없지만, 도구(IDE/타입 체커)가 읽어서 검증과 자동완성을 해 줍니다.
동적 타이핑인데 왜 타입을 적나? #
타입 힌트는 런타임에는 그냥 무시됩니다. 다음 코드는 정상 실행됩니다.
x: int = "this is a string"
print(x) # this is a string그런데도 다 적는 이유는:
- 에디터 자동완성과 정의 점프가 정확해집니다 — VS Code, PyCharm 모두 타입을 봅니다
- 타입 체커(mypy / pyright / Pyrefly)가 문제를 컴파일 타임에 잡아줍니다
- 남이 코드를 읽을 때의 문서가 됩니다 — 함수 시그니처만 보고 무엇을 받고 무엇을 주는지 알 수 있습니다
- 리팩터링 안전망 — 함수 시그니처를 바꾸면 어디가 깨지는지 보입니다
오래된 파이썬 코드는 타입이 없습니다. 새 코드는 가능한 한 적는 게 모던 파이썬의 약속입니다.
타입 힌트 — 변수, 함수, 컬렉션 #
변수 #
count: int = 0
name: str
name = "curtis" # 선언만 먼저, 값은 나중에name: str처럼 값 없이 타입만 적는 것도 가능합니다. 클래스 필드 선언에서 자주 쓰입니다.
함수 #
def add(a: int, b: int) -> int:
return a + b
def greet(name: str) -> None:
print(f"hi, {name}")매개변수 뒤에 : 타입, 반환은 -> 타입. 반환이 없으면 -> None. 이걸 안 적는 함수는 IDE가 “뭘 반환하는지 모름” 상태가 돼서 그 함수를 쓰는 코드의 자동완성도 같이 흐려집니다.
컬렉션 — 빌트인 제네릭 #
리스트, 딕셔너리 같은 컬렉션은 원소 타입까지 같이 적습니다.
nums: list[int] = [1, 2, 3]
names: list[str] = ["a", "b"]
ages: dict[str, int] = {"curtis": 30, "smith": 25}
unique: set[str] = {"a", "b"}
point: tuple[float, float] = (1.0, 2.0)list[int]처럼 빌트인 타입에 그대로 []를 쓰는 문법은 Python 3.9부터 지원됩니다. 그 이전에는 from typing import List 후 List[int] 였습니다. 옛 코드에서는 아직 보이지만, 새 코드는 빌트인 쪽으로 통일하면 됩니다.
from typing import List, Dict
nums: List[int] = [1, 2, 3]
ages: Dict[str, int] = {"curtis": 30}None과 옵션 타입 — int | None
#
값이 있을 수도, 없을 수도 있는 경우를 표현할 때.
def find_user(id: int) -> str | None:
if id == 1:
return "curtis"
return Nonestr | None은 Python 3.10부터 지원되는 union 단축 문법입니다. 그 이전에는 Optional[str] 또는 Union[str, None]을 typing에서 import 해야 했습니다.
from typing import Optional, Union
def find_user(id: int) -> Optional[str]: ...
def parse(value: str) -> Union[int, float, None]: ...새 코드는 무조건 |입니다. 짧고, 추가 import가 없습니다.
def parse(value: str) -> int | float | None:
try:
return int(value)
except ValueError:
try:
return float(value)
except ValueError:
return None숫자 — int와 float #
파이썬의 int는 임의 정밀도입니다. 64비트 한계가 없습니다.
big = 10 ** 100
print(big)
# 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000float는 IEEE 754 double입니다. 다른 언어와 같은 부동소수점 함정이 그대로 있습니다.
print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False금융이나 정확한 소수가 필요하면 decimal.Decimal을 씁니다.
from decimal import Decimal
print(Decimal("0.1") + Decimal("0.2")) # 0.3숫자 리터럴 작은 팁 #
읽기 좋게 언더스코어를 끼워 넣을 수 있습니다 (3.6+).
ONE_MILLION = 1_000_000
HEX = 0xFF_FF
BIN = 0b_1010_1010문자열 — str과 f-string
#
작은따옴표/큰따옴표 차이는 없습니다. 한 프로젝트에서 일관되게 쓰면 됩니다 (보통 ").
s1 = "hello"
s2 = 'world'
s3 = """여러 줄
문자열"""값을 끼워 넣을 때는 f-string을 씁니다.
name = "curtis"
age = 30
print(f"hi, {name}! you are {age} years old.")
print(f"next year you'll be {age + 1}.")
print(f"{name = }") # name = 'curtis' (디버깅에 유용)f"{변수 = }" 형태는 변수 이름과 값을 같이 출력합니다. 디버깅용 print에 정말 자주 쓰입니다.
t-string — Python 3.14의 새 문법 #
Python 3.14에서 PEP 750으로 t-string이 추가됐습니다. f-string과 모양이 비슷하지만, 보간이 즉시 일어나지 않고 Template 객체로 남는 것이 차이입니다.
from string.templatelib import Template
name = "curtis"
tpl: Template = t"hi, {name}!"
# Template 객체. 아직 문자열로 합쳐지지 않은 상태.
# 안전하게 처리하는 함수에 넘김
def render_html(template: Template) -> str: ...
html = render_html(t"<a href='{user_url}'>{user_name}</a>")언제 쓰나? 사용자 입력을 안전하게 처리해야 하는 경우 — SQL, HTML, shell. f-string은 보간된 결과가 곧장 문자열이라 escape를 잊기 쉬운데, t-string은 라이브러리가 escape 책임을 가져갑니다. 당장 입문 단계에서는 “f-string만 알면 충분”, 다만 라이브러리 코드에서 t-string을 보게 되면 이게 그것이라고 인지하면 됩니다.
bool과 진리값
#
True, False 둘이고, bool은 int의 서브 타입입니다.
print(True + True) # 2
print(isinstance(True, int)) # True흥미롭지만, bool 위치에 int를 섞어 쓰는 건 권장되지 않습니다. 명시적으로 True/False를 쓰세요.
진리값 (truthy / falsy) #
빈 컨테이너와 0은 거짓, 나머지는 참입니다.
if not []: print("empty list is falsy")
if not "": print("empty str is falsy")
if not 0: print("zero is falsy")
if not None: print("None is falsy")이걸 이용해 if not items: 같은 표현이 자주 쓰입니다. JavaScript의 if (!arr.length)보다 짧습니다.
타입 변환 — 명시적으로 #
파이썬은 암묵적 형 변환을 거의 하지 않습니다. 정수와 문자열을 더하면 에러가 납니다.
n = 42
s = "answer: " + str(n) # str(n)으로 변환해야 함
# "answer: " + n → TypeError
age = int(input("나이: ")) # input은 str 반환 → int로 변환자주 쓰는 변환:
str(42) # '42'
int("42") # 42
int("42", 16) # 66 (16진수 해석)
float("3.14") # 3.14
bool(0) # False
bool("any") # True (빈 문자열만 False)
list("abc") # ['a', 'b', 'c']타입 별칭 — type 키워드
#
같은 타입 모양이 반복되면 이름을 붙입니다.
type UserId = int
type UserName = str
type UserMap = dict[UserId, UserName]
def get_user(id: UserId) -> UserName | None: ...type 문법은 Python 3.12에서 추가됐습니다. 그 이전에는 다음과 같이 적었습니다.
UserId = int # 그냥 변수 — 별칭처럼 동작
# 또는
from typing import TypeAlias
UserId: TypeAlias = int새 코드는 type으로 통일하면 됩니다.
타입 체커 — mypy / pyright #
타입 힌트는 런타임에 검증되지 않으므로, 별도 도구가 정적 검증을 합니다. 두 진영이 있습니다.
- mypy — 가장 오래된 표준. 파이썬 진영 도구.
uv add --dev mypy - pyright / Pylance — Microsoft 개발. 빠르고 VS Code와 통합 좋음.
uv add --dev pyright - Pyrefly — Meta 신작 (2026 현재 베타). 더 빠른 차세대 후보
새 프로젝트는 pyright부터 시작하는 게 무난합니다. VS Code에서 Python 확장을 설치하면 Pylance로 자동 동작해서 별도 설정도 거의 필요 없습니다.
uv add --dev pyright
uv run pyright .작은 예제로 동작을 보면:
def add(a: int, b: int) -> int:
return a + b
result = add("hello", 1) # ✗ str은 int가 아님$ uv run pyright check.py
check.py:4:18 - error: Argument of type "Literal['hello']" cannot be assigned to parameter "a" of type "int"런타임 전에 잡힙니다. 처음에는 잔소리처럼 느껴질 수 있는데, 한 달쯤 쓰면 “이걸 어떻게 안 쓰고 살았지” 합니다.
정리 #
이번 글에서 잡은 것:
- 파이썬은 변수 선언이 따로 없음 — 대입 = 선언
- 기본 타입 5개:
int,float,str,bool,None - 타입 힌트는 런타임에 무시됨, 그래도 항상 적는 게 모던 파이썬의 약속
- 컬렉션은 빌트인 제네릭으로:
list[int],dict[str, int],tuple[int, str] - 옵션은
T | None(3.10+), 옛Optional은 새 코드에 안 씀 - f-string
f"{var}"와 디버깅용f"{var = }", t-string은 안전한 보간용 int는 임의 정밀도,float는 IEEE 754, 정확도 필요하면Decimal- 타입 별칭은
type Name = ...(3.12+) - 정적 타입 검증은 mypy 또는 pyright
다음 글(#3 제어 흐름)에서는 흐름 제어 — if, while, for, 그리고 3.10에서 들어온 **match-case**를 다룹니다. 다른 언어의 switch와 결이 어떻게 다른지가 핵심입니다.