String interning - 'a' is 'a' nhưng...
Hiện tượng lạ
Phần tiêu đề “Hiện tượng lạ”# Case 1: String đơn giảna = "hello"b = "hello"print(a is b) # True
# Case 2: String có khoảng trắngx = "hello world"y = "hello world"print(x is y) # True (trong Python interactive) hoặc False (tùy trường hợp)
# Case 3: String được tạo runtimes1 = "hello"s2 = "hel" + "lo"print(s1 is s2) # True
s3 = "hel"s4 = s3 + "lo"print(s1 is s4) # False <- What the Python?!Tại sao cùng nội dung “hello” nhưng khi so sánh is có khi True có khi False?! 🤔
Giải thích
Phần tiêu đề “Giải thích”String Interning là gì?
Phần tiêu đề “String Interning là gì?”String interning là kỹ thuật tối ưu bộ nhớ: Python lưu trữ mỗi string unique chỉ một lần, và tất cả references đều trỏ đến cùng object đó.
# Thay vì:# "hello" tại địa chỉ 0x1000# "hello" tại địa chỉ 0x2000 (2 copies)
# Python làm:# "hello" tại địa chỉ 0x1000# Tất cả biến đều trỏ đến 0x1000 (1 copy duy nhất)Tại sao cần String Interning?
Phần tiêu đề “Tại sao cần String Interning?”- Tiết kiệm bộ nhớ: Không lưu trùng lặp strings
- So sánh nhanh hơn:
is(so sánh địa chỉ) nhanh hơn==(so sánh nội dung) - Dictionary lookup nhanh hơn: Dict keys là strings
Khi nào strings được interned?
Phần tiêu đề “Khi nào strings được interned?”✅ Luôn được interned
Phần tiêu đề “✅ Luôn được interned”1. String literals (compile-time)
Phần tiêu đề “1. String literals (compile-time)”# Strings được định nghĩa trực tiếp trong codea = "hello"b = "hello"print(a is b) # Trueprint(id(a) == id(b)) # True2. Strings giống identifiers
Phần tiêu đề “2. Strings giống identifiers”Strings chỉ chứa:
- Chữ cái (a-z, A-Z)
- Số (0-9)
- Underscore (_)
# Giống tên biến/function → Được interneds1 = "variable_name"s2 = "variable_name"print(s1 is s2) # True
s1 = "hello123"s2 = "hello123"print(s1 is s2) # True3. Empty string và single characters
Phần tiêu đề “3. Empty string và single characters”# Empty stringa = ""b = ""print(a is b) # True
# Single characterc = "a"d = "a"print(c is d) # True❌ Không chắc chắn được interned
Phần tiêu đề “❌ Không chắc chắn được interned”1. Strings có khoảng trắng
Phần tiêu đề “1. Strings có khoảng trắng”# Không luôn được internedx = "hello world"y = "hello world"print(x is y) # Có thể True hoặc False tùy context2. Strings có ký tự đặc biệt
Phần tiêu đề “2. Strings có ký tự đặc biệt”# Có ký tự đặc biệts1 = "hello!"s2 = "hello!"print(s1 is s2) # Có thể True hoặc False
s1 = "hello@world"s2 = "hello@world"print(s1 is s2) # Có thể True hoặc False3. Strings được tạo runtime
Phần tiêu đề “3. Strings được tạo runtime”# Compile-time concatenation - Được interneds1 = "hello"s2 = "hel" + "lo" # Python optimizer tính lúc compileprint(s1 is s2) # True
# Runtime concatenation - Không được interneds3 = "hel"s4 = s3 + "lo" # Tính lúc runtimeprint(s1 is s4) # False4. Strings dài
Phần tiêu đề “4. Strings dài”# String quá dài thường không được internedlong1 = "a" * 1000long2 = "a" * 1000print(long1 is long2) # Thường là FalseCompile-time vs Runtime
Phần tiêu đề “Compile-time vs Runtime”Compile-time optimization
Phần tiêu đề “Compile-time optimization”# Python optimizer "gộp" lúc compilea = "hel" + "lo" # Thành "hello" lúc compileb = "hello"print(a is b) # True
# Với sốc = 2 + 3 # Thành 5 lúc compileRuntime concatenation
Phần tiêu đề “Runtime concatenation”# Tính lúc chạy → Tạo string mớix = "hel"y = x + "lo" # Tạo string mới lúc runtimez = "hello"print(y is z) # False - y là object mớiprint(y == z) # True - Nội dung giống nhauVí dụ thực tế
Phần tiêu đề “Ví dụ thực tế”Case 1: Input từ user
Phần tiêu đề “Case 1: Input từ user”# User input luôn là string mớiname1 = input("Enter name: ") # User nhập "Alice"name2 = "Alice" # String literal
print(name1 == name2) # True - Nội dung giốngprint(name1 is name2) # False - Khác object!Case 2: Reading từ file
Phần tiêu đề “Case 2: Reading từ file”# Đọc từ file tạo string mớiwith open("data.txt") as f: line = f.readline().strip() # "hello"
literal = "hello"
print(line == literal) # Trueprint(line is literal) # False (thường)Case 3: String operations
Phần tiêu đề “Case 3: String operations”# String operations tạo objects mớioriginal = "HELLO"lower1 = original.lower() # "hello"lower2 = original.lower() # "hello"
print(lower1 == lower2) # True - Giống nội dungprint(lower1 is lower2) # False - Khác objectsForce interning với sys.intern()
Phần tiêu đề “Force interning với sys.intern()”Bạn có thể force Python intern một string:
import sys
# Không được interneda = "hello world!"b = "hello world!"print(a is b) # False
# Force interninga = sys.intern("hello world!")b = sys.intern("hello world!")print(a is b) # TrueKhi nào nên dùng sys.intern()?
Phần tiêu đề “Khi nào nên dùng sys.intern()?”✅ Nên dùng khi:
Phần tiêu đề “✅ Nên dùng khi:”-
Có rất nhiều strings trùng lặp
# Processing log files với nhiều repeated stringslog_levels = [sys.intern(level) for level in log_data] -
Dictionary lookups nhiều
# Dictionary với nhiều lookupscache = {}key = sys.intern("user_data") # Faster lookups -
Memory-constrained applications
# Tiết kiệm RAM khi có nhiều string giống nhau
❌ Không nên dùng khi:
Phần tiêu đề “❌ Không nên dùng khi:”- Strings không lặp lại nhiều
- Strings short-lived (tồn tại ngắn)
- Performance không quan trọng
is vs == với Strings
Phần tiêu đề “is vs == với Strings”Khi nào dùng is?
Phần tiêu đề “Khi nào dùng is?”# ✅ So sánh với None, True, Falseif value is None: pass
# ✅ Kiểm tra cùng object (hiếm khi cần với strings)if a is b: print("Same object")Khi nào dùng ==?
Phần tiêu đề “Khi nào dùng ==?”# ✅ So sánh nội dung strings (hầu hết trường hợp)if username == "admin": pass
if password == stored_password: pass
# ✅ So sánh strings từ input/file/runtimeif user_input == "yes": passCác lỗi phổ biến
Phần tiêu đề “Các lỗi phổ biến”Bug 1: Dùng is thay vì ==
Phần tiêu đề “Bug 1: Dùng is thay vì ==”# SAI - Dùng is để so sánhdef check_status(status): if status is "active": # ❌ Bug! return True
# Có thể work với literalsprint(check_status("active")) # True (may be)
# Nhưng fail với runtime stringsuser_status = input("Status: ") # User nhập "active"print(check_status(user_status)) # False - Bug!
# ĐÚNG - Dùng ==def check_status(status): if status == "active": # ✅ return TrueBug 2: Rely on interning behavior
Phần tiêu đề “Bug 2: Rely on interning behavior”# SAI - Assume strings được interneddef optimize_lookup(key): # Giả sử key được interned if key is "user_id": # ❌ Không reliable! return fast_path()
# ĐÚNG - Luôn dùng ==def optimize_lookup(key): if key == "user_id": # ✅ return fast_path()Bug 3: Performance premature optimization
Phần tiêu đề “Bug 3: Performance premature optimization”# SAI - Intern mọi string without reasonclass UserManager: def __init__(self): # Không cần thiết self.username = sys.intern(username) # ❌ Overkill
# ĐÚNG - Chỉ intern khi cầnclass UserManager: def __init__(self): self.username = username # ✅ SimpleTesting string interning
Phần tiêu đề “Testing string interning”Kiểm tra có được interned không
Phần tiêu đề “Kiểm tra có được interned không”def check_interned(s1, s2): print(f"Values equal: {s1 == s2}") print(f"Same object (interned): {s1 is s2}") print(f"ID of s1: {id(s1)}") print(f"ID of s2: {id(s2)}")
# Testcheck_interned("hello", "hello")check_interned("hello world", "hello world")Tóm tắt
Phần tiêu đề “Tóm tắt”| String | Interned? | Lý do |
|---|---|---|
"hello" |
✅ Luôn | String literal |
"hello123" |
✅ Luôn | Giống identifier |
"" |
✅ Luôn | Empty string |
"a" |
✅ Luôn | Single char |
"hello world" |
⚠️ Có thể | Có space, tùy context |
"hello!" |
⚠️ Có thể | Có ký tự đặc biệt |
"hel" + "lo" |
✅ Có | Compile-time concat |
s + "lo" |
❌ Không | Runtime concat |
input() |
❌ Không | Runtime tạo |
sys.intern(s) |
✅ Luôn | Force interned |
Best Practices
Phần tiêu đề “Best Practices”✅ Nên làm
Phần tiêu đề “✅ Nên làm”# 1. Luôn dùng == để so sánh stringsif name == "Alice": # ✅ pass
# 2. Chỉ dùng is với None, True, Falseif value is None: # ✅ pass
# 3. Dùng sys.intern() cho strings lặp lại nhiềucache_keys = [sys.intern(key) for key in frequent_keys] # ✅❌ Tránh làm
Phần tiêu đề “❌ Tránh làm”# 1. Dùng is để so sánh string contentif status is "active": # ❌ pass
# 2. Rely on interning cho logicif key is expected_key: # ❌ Unreliable pass
# 3. Intern mọi string without reasonevery_string = sys.intern(s) # ❌ OverkillGhi nhớ
Phần tiêu đề “Ghi nhớ”String interning là optimization detail, không phải feature!
- ✅ Luôn dùng
==để so sánh string content- ❌ Đừng dùng
isđể so sánh strings- ⚠️ Interning behavior có thể thay đổi giữa các Python versions
- 🔧 Chỉ dùng
sys.intern()khi thật sự cần optimize
Nguyên tắc vàng: is for identity, == for equality!