누구나 로고
オンライン講座企業・団体研修ライブラリトレンドインサイト無料コースお知らせお問い合わせ採用サイト
모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.
로그인무료 회원가입
강좌파이썬 기초 40강 — 처음 배우는 프로그래밍8강

8강 / 전체 14강

문자열(str) 완전 해부 — 유니코드, 불변성, 인코딩

9분 읽기 조회 0

파이썬 문자열이 유니코드를 어떻게 처리하는지, 인코딩이 왜 필요한지 깊이 이해한다.

🎯 이 강의에서 배우는 것

파이썬 3에서 문자열은 단순히 글자들의 나열이 아닙니다. 전 세계 모든 언어를 지원하는 유니코드 코드 포인트의 불변 시퀀스입니다. 이 강의에서는 유니코드와 UTF-8의 원리, 문자열이 왜 불변이어야 하는지, 그리고 인코딩 오류가 왜 발생하고 어떻게 해결하는지를 완전히 이해합니다.

🌐 ASCII에서 유니코드로 — 왜 유니코드가 필요한가

초창기 컴퓨터는 영어권에서 발명됐습니다. ASCII(American Standard Code for Information Interchange)는 128개의 문자만 표현합니다: 영문자(대·소문자), 숫자, 특수문자. 한글, 한자, 아랍어, 이모지는 전혀 표현할 수 없었습니다.

각 나라들이 자국 언어를 위한 인코딩을 따로 만들기 시작했습니다. 한국의 EUC-KR, 일본의 Shift-JIS, 중국의 GB2312 등. 서로 다른 인코딩의 파일을 잘못된 인코딩으로 열면 글자가 깨지는 이른바 "모지바케(문자 깨짐)"가 발생했습니다.

유니코드(Unicode)는 이 문제를 해결하기 위해 전 세계 모든 문자에 고유한 번호(코드 포인트)를 부여하는 표준입니다:

# 유니코드 코드 포인트 확인
print(ord("A"))        # 65   (U+0041)
print(ord("가"))       # 44032 (U+AC00)
print(ord("😀"))       # 128512 (U+1F600)
print(ord("漢"))       # 28450 (U+6F22)

# 코드 포인트에서 문자로
print(chr(65))         # A
print(chr(44032))      # 가
print(chr(128512))     # 😀

# 유니코드 이름 확인
import unicodedata
print(unicodedata.name("가"))    # HANGUL SYLLABLE GA
print(unicodedata.name("😀"))   # GRINNING FACE

현재 유니코드는 140,000개 이상의 코드 포인트를 정의합니다. U+0000부터 U+10FFFF까지 약 110만 개의 슬롯이 있으며, 계속 새로운 문자(이모지 포함)가 추가됩니다.

🔤 UTF-8: 유니코드 코드 포인트를 바이트로 변환하기

유니코드는 코드 포인트의 목록이지, 저장 방식이 아닙니다. 실제로 파일이나 네트워크에 저장·전송하려면 코드 포인트를 바이트로 변환해야 합니다. 이 변환 방식을 인코딩(encoding)이라고 합니다.

UTF-8은 현재 가장 많이 사용되는 유니코드 인코딩입니다:

코드 포인트 범위바이트 수예시
U+0000 ~ U+007F1바이트ASCII 문자 (A, 0, @)
U+0080 ~ U+07FF2바이트라틴어 확장, 아랍어
U+0800 ~ U+FFFF3바이트한글, 한자, 일본어
U+10000 ~ U+10FFFF4바이트이모지, 고대 문자
# str → bytes 인코딩 (encode)
text = "안녕, Python!"
encoded_utf8 = text.encode("utf-8")
print(encoded_utf8)         # b'\xec\x95\x88\xeb\x85\x95, Python!'
print(len(text))            # 12 (문자 수)
print(len(encoded_utf8))    # 18 (바이트 수 — 한글 각 3바이트)

# bytes → str 디코딩 (decode)
decoded = encoded_utf8.decode("utf-8")
print(decoded)              # 안녕, Python!

# 다른 인코딩
encoded_cp949 = text.encode("cp949")   # 한국 윈도우 기본 인코딩
print(len(encoded_cp949))  # 14 (한글 각 2바이트)

# 인코딩 불일치 예시
try:
    # UTF-8로 인코딩된 데이터를 CP949로 디코딩 시도
    encoded_utf8.decode("cp949")
except UnicodeDecodeError as e:
    print(f"오류: {e}")   # 인코딩 불일치로 오류 발생

UTF-8이 널리 사용되는 이유:

  • ASCII 하위 호환: ASCII 문자(코드 포인트 0-127)는 UTF-8에서 동일한 1바이트로 표현됩니다. 영문 텍스트를 처리하는 기존 시스템과 호환됩니다.
  • 자기 동기화: 멀티바이트 시퀀스의 어느 바이트에서든 문자 경계를 찾을 수 있습니다.
  • 공간 효율: 주로 ASCII를 사용하는 텍스트는 UTF-32(모든 문자를 4바이트)보다 훨씬 적은 공간을 사용합니다.

🔒 문자열의 불변성 — 왜 str은 바꿀 수 없는가

s = "hello"

# 인덱싱으로 읽기는 가능
print(s[0])     # h

# 인덱싱으로 쓰기는 불가 — TypeError 발생
try:
    s[0] = "H"
except TypeError as e:
    print(f"오류: {e}")   # 'str' object does not support item assignment

# 수정처럼 보이는 것은 사실 새 객체 생성
s2 = s.replace("hello", "Hello")
print(s)    # hello  — 원본 변경 없음
print(s2)   # Hello  — 새 객체
print(s is s2)  # False

문자열이 불변(immutable)인 이유:

  1. 해시 가능성: 불변 객체는 해시 가능합니다. 이 덕분에 문자열을 딕셔너리 키나 셋 원소로 사용할 수 있습니다.
  2. 인터닝 최적화: 같은 내용의 문자열 리터럴을 재사용(인터닝)할 수 있습니다.
  3. 스레드 안전: 불변 객체는 여러 스레드에서 동시에 읽어도 안전합니다.

문자열 연결(+)의 함정: 루프에서 + 연산자로 문자열을 계속 이어붙이면 매번 새 객체가 생성됩니다:

import time

# 느린 방법: + 연산자 반복 (O(n^2))
start = time.perf_counter()
result = ""
for i in range(100_000):
    result += str(i)
print(f"+ 연산자: {time.perf_counter() - start:.3f}초")

# 빠른 방법: join() (O(n))
start = time.perf_counter()
result = "".join(str(i) for i in range(100_000))
print(f"join():   {time.perf_counter() - start:.3f}초")
# join()이 5~10배 빠름

💡 CPython의 문자열 내부 표현

CPython 3는 문자열의 메모리 효율을 위해 내용에 따라 세 가지 내부 인코딩 중 하나를 선택합니다:

import sys

# Latin-1 (ISO-8859-1): 모든 코드 포인트가 U+00FF 이하면 1바이트/문자
s_ascii = "hello"
print(sys.getsizeof(s_ascii))    # 54 bytes (문자당 1바이트)

# UCS-2: 가장 큰 코드 포인트가 U+FFFF 이하면 2바이트/문자
s_korean = "안녕"
print(sys.getsizeof(s_korean))   # 76 bytes (문자당 2바이트)

# UCS-4: U+10000 이상 코드 포인트 포함 시 4바이트/문자 (이모지 등)
s_emoji = "😀"
print(sys.getsizeof(s_emoji))    # 80 bytes (문자당 4바이트)

# 같은 문자열에 이모지가 하나만 들어가도 전체가 UCS-4가 됨
s_mixed = "안녕😀"
print(sys.getsizeof(s_mixed))    # 문자당 4바이트 × 3문자

🔗 문자열 인터닝

import sys

# CPython은 짧은 식별자 형태 문자열을 자동 인터닝
a = "hello"
b = "hello"
print(a is b)     # True  (인터닝으로 같은 객체)

# 공백 포함 문자열은 인터닝 안 됨
c = "hello world"
d = "hello world"
print(c is d)     # False (실행 환경마다 다를 수 있음)

# sys.intern()으로 명시적 인터닝
e = sys.intern("hello world")
f = sys.intern("hello world")
print(e is f)     # True  (강제 인터닝)

# 인터닝의 실용적 용도: 대량의 반복 문자열 키 비교 성능 향상

⚠️ 자주 하는 실수

  • 파일 인코딩 명시 안 함: open("file.txt")는 운영체제 기본 인코딩을 사용합니다. Windows는 CP949, Linux/macOS는 UTF-8. 항상 encoding="utf-8"을 명시하세요.
  • 루프에서 + 연산자로 문자열 연결: O(n²)의 시간 복잡도. "".join(list)를 사용하세요.
  • len()이 바이트 수가 아닌 문자 수: len("안녕")은 2 (문자 수). 바이트 수는 len("안녕".encode("utf-8"))로 확인합니다.

📝 정리 및 다음 강의 예고

  • 유니코드는 전 세계 모든 문자에 코드 포인트를 부여하는 표준이고, UTF-8은 이를 바이트로 변환하는 가장 널리 쓰이는 인코딩입니다.
  • 파이썬 str은 유니코드 코드 포인트의 불변 시퀀스입니다.
  • 문자열 연결 루프에서는 + 대신 "".join()을 사용하세요.
  • 파일 I/O에서 항상 encoding="utf-8"을 명시하세요.

다음 강의: 불리언(bool)과 None의 깊은 의미를 배웁니다. 트루시니스(truthiness)가 무엇인지, 단락 평가(short-circuit evaluation)가 어떻게 작동하는지, 그리고 None을 올바르게 사용하는 방법을 배웁니다.

관련 주제

  • 유니코드와 UTF-8 인코딩
  • 문자열 불변성
  • 문자열 인터닝
  • bytes vs str 변환
  • UnicodeDecodeError
  • join vs 문자열 연결 성능
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • 파이썬 기초 40강 — 처음 배우는 프로그래밍
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나
이전 7강부동소수점(float)의 함정 — IEEE 754와 수치 오류의 진실다음 9강 불리언(bool)과 None — 진리값 시스템의 철학

댓글

0/1000

불러오는 중...

목차

  1. 1.컴퓨터는 어떻게 생각하는가 — 0과 1에서 프로그램까지
  2. 2.파이썬 생태계와 개발 환경 완전 설정
  3. 3.파이썬의 실행 모델 — 바이트코드, PVM, GIL
  4. 4.첫 프로그램 — print()와 input()으로 배우는 I/O 원리
  5. 5.변수란 무엇인가 — 이름표, 객체, 참조의 삼각관계
  6. 6.정수(int) 완전 해부 — 임의 정밀도, 비트 연산, 정수 연산의 함정
  7. 7.부동소수점(float)의 함정 — IEEE 754와 수치 오류의 진실
  8. 8.문자열(str) 완전 해부 — 유니코드, 불변성, 인코딩
  9. 9.불리언(bool)과 None — 진리값 시스템의 철학
  10. 10.연산자 완전 분석 — 우선순위, 결합성, 연산자 오버로딩 예고
  11. 11.f-string과 문자열 포맷팅 완전 정복
  12. 12.조건문 — 프로그램이 결정을 내리는 방법
  13. 13.while 루프 — 반복의 본질과 루프 설계
  14. 14.for 루프 — 이터레이션 프로토콜의 깊은 이해

カスタマーサポート

  • お知らせ
  • よくある質問
  • お問い合わせ
  • コミュニティ

ご利用案内

  • 利用規約
  • プライバシーポリシー
  • 返金ポリシー

サービス

  • 会社紹介
  • 新規登録
  • 新着コース
  • 無料コース
누구나 로고
利用規約プライバシーポリシー返金ポリシー

상호명: NUGUNA  |  대표자: 정우진  |  사업자등록번호: 392-32-01817  |  통신판매업신고: 제 2026-서울양천-0564 호

주소: 서울특별시 양천구 목동서로 100  |  이메일: nugunapass@gmail.com  |  전화: 010-6395-3043

© 2026 NUGUNA. All rights reserved.

KB예금주인증관리자
ホームコースライブラリお問い合わせMY