Skip to content
This page has been auto-translated and may contain errors.View in English

변수와 타입

모든 프로그램은 무언가를 기억해야 합니다. 퀴즈는 플레이어의 이름이 필요하고, 게임은 현재 점수가 필요하며, 날씨 스크립트는 확인하려는 도시가 필요합니다. Python은 이를 위해 변수를 사용합니다. 변수는 값에 붙이는 이름으로, 프로그램 전체에서 해당 값을 사용할 수 있게 해줍니다.

변수는 값을 가리키는 이름이 있는 참조입니다. Python은 =의 오른쪽에 있는 객체에 이름을 바인딩하며, 언제든지 다시 바인딩할 수 있습니다. 타입은 이름이 아닌 값에 존재합니다.

Python에서 변수는 이름 바인딩입니다. 런타임에 객체로 해석되는 네임스페이스 내의 참조입니다. 타입은 객체에 있으며 이름에는 없습니다. 이것이 동적 타이핑입니다. 즉, 동일한 이름이 서로 다른 구문에서 완전히 다른 타입의 객체를 참조할 수 있습니다.

python
player_name = "지민"
score       = 0
city        = "서울"

세 줄. Python이 이제 기억하는 세 가지입니다. 나중에 그 이름 중 하나를 사용하면 Python이 해당 값을 돌려줍니다.

각 줄은 바인딩을 생성합니다. 왼쪽의 이름이 오른쪽의 객체를 참조합니다. Python은 먼저 오른쪽을 평가한 다음 바인딩을 생성합니다.

각 할당은 현재 스코프의 로컬 네임스페이스에 이름을 생성하거나 재바인딩합니다. Python은 바인딩이 적용되기 전에 오른쪽 표현식을 완전히 평가합니다. 이름 자체에는 타입 정보가 없습니다.

값 저장하기

= 기호는 수학 시간에 배운 것과 달라 거의 모든 사람이 처음에 혼란스러워합니다. Python에서 =는 "같다"를 의미하지 않습니다. 이 이름으로 이 값을 저장하라는 의미입니다. 왼쪽에서 오른쪽으로 읽어보세요:

python
city = "서울"

city"서울"을 받습니다. Python에게 이렇게 말하는 겁니다: "서울"을 기억하고 city라고 이름 붙여라.

언제든지 변수의 값을 바꿀 수 있습니다. Python은 가장 최근 값을 사용합니다:

python
score = 0
score = 10   # score는 이제 10
score = 15   # score는 이제 15

=할당입니다. 현재 스코프에서 이름을 객체에 바인딩합니다. 변수를 업데이트하는 표준 약식 표현은 복합 할당입니다:

python
score = 0
score += 10   # 다음과 동일: score = score + 10
score *= 2    # 다음과 동일: score = score * 2

여러 이름을 한 번에 바인딩할 수도 있습니다:

python
x, y, z = 1, 2, 3
a = b = 0        # 둘 다 0으로 시작

할당은 이름을 객체에 바인딩합니다. 값을 컨테이너에 복사하지 않습니다. 두 이름이 같은 객체를 참조할 수 있습니다:

python
a = "hello"
b = a
print(id(a) == id(b))   # True (메모리에서 같은 객체)

b = "world"             # b가 새 객체에 재바인딩됨
print(id(a) == id(b))   # False
print(a)                # 여전히 "hello": b를 재바인딩해도 a에는 영향 없음

id()는 객체의 정체성(CPython에서의 메모리 주소)을 반환합니다. 이름 바인딩과 복사의 차이는 이후 챕터에서 다루는 리스트나 딕셔너리 같은 가변 객체에서 더 중요합니다.

타입 어노테이션은 정적 분석 도구를 위해 예상 타입을 문서화합니다. 런타임에는 아무런 영향이 없습니다:

python
name:  str   = "지민"
score: int   = 0
ratio: float = 0.85

변수 이름 짓기

이름은 여러분이 선택합니다. Python에는 몇 가지 엄격한 규칙이 있고, 커뮤니티는 처음부터 따를 가치가 있는 관례를 따릅니다. 명확한 이름은 몇 주 후에도 코드를 읽기 쉽게 만듭니다. 모호한 이름은 고통을 유발합니다.

Python은 소수의 식별자 구문 규칙을 강제합니다. 그 외에는 PEP 8 관례가 모든 Python 코드베이스와 도구에서 사실상의 표준입니다.

Python의 식별자 구문 규칙은 최소화되어 있습니다. PEP 8 관례는 인터프리터가 강제하지 않지만, 린터, 타입 체커, 그리고 모든 전문적인 Python 코드베이스에서 당연한 것으로 여겨집니다. 이를 벗어나면 마찰이 생깁니다.

Python이 강제하는 규칙:

  • 문자, 숫자, 밑줄만 사용 가능. 공백이나 하이픈 불가.
  • 문자나 밑줄로 시작해야 하며, 숫자로 시작 불가
  • 대소문자 구분: score, Score, SCORE는 세 개의 별개 변수

모든 사람이 따르는 관례 (PEP 8):

대상스타일예시
변수와 함수snake_caseuser_name, total_price
상수UPPER_SNAKE_CASEMAX_RETRIES, BASE_URL
클래스PascalCaseUserAccount, DataLoader
python
# 명확한 이름, 한눈에 읽기 쉬움
user_name    = "지민"
total_price  = 49.99
is_logged_in = True
MAX_RETRIES  = 3

# 한 시간 안에 후회하게 될 이름들
x   = "지민"
tp  = 49.99
b   = True

초반에 알아둘 가치 있는 한 가지 함정: list, input, type, print와 같은 Python 내장 함수 이름으로 변수를 만들지 마세요. Python은 허용하지만, 해당 스코프의 나머지 부분에서 내장 함수가 조용히 망가지고 그로 인한 오류는 추적하기 어렵습니다.

Python의 내장 함수를 가리지 마세요. list, type, input, print, str에 할당하면 아무런 경고 없이 해당 스코프의 나머지 부분에서 내장 함수를 덮어씁니다. 이는 찾기 어려울 수 있는 조용한 버그입니다.

UPPER_SNAKE_CASE는 관례일 뿐, 강제되지 않습니다. Python은 나중에 MAX_RETRIES = 99로 재할당하는 것을 막지 않습니다. 이는 다른 개발자에게 보내는 신호일 뿐 그 이상은 아닙니다.

내장 함수를 가리면 일반적인 이름 조회 순서에서 내장 함수보다 우선하는 로컬 바인딩이 생성됩니다. builtins.print 등을 통해 내장 함수에 접근할 수 있지만, 가리는 이름이 일반 사용에서 그것을 숨깁니다. UPPER_SNAKE_CASE는 언어 수준의 강제가 없습니다. 도구가 확인할 수 있는 진정한 불변성 보장을 위해서는 어노테이션의 typing.Final이 표준 방법입니다.

저장할 수 있는 것

Python에는 거의 모든 프로그램에서 사용하게 될 네 가지 타입이 있습니다. Python은 값을 작성하는 방식에서 어떤 타입인지 파악합니다. 타입을 명시적으로 선언할 필요가 없습니다.

Python은 리터럴 구문에서 타입을 추론합니다. 이 네 가지 타입이 기본적인 값의 공간을 커버하며, 언어의 모든 것이 이 위에 구축됩니다.

Python의 네 가지 기본 타입은 각각 서로 다른 메모리 레이아웃, 정밀도 특성, 연산 의미를 가진 별개의 런타임 객체에 매핑됩니다. 타입은 이름이 아닌 객체에 의해 결정됩니다.

텍스트 (str)

모든 텍스트는 작은따옴표나 큰따옴표 안에 넣습니다. 따옴표는 Python에게 변수 이름이 아닌 리터럴 문자를 의미한다고 알려줍니다. 한번 생성된 문자열은 제자리에서 변경할 수 없습니다. 문자열 챕터에서 문자열로 할 수 있는 모든 것을 다룹니다.

python
player_name = "지민"
city        = "서울"
message     = '게임 오버'

텍스트에 아포스트로피가 포함된 경우, 이스케이프를 피하기 위해 큰따옴표를 사용하세요:

python
note = "It's a great day"
note = 'It\'s a great day'   # 이스케이프를 사용한 동일한 결과

문자열은 작은따옴표나 큰따옴표로 모든 텍스트를 담습니다. 문자열은 불변입니다. 어떤 연산도 문자열을 제자리에서 수정하지 않으며, 모든 변환은 새 문자열을 반환합니다. 이는 성능에 영향을 미칩니다. 루프 안에서 반복적인 +는 매 단계마다 새 문자열 객체를 생성합니다. 문자열 챕터에서 효율적인 대안을 다룹니다.

python
player_name = "지민"
city        = "서울"
note        = "It's a great day"

str은 바이트가 아닌 유니코드 코드 포인트의 불변 시퀀스입니다. len("café")는 5가 아닌 4입니다. 불변성 덕분에 문자열은 해시 가능합니다. 딕셔너리 키와 집합 멤버로 사용할 수 있습니다. CPython은 식별자처럼 생긴 짧은 문자열을 인터닝합니다. 동일한 짧은 리터럴에 할당된 두 변수는 종종 메모리에서 단일 객체를 공유합니다. 두 따옴표 스타일 모두 동일한 객체를 생성합니다.

python
player_name = "지민"
city        = "서울"
note        = "It's a great day"

정수 (int)

정수는 따옴표나 소수점 없이 입력합니다. Python은 이를 **정수(integer)**라고 합니다. 얼마든지 크게 만들 수 있습니다. Python은 특별한 노력 없이 임의로 큰 숫자를 처리합니다.

python
score      = 0
age        = 28
population = 8_100_000_000   # 밑줄은 가독성을 위한 것

정수는 따옴표나 소수점 없이 작성합니다. Python의 정수는 임의 정밀도입니다. C나 Java의 32비트 또는 64비트 고정 크기 정수와 달리 어떤 값도 담을 수 있도록 증가합니다. 숫자 리터럴의 밑줄은 시각적인 것으로 Python이 무시합니다.

python
score      = 0
age        = 28
population = 8_100_000_000

Python의 int는 임의 정밀도입니다. 객체는 값이 커질수록 추가 메모리를 할당하며, 가용 RAM에 의해서만 제한됩니다. CPython은 -5에서 256까지의 작은 정수를 싱글톤으로 캐시합니다. id(1) == id(1)은 항상 True입니다. 그 범위 밖에서는 각 리터럴이 별개의 객체를 생성합니다. 이것이 정수 비교에서 is가 신뢰할 수 없는 결과를 주는 이유입니다. 항상 ==를 사용하세요.

python
score      = 0
age        = 28
population = 8_100_000_000

소수 (float)

소수점이 있는 모든 숫자는 float입니다. 대부분의 계산에서 예상대로 작동합니다. 알아둘 점: 일부 소수 값은 이진수로 정확하게 저장할 수 없어 아주 작은 반올림 오류가 발생할 수 있습니다:

python
price       = 4.99
temperature = 36.6

0.1 + 0.2   # 0.30000000000000004

일상적인 작업에서는 거의 문제가 되지 않습니다. 몇 분의 일 센트까지 중요한 금융 계산에서는 Python의 decimal 모듈이 올바르게 처리합니다. 이는 숫자 챕터에서 다룹니다.

소수점이 있는 모든 숫자는 float가 됩니다. Python의 float는 IEEE 754 binary64: 64비트로 약 15-17자리의 유효 십진수 정밀도를 가집니다. 잘 알려진 문제: 0.1 + 0.20.30000000000000004입니다. Python의 버그가 아니라 이진 표현의 결과입니다. 정확한 소수가 중요한 금융 계산에서는 Python의 decimal 모듈이 적합한 도구이며, 숫자 챕터에서 다룹니다.

python
price       = 4.99
temperature = 36.6

float는 C의 double에 매핑됩니다: IEEE 754 binary64, 53비트 가수부, 상대 정밀도 2^-52 ≈ 2.2e-16. 분모에 2 이외의 소인수가 있는 분수(예: 1/10 = 1/(2×5))는 이진수에서 무한 소수이므로 정확하게 저장할 수 없습니다. 정확한 십진수 산술을 위해 Python의 decimal.Decimal은 임의 정밀도 10진수를 사용합니다. 정확한 유리수 산술을 위해 fractions.Fraction은 분자/분모 쌍을 저장합니다. 둘 다 표준 라이브러리에 있으며, 모듈 챕터에서 다룹니다.

python
price       = 4.99
temperature = 36.6

참 또는 거짓 (bool)

어떤 것들은 단순히 켜져 있거나 꺼져 있습니다. Python은 이를 위해 **불리언(boolean)**을 사용합니다: 정확히 두 가지 값, TrueFalse. 이 단계에서는 사소해 보이지만, 프로그램의 모든 조건과 분기는 불리언으로 작동합니다.

python
is_logged_in = True
has_errors   = False

Python은 또한 조건에서 사용될 때 특정 값을 False처럼 취급합니다: 0, 0.0, "", 그리고 None("여기에 값 없음"을 뜻하는 Python의 표현)은 모두 False처럼 동작합니다. 다른 모든 것은 True처럼 동작합니다. 이는 제어 흐름 챕터에서 유용해집니다.

bool은 정확히 True 또는 False를 담습니다. 비교에 의해 반환되고 조건에서 소비됩니다. Python에는 더 넓은 참 같은(truthy) 값과 거짓 같은(falsy) 값이 있습니다: 영값, 빈 컨테이너, None은 거짓 같은 값이며, 다른 모든 것은 참 같은 값입니다. 유용한 세부 사항: boolint의 서브클래스이므로 True + True2로 평가됩니다.

python
is_logged_in = True
has_errors   = False

boolint를 상속합니다. TrueFalse는 각각 정수값 1과 0을 가진 싱글톤입니다. 거짓 같은 값: 영값(0, 0.0), 빈 시퀀스와 매핑("", [], (), {}), None, 그리고 False. 다른 모든 것은 참 같은 값입니다. 커스텀 객체는 __bool__ 또는 __len__을 통해 이를 제어합니다. isinstance(True, int)True이며, 이는 제네릭 타입 체크 코드에서 중요합니다.

python
is_logged_in = True
has_errors   = False

타입 확인과 변환

값의 타입이 확실하지 않을 때 type()이 알려줍니다. 값이 특정 타입인지 확인하려면 isinstance()가 더 신뢰할 수 있는 도구입니다:

python
print(type("hello"))   # <class 'str'>
print(type(42))        # <class 'int'>
print(type(3.14))      # <class 'float'>
print(type(True))      # <class 'bool'>

isinstance(42, int)    # True
isinstance("hi", str)  # True

type()은 객체의 정확한 타입을 반환합니다. 자신의 코드에서 타입 확인을 위해서는 isinstance()가 선호됩니다. 상속을 처리하는데, type() 비교는 그렇지 않습니다.

python
print(type(42))          # <class 'int'>
isinstance(True, int)    # True   (bool은 int의 서브클래스)
type(True) == int        # False  (정확한 일치만, 서브클래스 없음)

type(x)x의 타입 객체를 반환합니다. isinstance(x, T)는 MRO(x.__class__.__mro__)를 순회하며, type() 비교가 놓치는 서브클래스 관계를 처리합니다. 실용적인 사례: isinstance(True, int)boolint를 상속하기 때문에 True입니다. type(True) == int는 정확한 정체성 확인이므로 False입니다. 프로덕션 코드의 타입 가드에는 isinstance()를 사용하세요.

python
isinstance(True, int)    # True
type(True) == int        # False

Python은 타입을 자동으로 혼합하지 않습니다. 문자열과 숫자를 연결하면 TypeError가 발생합니다:

python
score = 42
print("Your score is " + score)        # TypeError
print("Your score is " + str(score))   # 작동함

타입 이름을 함수로 사용해 명시적으로 변환하세요:

호출결과
str(42)"42"
int(3.9)3 (잘림, 반올림 아님)
float("3.14")3.14
int("3.14")ValueError: 소수 문자열을 int로 직접 변환 불가
int(float("3.14"))3 (먼저 float로 변환 후 int로 변환)
bool(0) / bool("")False

실제 활용

네 가지 타입이 모두 함께 작동하는 간단한 스크립트입니다. 출력 줄은 f-문자열을 사용해 텍스트에 값을 삽입합니다: 여는 따옴표 앞에 f를 붙이고 {}로 변수를 감쌉니다. Python이 실제 변수 값으로 대체합니다. 다음 챕터에서 제대로 배우게 됩니다.

python
player_name = "지민"
level       = 3
accuracy    = 0.94
is_premium  = True

print(f"{player_name}은 레벨 {level}에서 정확도 {accuracy:.0%}입니다.")
print(f"프리미엄 계정: {is_premium}")

타입이 중요한 이유는 level + 1은 작동하지만 player_name + 1은 작동하지 않기 때문입니다. 각 변수는 정확히 한 종류의 값을 담으며, Python은 조용히 그것들을 혼합하지 않습니다.

모든 네 가지 타입을 포함하고 상수와 런타임 상태가 분리된 실제적인 설정 블록입니다. f"..." 구문은 f-문자열입니다. {} 안의 모든 표현식은 런타임에 평가되어 출력에 삽입됩니다. 출력과 입력 챕터에서 자세히 다룹니다.

python
BASE_URL    = "https://api.example.com"
MAX_RETRIES = 3
DEBUG       = False

user_name     = "지민"
request_count = 0
last_response = None

request_count += 1
print(f"[{request_count}] {BASE_URL} | debug={DEBUG}")

None은 "아직 값 없음"의 표준 플레이스홀더입니다. 타입은 NoneType이며 조건에서 거짓 같은 값으로 동작합니다. 프로그램의 나중에야 의미가 있는 변수의 기본값으로 사용하세요.

인라인 타입 어노테이션이 있는 동일한 설정입니다. 어노테이션은 타입 체커와 IDE를 위한 문서이며 런타임에는 영향이 없습니다:

python
BASE_URL:    str  = "https://api.example.com"
MAX_RETRIES: int  = 3
DEBUG:       bool = False

user_name:     str        = "지민"
request_count: int        = 0
last_response: str | None = None

str | None은 Python 3.10의 유니온 구문입니다. 변수가 문자열 또는 None을 담습니다. 이전 버전에서 동등한 표현은 typing 모듈의 Optional[str]입니다. 최소 버전이 허용하는 경우 현대 Python에서는 str | None 형식이 선호됩니다.