어휘구조란?

우리가 파이썬 코드를 작성하면, 파이썬은 그것을 어떻게 이해할까?

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 ="을 처리하는 과정:

  1. 시작 -> 숫자대기 -> '53' 입력 -> 연산자대기
  2. 연산자대기 -> '+' 입력 -> 숫자대기
  3. 숫자대기 -> '10' 입력 -> 연산자대기
  4. 연산자대기 -> '=' 입력 -> 완료

이렇게 상태를 바꿔가며 입력을 처리하는 것이 상태 기계다.

어휘 분석 상태 기계

이제 파이썬의 어휘 분석이 어떻게 작동하는지 보자.

정수 인식하기

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 처리 과정:

  1. x -> 문자 -> 식별자 읽기 -> 공백 -> NAME: 'x'
  2. = -> 연산자 -> OP: '='
  3. 42 -> 숫자들 -> 공백 -> NUMBER: 42
  4. + -> 연산자 -> OP: '+'
  5. 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 토큰으로 쪼개지는 걸 확인할 수 있다.

어휘 분석을 이해하면 파이썬이 코드를 어떻게 읽는지 밑바닥부터 보이기 시작한다. 문법 오류가 왜 발생하는지도 훨씬 잘 이해할 수 있다. 그리고 혹시 나만의 언어를 만들고 싶다면, 바로 여기서부터 시작하면 된다.