목차
- 1. 동적 언어와 정적 언어
- 2. 동적 언어의 장단점
- 3. 타입 어노테이션이란?
- 4. 변수에 타입 지정하기
- 5. 함수에 타입 지정하기
- 6. 리스트와 딕셔너리 타입 지정하기
- 7. typing 모듈
- 8. Optional과 Union
- 9. Callable과 Any
- 10. mypy를 이용한 타입 검사
파이썬은 동적 타입 언어(Dynamically Typed Language) 이다.
다음 코드를 살펴보자.
a = 1
print(type(a))
실행 결과
<class 'int'>
현재 a에는 정수 1이 저장되어 있으므로 자료형은 int이다.
이번에는 같은 변수에 문자열을 저장해 보자.
a = "1"
print(type(a))
실행 결과
<class 'str'>
처음에는
a → int
였지만 새로운 값을 대입하면서
a → str
처럼 사용할 수 있다.
즉 파이썬에서는 프로그램 실행 중에도 변수에 서로 다른 타입의 값을 대입할 수 있다.
a = 10
a = "Python"
a = [1, 2, 3]
이것이 파이썬을 동적 타입 언어라고 부르는 이유이다.
반대로 자바와 같은 언어는 정적 타입 언어(Statically Typed Language) 이다.
예를 들어 자바에서는
int a = 1;
처럼 변수의 타입을 미리 지정한다.
여기서 a는 int형이므로
a = "1";
처럼 문자열을 저장할 수 없다.
즉,
정적 타입 언어
변수 생성
↓
타입 지정
↓
지정된 타입의 값만 사용
하는 방식이다.
| 구분 | 동적 타입 | 정적 타입 |
|---|---|---|
| 대표 언어 | Python | Java |
| 타입 결정 | 실행 중 | 미리 지정 |
| 타입 변경 | 비교적 자유로움 | 제한됨 |
| 코드 작성 | 빠르고 유연함 | 타입이 명확함 |
| 타입 오류 발견 | 실행 중 발견 가능 | 실행 전 발견 가능 |
파이썬과 같은 동적 타입 언어는 타입을 일일이 지정하지 않아도 된다.
예를 들어
name = "pingu"
age = 20
scores = [90, 80, 100]
처럼 바로 값을 저장할 수 있다.
따라서
이라는 장점이 있다.
하지만 프로그램의 규모가 커지면 문제가 발생할 수 있다.
다음 함수를 보자.
def add(a, b):
return a + b
코드만 보면
a에는 어떤 값이 들어와야 하지?
b는 숫자인가?
문자열인가?
결과는 어떤 자료형이지?
를 바로 알기 어렵다.
특히 여러 사람이 함께 개발하는 큰 프로젝트에서는 타입을 잘못 사용해 버그가 발생할 가능성이 커질 수 있다.
이러한 문제를 보완하기 위해 사용하는 것이
타입 어노테이션(Type Annotation)
이다.
타입 어노테이션은 변수나 함수에
어떤 타입을 사용할 것인지 표시해 주는 기능
이다.
예를 들어
num: int = 1
이라고 작성한다.
여기서
: int
부분이 타입 어노테이션이다.
뜻은
num 변수에는
int 타입의 값을 사용할 예정이다.
라고 알려 주는 것이다.
이 부분이 매우 중요하다.
num: int = 1
이라고 작성했다고 해서 파이썬이 반드시 int만 사용하도록 강제하는 것은 아니다.
예를 들어
num: int = 1
num = "Python"
print(num)
실행 결과
Python
정상적으로 실행된다.
즉,
타입 어노테이션
↓
타입 강제 X
↓
타입에 대한 힌트 O
이다.
그래서 Type Hint라고도 부른다.
기본적인 변수 타입은 다음과 같이 지정할 수 있다.
num: int = 1
name: str = "홍길동"
height: float = 175.5
is_student: bool = True
각각 의미는 다음과 같다.
| 코드 | 타입 |
|---|---|
num: int | 정수 |
name: str | 문자열 |
height: float | 실수 |
is_student: bool | 참/거짓 |
자주 사용하는 타입은 다음과 같다.
int → 정수
float → 실수
str → 문자열
bool → 불
list → 리스트
tuple → 튜플
dict → 딕셔너리
set → 집합
예를 들어
number: int = 10
name: str = "Python"
numbers: list = [1, 2, 3]
처럼 사용할 수 있다.
타입 어노테이션은 함수에서도 많이 사용한다.
다음 함수를 살펴보자.
def add(a: int, b: int) -> int:
return a + b
하나씩 보면
a: int
은
a는 int 타입
이라는 의미이다.
b: int
은
b는 int 타입
이라는 의미이다.
그리고
-> int
는
이 함수는 int 타입을 반환한다.
라는 뜻이다.
기본 구조는 다음과 같다.
def 함수이름(매개변수: 타입) -> 반환타입:
...
예를 들어
def greet(name: str) -> str:
return f"안녕하세요, {name}님!"
뜻은
name
↓
str 입력
greet()
↓
str 반환
이다.
다음 코드를 보자.
def add(a, b):
return a + b
이 함수만 보면
a = ?
b = ?
return = ?
를 바로 알기 어렵다.
하지만
def add(a: int, b: int) -> int:
return a + b
라고 작성하면
입력
a → int
b → int
출력
return → int
이라는 사실을 쉽게 알 수 있다.
즉 타입 어노테이션은 코드의 가독성을 높여 준다.
단순히
numbers: list
라고 작성하면 리스트라는 사실만 알 수 있다.
하지만 리스트 안에 어떤 타입이 들어 있는지도 지정할 수 있다.
numbers: list[int] = [1, 2, 3]
뜻은
numbers
↓
list
↓
리스트 내부 값은 int
이다.
numbers: list[int] = [1, 2, 3]
int를 저장하는 리스트
user_info: dict[str, int] = {
"age": 30
}
뜻은
key → str
value → int
이다.
coordinates: tuple[float, float] = (3.5, 7.2)
뜻은
첫 번째 값 → float
두 번째 값 → float
이다.
numbers: set[int] = {1, 2, 3}
int 값을 저장하는 집합이라는 의미이다.
다음 두 코드를 비교해 보자.
users: list
이 코드만 보면
리스트인 것은 알겠는데
안에 무엇이 들어 있지?
라는 의문이 생긴다.
하지만
users: list[str]
라고 작성하면
문자열을 저장하는 리스트
라는 사실을 바로 알 수 있다.
따라서 코드를 읽고 이해하기 쉬워진다.
타입 어노테이션을 보다 다양하게 사용하기 위해
typing
모듈을 사용할 수 있다.
과거 파이썬에서는 다음과 같은 형태를 많이 사용했다.
from typing import List, Dict, Tuple
numbers: List[int] = [1, 2, 3]
user_info: Dict[str, int] = {
"age": 30
}
Python 3.9 이상에서는
List[int]
Dict[str, int]
Tuple[int, int]
대신 기본 자료형을 직접 사용할 수 있다.
list[int]
dict[str, int]
tuple[int, int]
따라서 현재는 다음처럼 작성하는 방식이 일반적이다.
numbers: list[int] = [1, 2, 3]
user_info: dict[str, int] = {
"age": 30
}
즉
과거
List[int]
Dict[str, int]
Tuple[int, int]
에서
현재
list[int]
dict[str, int]
tuple[int, int]
형태로 사용할 수 있다.
기본 자료형은
list[int]
dict[str, int]
처럼 사용할 수 있지만 좀 더 특별한 타입을 표현할 때는 typing 모듈을 사용할 수 있다.
대표적으로
Optional
Union
Callable
Any
등이 있다.
다음 상황을 생각해 보자.
사용자 이름을 저장하는 변수인데 사용자 이름이 없을 수도 있다.
즉
문자열일 수도 있음
또는
None일 수도 있음
이다.
이럴 때 Optional을 사용할 수 있다.
from typing import Optional
user_name: Optional[str] = None
Optional[str]은
str 또는 None
이라는 의미이다.
user_name: Optional[str] = "pingu"
가능하다.
user_name: Optional[str] = None
이것도 가능하다.
따라서
Optional[str]
┌─ str
│
└─ None
이라고 생각하면 된다.
하나의 값에 여러 타입이 들어올 수 있다고 표시할 때 Union을 사용한다.
from typing import Union
user_id: Union[int, str]
뜻은
user_id
↓
int 또는 str
이다.
따라서
user_id = 100
도 가능하고
user_id = "pingu"
도 가능하다.
사실
Optional[str]
은 개념적으로
Union[str, None]
과 같은 의미라고 이해하면 된다.
즉
Optional[str]
str 또는 None
이다.
최근 파이썬에서는 Union 대신 |를 사용할 수도 있다.
예를 들어
user_id: int | str
은
int 또는 str
을 의미한다.
그리고
user_name: str | None
은
str 또는 None
이라는 의미이다.
따라서 다음처럼 대응해서 이해하면 된다.
Optional[str]
↓
str | None
Union[int, str]
↓
int | str
함수 자체의 타입도 표현할 수 있다.
이때 Callable을 사용할 수 있다.
from typing import Callable
예를 들어
def process_data(
callback: Callable[[int], str]
) -> str:
return callback(42)
여기서
Callable[[int], str]
은
int를 입력받고
↓
str을 반환하는 함수
라는 의미이다.
Callable[[매개변수 타입], 반환 타입]
예를 들어
Callable[[int, int], int]
은
int, int
↓
함수
↓
int 반환
이라는 의미이다.
어떤 타입이 들어와도 괜찮다고 표현할 때는
Any
를 사용할 수 있다.
from typing import Any
data: Any
다음과 같은 값이 모두 가능하다.
data = 10
data = "Python"
data = [1, 2, 3]
data = {"name": "pingu"}
즉
Any
↓
어떤 타입이든 허용
이라는 의미이다.
예를 들어 사용자를 찾는 함수를 만들어 보자.
from typing import Optional
def find_user(user_id: int) -> Optional[dict[str, str]]:
if user_id > 0:
return {
"name": "홍길동",
"email": "hong@example.com"
}
return None
반환 타입을 보면
Optional[dict[str, str]]
이다.
즉 이 함수의 결과는
사용자를 찾음
↓
dict[str, str]
사용자를 못 찾음
↓
None
중 하나이다.
다음 코드를 살펴보자.
def add(a: int, b: int) -> int:
return a + b
a와 b 모두 int라고 지정했다.
그런데 다음처럼 실행해 보자.
result = add(3, 3.4)
print(result)
실행 결과
6.4
정상적으로 실행된다.
왜 그럴까?
파이썬의 타입 어노테이션은
타입 강제 기능
이 아니라
타입에 대한 정보와 힌트
이기 때문이다.
다음 코드에서
def add(a: int, b: int) -> int:
return a + b
분명 b는
int
라고 표시되어 있다.
하지만
add(3, 3.4)
처럼 float을 사용해도 파이썬은 타입 어노테이션만 보고 실행을 막지 않는다.
즉
타입 어노테이션
↓
사람 / IDE / 검사 도구에게 정보 제공
↓
파이썬 실행 자체를 강제로 제한하지 않음
이다.
그렇다면 타입이 제대로 사용되고 있는지 어떻게 검사할까?
대표적인 도구가
mypy
이다.
mypy는 파이썬 코드를 실행하기 전에 타입 어노테이션을 분석하는 정적 타입 검사 도구이다.
mypy는 파이썬 표준 라이브러리가 아니므로 설치해야 한다.
pip install mypy
설치 후 파이썬 파일을 검사할 수 있다.
예를 들어
typing_sample.py
파일을 만들었다고 해보자.
def add(a: int, b: int) -> int:
return a + b
result = add(3, 3.4)
print(result)
터미널에서 실행한다.
mypy typing_sample.py
그러면 mypy는
b는 int를 사용한다고 했는데
float 값이 전달되었다.
라는 문제를 발견할 수 있다.
즉
add(3, 3.4)
에서
3 → int O
3.4 → float X
가 되는 것이다.
다음처럼 수정한다.
def add(a: int, b: int) -> int:
return a + b
result = add(3, 4)
print(result)
다시 검사하면
mypy typing_sample.py
타입 문제가 없다는 결과를 확인할 수 있다.
이 부분을 확실히 구분해야 한다.
python typing_sample.py
실제 프로그램 실행
mypy typing_sample.py
프로그램을 실행하기 전
타입 어노테이션 검사
즉
코드 작성
↓
mypy
↓
타입 검사
↓
문제 수정
↓
Python 실행
과 같은 흐름으로 사용할 수 있다.
타입 어노테이션의 가장 큰 목적은 코드를 이해하기 쉽게 만드는 것이다.
다음 함수를 비교해 보자.
def get_user(user_id):
...
이것만 보면
user_id가 숫자인가?
문자열인가?
무엇을 반환하지?
를 바로 알기 어렵다.
def get_user(user_id: int) -> dict[str, str]:
...
이제 코드만 보고도
입력
↓
user_id = int
출력
↓
dict[str, str]
이라는 사실을 알 수 있다.
혼자 작성한 작은 프로그램이라면 자신이 작성한 코드를 기억하고 있을 수 있다.
하지만 규모가 커지면
개발자 A
개발자 B
개발자 C
개발자 D
...
여러 사람이 하나의 코드를 함께 사용한다.
이때 타입 어노테이션이 있다면 함수가 어떤 값을 요구하는지 쉽게 확인할 수 있다.
def login(
username: str,
password: str
) -> bool:
...
코드만 보고도
username → str
password → str
return → bool
이라는 구조를 알 수 있다.
VSCode나 PyCharm과 같은 개발 도구는 타입 어노테이션을 이용하여
등을 제공할 수 있다.
따라서 타입 어노테이션은 단순한 주석보다 훨씬 유용하다.
변수
변수명: 타입 = 값
예
age: int = 20
함수 매개변수
def 함수(매개변수: 타입):
예
def hello(name: str):
함수 반환값
def 함수() -> 타입:
예
def get_age() -> int:
매개변수와 반환값
def add(a: int, b: int) -> int:
return a + b
numbers: list[int]
user: dict[str, str]
user_id: int | str
name: str | None
from typing import Any
data: Any
from typing import Callable
callback: Callable[[int], str]
파이썬은 변수에 서로 다른 타입의 값을 대입할 수 있다.
a = 1
a = "Python"
변수나 함수가 어떤 타입을 사용할 것인지 알려 주는 타입 힌트이다.
age: int = 20
def add(a: int, b: int) -> int:
return a + b
a → int
b → int
return → int
numbers: list[int]
user: dict[str, str]
position: tuple[float, float]
numbers: set[int]
from typing import Optional
name: Optional[str]
str 또는 None
from typing import Union
user_id: Union[int, str]
int 또는 str
최근 파이썬에서는 다음처럼 표현할 수도 있다.
user_id: int | str
name: str | None
from typing import Callable
callback: Callable[[int], str]
int 입력
↓
함수
↓
str 반환
from typing import Any
data: Any
어떤 타입이든 사용할 수 있다는 의미이다.
타입 어노테이션이 올바르게 사용되었는지 검사하는 도구이다.
pip install mypy
검사
mypy typing_sample.py
타입 어노테이션을 작성했다고 해서 파이썬이 타입을 강제로 제한하는 것은 아니다.
def add(a: int, b: int) -> int:
return a + b
여기서
add(3, 3.4)
를 실행해도 파이썬 자체는 실행할 수 있다.
따라서
Type Annotation
↓
타입을 강제한다 X
Type Annotation
↓
어떤 타입을 사용할지 알려 주는 힌트 O
이다.
실제로 타입이 올바르게 사용되고 있는지 확인하려면
IDE
mypy
와 같은 도구를 함께 사용한다.
Python
↓
동적 타입 언어
↓
타입 사용이 자유로움
↓
코드가 커지면 타입 파악이 어려울 수 있음
↓
Type Annotation
↓
변수와 함수에 타입 정보 표시
↓
코드 이해가 쉬워짐
↓
mypy
↓
타입 오류를 실행 전에 검사
타입 어노테이션은 파이썬을 정적 타입 언어로 만드는 기능이 아니라, 동적 타입의 장점을 유지하면서 코드에 타입 정보를 추가하는 기능이라고 이해하면 된다.