Python 강좌 2편 - 파이썬의 어휘구조
파이썬이 우리가 쓴 코드를 어떻게 읽는지, 그 첫 번째 단계를 쉽게 풀어봅니다
어휘구조란?
우리가 파이썬 코드를 작성하면, 파이썬은 그것을 어떻게 이해할까?
x = 42 + y
우리 눈에는 "변수 x에 42와 y를 더한 값을 넣어라"라고 보인다.
하지만 파이썬 입장에서는 그냥 문자들의 나열일 뿐이다.
'x', ' ', '=', ' ', '4', '2', ' ', '+', ' ', 'y'
파이썬이 코드를 실행하려면 먼저 이 문자들을 의미 있는 단위로 쪼개야 한다.
우리가 글을 읽을 때 글자들을 단어로 인식하는 것과 같다.
토큰: 의미의 최소 단위
이렇게 쪼갠 의미 있는 조각을 토큰(Token)이라 부른다.
x = 42 + y
# 파이썬이 보는 토큰들:
# NAME: 'x'
# OP: '='
# NUMBER: 42
# OP: '+'
# NAME: 'y'
자연어와 비교하면 바로 감이 온다.
문장: "나는 밥을 먹는다"
단어: ["나는", "밥을", "먹는다"]
코드: "x = 42"
토큰: [NAME:'x', OP:'=', NUMBER:42]
어휘 분석: 토큰으로 쪼개기
어휘 분석(Lexical Analysis)은 코드를 읽어서 토큰으로 쪼개는 과정이다.
파이썬 인터프리터가 하는 첫 번째 일이다.
[소스 코드]
↓
[어휘 분석] ← 이번 강좌의 주제
↓
[토큰 리스트]
↓
[구문 분석]
↓
...
왜 이런 과정이 필요할까? 코드를 한 번에 이해하기엔 너무 복잡하기 때문이다. 파이썬은 단계별로 나눠서 처리하고, 어휘 분석이 그 첫 단계다.
어휘 분석기의 작동 원리
어휘 분석기는 상태 기계(State Machine)라는 방식으로 작동한다.
어렵게 들리지만, 우리 주변에서 흔히 볼 수 있는 개념이다.
상태 기계 예시
1. 배고픔 상태
우리의 배고픔 상태를 생각해보자.
stateDiagram-v2
배고픔 --> 배부름: 식사
배부름 --> 배고픔: 운동
특정 상태에 있다가 입력이 들어오면 다른 상태로 바뀌는 시스템. 이게 상태 기계다.
2. 신호등
신호등도 상태 기계다.
stateDiagram-v2
빨간불 --> 초록불: 30초 경과
초록불 --> 노란불: 30초 경과
노란불 --> 빨간불: 3초 경과
세 가지 상태가 있고, 시간이 지나면 상태가 바뀐다. 빨간불, 초록불, 노란불, 빨간불 -- 반복이다.
3. 계산기
계산기를 사용할 때를 생각해보자.
stateDiagram-v2
시작 --> 숫자대기
숫자대기 --> 연산자대기: 숫자 입력 (53, 10)
연산자대기 --> 숫자대기: 연산자 입력 (+)
연산자대기 --> 종료: (=) 입력
계산기는 항상 두 가지 상태 중 하나에 있다. 숫자를 기다리는 상태, 아니면 연산자를 기다리는 상태.
"5 + 3 ="을 처리하는 과정:
- 시작 -> 숫자대기 -> '53' 입력 -> 연산자대기
- 연산자대기 -> '+' 입력 -> 숫자대기
- 숫자대기 -> '10' 입력 -> 연산자대기
- 연산자대기 -> '=' 입력 -> 완료
이렇게 상태를 바꿔가며 입력을 처리하는 것이 상태 기계다.
어휘 분석 상태 기계
이제 파이썬의 어휘 분석이 어떻게 작동하는지 보자.
정수 인식하기
123이라는 문자열을 읽을 때의 상태 전이다.
stateDiagram-v2
[*] --> 시작
시작 --> 숫자읽는중: 숫자 ('1')
숫자읽는중 --> 숫자읽는중: 숫자 ('2', '3')
숫자읽는중 --> [*]: 공백 또는 연산자
note right of 숫자읽는중
계속 숫자를 모음
예: "123"
end note
한 글자씩 읽으면서 "지금 숫자를 읽고 있구나"라는 상태를 유지한다. 숫자가 아닌 문자를 만나면 토큰이 완성된다.
결과: NUMBER 토큰: 123
실수 인식하기
3.14처럼 소수점이 있으면 어떻게 될까?
stateDiagram-v2
[*] --> 시작
시작 --> 정수읽는중: 숫자 ('3')
정수읽는중 --> 정수읽는중: 숫자
정수읽는중 --> 실수읽는중: 소수점 ('.')
실수읽는중 --> 실수읽는중: 숫자 ('1', '4')
정수읽는중 --> [*]: 공백/연산자
실수읽는중 --> [*]: 공백/연산자
note right of 실수읽는중
소수점 이하 숫자들을 모음
예: "3.14"
end note
정수를 읽다가 .을 만나면 "아, 실수구나!" 하고 상태를 바꿔서 소수점 이하 숫자들을 계속 읽는다.
결과: NUMBER 토큰: 3.14
여러 종류 토큰 인식하기
실제 코드 x = 42 + y를 어떻게 읽을까?
stateDiagram-v2
[*] --> 시작
시작 --> 식별자읽기: 문자 ('x')
시작 --> 숫자읽기: 숫자 ('4')
시작 --> 연산자: 연산자 ('+', '=')
식별자읽기 --> 식별자읽기: 문자/숫자
식별자읽기 --> 시작: 공백 (NAME 토큰 생성)
숫자읽기 --> 숫자읽기: 숫자
숫자읽기 --> 시작: 공백 (NUMBER 토큰 생성)
연산자 --> 시작: (OP 토큰 생성)
시작 --> [*]: 문자열 끝
코드 x = 42 + y 처리 과정:
x-> 문자 -> 식별자 읽기 -> 공백 -> NAME: 'x'=-> 연산자 -> OP: '='42-> 숫자들 -> 공백 -> NUMBER: 42+-> 연산자 -> OP: '+'y-> 문자 -> 끝 -> NAME: 'y'
최종 결과: [NAME:'x', OP:'=', NUMBER:42, OP:'+', NAME:'y']
상태 기계를 쓰면 복잡한 코드도 체계적으로 토큰으로 쪼갤 수 있다.
파이썬의 어휘구조
파이썬만의 특별한 어휘 규칙들을 살펴보자.
1. 들여쓰기도 토큰이다
파이썬의 가장 큰 특징은 들여쓰기가 문법이라는 것이다.
def greet():
print("hello") # 들여쓰기 1단계
if True:
print("world") # 들여쓰기 2단계
다른 언어들은 중괄호 {}를 쓴다. 파이썬은 들여쓰기를 토큰으로 만든다.
# 파이썬이 보는 토큰들:
[
NAME: 'def',
NAME: 'greet',
OP: '(',
OP: ')',
OP: ':',
NEWLINE,
INDENT, # 들여쓰기 시작!
NAME: 'print',
...
NEWLINE,
DEDENT, # 들여쓰기 끝!
]
2. 문자열 표현의 다양성
파이썬은 문자열을 여러 가지 방식으로 쓸 수 있다.
# 기본 문자열
s1 = 'hello'
s2 = "world"
# 여러 줄 문자열
s3 = """
여러 줄에 걸쳐
문자열을 쓸 수 있습니다
"""
# 이스케이프를 무시하는 Raw 문자열
s4 = r'\n은 줄바꿈이 아니라 그냥 \n 입니다'
# 변수를 넣을 수 있는 f-string
name = "파이썬"
s5 = f"안녕하세요, {name}!" # "안녕하세요, 파이썬!"
3. 숫자 표현의 다양성
파이썬은 숫자도 여러 방식으로 쓸 수 있다.
# 일반 숫자
a = 42
# 2진수 (0과 1만)
b = 0b1010 # 10
# 8진수 (0~7)
c = 0o12 # 10
# 16진수 (0~9, A~F)
d = 0xA # 10
# 실수
e = 3.14
# 큰 숫자는 _로 구분 (읽기 쉽게)
f = 1_000_000 # 1000000
# 복소수
g = 3 + 4j
4. 파이썬 토크나이저로 직접 확인하기
파이썬에는 토큰 분석을 직접 볼 수 있는 tokenize 모듈이 있다.
코드 x = 42를 토큰으로 분석하면 이렇게 나온다.
NAME : 'x'
OP : '='
NUMBER : '42'
NEWLINE : '\n'
ENDMARKER : ''
예상한 대로 NAME, OP, NUMBER 토큰으로 쪼개지는 걸 확인할 수 있다.
어휘 분석을 이해하면 파이썬이 코드를 어떻게 읽는지 밑바닥부터 보이기 시작한다. 문법 오류가 왜 발생하는지도 훨씬 잘 이해할 수 있다. 그리고 혹시 나만의 언어를 만들고 싶다면, 바로 여기서부터 시작하면 된다.