파이썬에서 문자열(string)은 불변(immutable) 객체입니다. 이는 한 번 생성된 문자열은 그 내용을 변경할 수 없다는 것을 의미합니다. 하지만 이게 정확히 어떤 의미일까요? 예제를 통해 살펴봅시다.
1. 문자열 불변성 예제
# 문자열 생성
greeting = "Hello"
# 문자열 변경 시도
try:
greeting[0] = "h"
except TypeError as e:
print(f"오류 발생: {e}")
# 출력: 오류 발생: 'str' object does not support item assignment
위 코드에서 우리는 greeting 문자열의 첫 번째 문자를 변경하려고 시도했지만, TypeError가 발생했습니다. 이는 문자열이 불변이기 때문입니다.
2. 그렇다면 문자열을 어떻게 "변경"할 수 있을까?
사실, 우리는 문자열을 "변경"하는 것이 아니라, 새로운 문자열을 생성하는 것입니다.
# 새로운 문자열 생성
greeting = "Hello"
new_greeting = "h" + greeting[1:]
print(greeting) # 출력: Hello
print(new_greeting) # 출력: hello
# id() 함수를 사용하여 객체의 고유 식별자 확인
print(id(greeting)) # 예: 140312528944560
print(id(new_greeting)) # 예: 140312528944624 (다른 값)
new_greeting은 greeting을 변경한 것이 아니라, 완전히 새로운 문자열 객체입니다. id() 함수를 사용하면 이 두 객체가 서로 다른 메모리 주소를 가지고 있음을 확인할 수 있습니다.
3. 문자열 불변성의 장점
예상치 못한 변경으로부터 보호
def process_name(name):
# 이 함수는 name을 변경할 수 없습니다.
# 따라서 호출한 곳의 원래 문자열은 안전합니다.
return name.upper()
original = "Alice"
processed = process_name(original)
print(original) # 여전히 "Alice"
print(processed) # "ALICE"
문자열이 불변이라는 것은 한 번 만들어진 문자열이 프로그램의 어느 부분에서도 변경되지 않는다는 뜻입니다. 이는 마치 금고에 넣어둔 귀중품과 같아요. 누군가 실수로 내용을 바꿀 염려가 없죠.
해시 가능: 딕셔너리 키와 집합의 요소로 사용 가능
student_scores = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(f"학생 점수: {student_scores}")
unique_names = {"Alice", "Bob", "Charlie", "Alice"}
print(f"고유한 이름: {unique_names}")
# 출력:
# 학생 점수: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# 고유한 이름: {'Bob', 'Alice', 'Charlie'} # Alice는 한번만 포함됩니다.
불변성 덕분에 문자열은 해시 가능합니다. 이게 무슨 말이냐고요? 간단히 말해, 문자열을 딕셔너리의 키나 집합의 요소로 사용할 수 있다는 뜻입니다.
캐싱: 메모리 최적화
a = "Hello"
b = "Hello"
print(a is b) # True: a와 b는 같은 객체를 가리킵니다
파이썬은 동일한 문자열을 재사용합니다. 이를 '문자열 인터닝'이라고 하죠. 이는 메모리 사용을 크게 줄일 수 있습니다.
4. 문자열 불변성의 단점
메모리 사용: 수정 시 새 객체 생성
import sys
result = ""
for i in range(5):
result += str(i)
print(f"현재 result의 메모리 크기: {sys.getsizeof(result)} bytes")
# 출력:
# 현재 result의 메모리 크기: 50 bytes
# 현재 result의 메모리 크기: 51 bytes
# 현재 result의 메모리 크기: 52 bytes
# 현재 result의 메모리 크기: 53 bytes
# 현재 result의 메모리 크기: 54 bytes
문자열을 "수정"할 때마다 실제로는 새로운 문자열 객체가 생성됩니다. 이는 많은 문자열 조작이 필요한 경우 메모리 사용량을 증가시킬 수 있습니다.
성능: 대규모 수정 시 비효율적
import time
start_time = time.time()
long_string = "a" * 1000000
long_string = "b" + long_string[1:] # 새로운 백만 글자 문자열 생성!
end_time = time.time()
print(f"실행 시간: {end_time - start_time:.5f} 초")
# 출력:
# 실행 시간: 0.01562 초 (실제 시간은 시스템에 따라 다를 수 있습니다)
큰 문자열을 여러 번 수정해야 할 경우, 매번 새로운 객체를 생성하는 것은 시간과 메모리 측면에서 비효율적일 수 있습니다.
5. 문자열 불변성의 단점 극복하기
import time
start_time = time.time()
chars = list("a" * 1000000)
chars[0] = "b"
long_string = "".join(chars)
end_time = time.time()
print(f"실행 시간: {end_time - start_time:.5f} 초")
# 출력:
# 실행 시간: 0.00781 초 (실제 시간은 시스템에 따라 다를 수 있습니다)
- 리스트 사용: 문자열 대신 리스트를 사용하여 수정 후 ''.join()으로 합칠 수 있습니다.
- StringIO: 큰 문자열을 효율적으로 구축할 때 사용할 수 있습니다.
- 문자열 연결을 위한 join() 메서드: '+' 연산자 대신 join()을 사용하면 더 효율적입니다.
6. 착각하기 쉬운 문자열 함수
replace() 함수
original = "Hello, World!"
result = original.replace("World", "Python")
print(f"원본: {original}")
print(f"결과: {result}")
# 출력:
# 원본: Hello, World!
# 결과: Hello, Python!
많은 초보자들이 replace() 함수가 원본 문자열을 직접 수정한다고 생각합니다. 하지만 실제로는 새로운 문자열을 반환합니다. 여기서 original 문자열은 변경되지 않았습니다. replace() 함수는 새로운 문자열을 생성하여 반환합니다.
upper(), lower() 함수
text = "Python is Amazing"
upper_text = text.upper()
lower_text = text.lower()
print(f"원본: {text}")
print(f"대문자: {upper_text}")
print(f"소문자: {lower_text}")
# 출력:
# 원본: Python is Amazing
# 대문자: PYTHON IS AMAZING
# 소문자: python is amazing
원본 text는 그대로 유지되며, 새로운 문자열들이 생성됩니다.
strip(), lstrip(), rstrip() 함수
text = " Hello, Python! "
stripped = text.strip()
lstripped = text.lstrip()
rstripped = text.rstrip()
print(f"원본: '{text}'")
print(f"양쪽 공백 제거: '{stripped}'")
print(f"왼쪽 공백 제거: '{lstripped}'")
print(f"오른쪽 공백 제거: '{rstripped}'")
# 출력:
# 원본: ' Hello, Python! '
# 양쪽 공백 제거: 'Hello, Python!'
# 왼쪽 공백 제거: 'Hello, Python! '
# 오른쪽 공백 제거: ' Hello, Python!'
공백을 제거하는 이 함수들도 새로운 문자열을 반환합니다. 각 함수는 새로운 문자열을 생성하여 반환하며, 원본 text는 변경되지 않습니다.
split() 함수
sentence = "Python is a great language"
words = sentence.split()
print(f"원본: {sentence}")
print(f"분할된 단어들: {words}")
# 출력:
# 원본: Python is a great language
# 분할된 단어들: ['Python', 'is', 'a', 'great', 'language']
split() 함수는 문자열을 분할하여 리스트로 반환합니다. 원본 문자열은 그대로 유지됩니다. sentence는 변경되지 않고, 새로운 리스트 words가 생성됩니다.
join() 함수
words = ['Python', 'is', 'awesome']
sentence = " ".join(words)
print(f"원본 리스트: {words}")
print(f"생성된 문자열: {sentence}")
# 출력:
# 원본 리스트: ['Python', 'is', 'awesome']
# 생성된 문자열: Python is awesome
join()은 문자열 메서드이지만, 리스트나 튜플의 요소들을 연결하는 데 사용됩니다. 이 함수도 새로운 문자열을 생성합니다. words 리스트는 변경되지 않고, 새로운 문자열 sentence가 생성됩니다.
'TIL' 카테고리의 다른 글
[TIL] 그래프 1편 - 그래프 정의, 특징, 종류, 구성 요소 (2) | 2024.09.20 |
---|---|
[TIL] 컴퓨터 시스템 요약 - 1장. 컴퓨터 시스템으로의 여행 (1편) (4) | 2024.09.18 |
[TIL] 자료 구조와 배열 (Python) 5편 - 리스트와 튜플 활용하기 (1) | 2024.09.10 |
[TIL] 자료 구조와 배열 (Python) 4편 - 뮤터블과 이뮤터블 객체의 대입 (0) | 2024.09.10 |
[TIL] 파이썬의 인수 전달 방식 이해하기 (0) | 2024.09.09 |