Integer caching - 256 is 256 nhưng 257 is not 257?
Hiện tượng lạ
Phần tiêu đề “Hiện tượng lạ”# Số nhỏa = 256b = 256print(a is b) # True
# Số lớn hơn 1 chútx = 257y = 257print(x is y) # False (trong Python interactive) # True (trong file .py)What the Python?! Tại sao 256 is 256 là True nhưng 257 is 257 là False?! 🤔
Và tại sao kết quả lại khác nhau giữa interactive mode và file?!
Giải thích
Phần tiêu đề “Giải thích”Integer Caching/Interning
Phần tiêu đề “Integer Caching/Interning”Python pre-allocates (tạo sẵn) integers từ -5 đến 256 khi khởi động. Tất cả references đến các số này đều trỏ đến cùng một object.
# CPython implementation detail# Integers from -5 to 256 are cached
a = 100b = 100print(a is b) # True - Cùng object được cache
x = 1000y = 1000print(x is y) # Có thể True hoặc False tùy contextTại sao -5 đến 256?
Phần tiêu đề “Tại sao -5 đến 256?”Đây là quyết định dựa trên thống kê:
- Số nhỏ (-5 đến 256) được dùng rất nhiều trong code
- Loop indices:
for i in range(100) - Array indices:
arr[0],arr[1], … - Status codes: HTTP 200, 404, …
- ASCII values: 0-127
Việc cache giúp:
- ✅ Tiết kiệm bộ nhớ
- ✅ Tăng tốc độ so sánh
- ✅ Giảm garbage collection
Test với các số khác nhau
Phần tiêu đề “Test với các số khác nhau”# Trong Python interactive mode:
# -6 (ngoài cache range)a = -6b = -6print(a is b) # False
# -5 (trong cache range)a = -5b = -5print(a is b) # True
# 0 (trong cache range)a = 0b = 0print(a is b) # True
# 256 (trong cache range)a = 256b = 256print(a is b) # True
# 257 (ngoài cache range)a = 257b = 257print(a is b) # False (trong interactive mode)Interactive Mode vs Script File
Phần tiêu đề “Interactive Mode vs Script File”Interactive Mode (Python REPL)
Phần tiêu đề “Interactive Mode (Python REPL)”# Trong Python REPL>>> a = 257>>> b = 257>>> a is bFalse # Tạo 2 objects riêng biệt
# Mỗi lần Enter là 1 statement riêngScript File (.py)
Phần tiêu đề “Script File (.py)”# Trong file test.pya = 257b = 257print(a is b) # True!
# Compile-time optimization# Compiler thấy cùng 1 số trong cùng scope → dùng chungTại sao khác nhau?
Phần tiêu đề “Tại sao khác nhau?”# Interactive: Mỗi dòng compile riêng>>> a = 257 # Compile và execute>>> b = 257 # Compile và execute (tạo object mới)
# File: Cả file compile 1 lần# Compiler optimize: "257 xuất hiện 2 lần trong cùng scope"# → Dùng chung 1 objectKiểm tra với id()
Phần tiêu đề “Kiểm tra với id()”# Kiểm tra địa chỉ bộ nhớa = 256b = 256print(f"a: {id(a)}") # Ví dụ: 4352451504print(f"b: {id(b)}") # 4352451504 - GIỐNG NHAU!
x = 257y = 257print(f"x: {id(x)}") # Ví dụ: 140234567890print(f"y: {id(y)}") # 140234567891 - KHÁC NHAU! (trong REPL)Scope ảnh hưởng đến caching
Phần tiêu đề “Scope ảnh hưởng đến caching”Cùng scope - Có thể optimize
Phần tiêu đề “Cùng scope - Có thể optimize”def test_same_scope(): a = 1000 b = 1000 print(a is b) # True - Cùng scope, compile-time optimization
test_same_scope()Khác scope - Không optimize
Phần tiêu đề “Khác scope - Không optimize”def create_number(): return 1000
a = create_number()b = create_number()print(a is b) # False - Khác scope, tạo mới mỗi lầnVí dụ thực tế
Phần tiêu đề “Ví dụ thực tế”Example 1: Loop indices
Phần tiêu đề “Example 1: Loop indices”# Tại sao loop nhanh với range nhỏfor i in range(100): # i từ 0-99, tất cả đều cached # Không tạo object mới # So sánh i rất nhanh passExample 2: Status codes
Phần tiêu đề “Example 2: Status codes”HTTP_OK = 200 # CachedHTTP_NOT_FOUND = 404 # Ngoài cache
# Nhiều requests với status 200# → Tất cả dùng chung 1 object → Tiết kiệm RAMresponses = [{"status": 200} for _ in range(1000)]Example 3: Array indexing
Phần tiêu đề “Example 3: Array indexing”arr = [1, 2, 3, 4, 5]
# Indices 0-4 đều cachedfor i in range(len(arr)): # i = 0, 1, 2, 3, 4 print(arr[i]) # Không tạo object mới cho iKhi nào dùng is vs == với integers?
Phần tiêu đề “Khi nào dùng is vs == với integers?”✅ Luôn dùng == cho integers
Phần tiêu đề “✅ Luôn dùng == cho integers”# ✅ ĐÚNG - So sánh giá trịage = 25if age == 18: print("Just turned adult")
count = 1000if count == 1000: print("Reached milestone")❌ KHÔNG dùng is với integers
Phần tiêu đề “❌ KHÔNG dùng is với integers”# ❌ SAI - Unreliablescore = get_score() # Returns 100if score is 100: # Bug! Có thể False nếu 100 từ function print("Perfect!")
# ✅ ĐÚNGif score == 100: print("Perfect!")⚠️ Exception: So sánh với -5 đến 256 (không khuyến khích)
Phần tiêu đề “⚠️ Exception: So sánh với -5 đến 256 (không khuyến khích)”# Technically works nhưng KHÔNG NÊN làmcount = 0if count is 0: # Works vì 0 được cached print("Empty")
# ✅ Tốt hơnif count == 0: # Rõ ràng, an toàn hơn print("Empty")Lỗi phổ biến
Phần tiêu đề “Lỗi phổ biến”Bug 1: Dùng is thay vì ==
Phần tiêu đề “Bug 1: Dùng is thay vì ==”# SAIdef check_status_code(code): if code is 200: # ❌ Works với 200 nhưng unreliable return "OK" elif code is 404: # ❌ Works với 404 nhưng unreliable return "Not Found"
# Có thể fail nếu code từ computationstatus = 100 + 100 # = 200print(check_status_code(status)) # Có thể không work
# ĐÚNGdef check_status_code(code): if code == 200: # ✅ return "OK" elif code == 404: # ✅ return "Not Found"Bug 2: Rely on caching behavior
Phần tiêu đề “Bug 2: Rely on caching behavior”# SAI - Assume numbers được cacheddef optimize_comparison(n): magic_number = 100 if n is magic_number: # ❌ Unreliable logic return "Magic!"
# Có thể failresult = calculate_something() # Returns 100print(optimize_comparison(result)) # Có thể không matchBug 3: Singleton pattern với integers
Phần tiêu đề “Bug 3: Singleton pattern với integers”# SAI - Không nên làmclass Counter: _instance = 0 # ❌ Dùng int làm singleton?
def __new__(cls): if cls._instance is 0: # ❌ Bug potential cls._instance = 1 return super().__new__(cls) return None
# ĐÚNG - Dùng None cho singletonclass Counter: _instance = None # ✅
def __new__(cls): if cls._instance is None: # ✅ cls._instance = super().__new__(cls) return cls._instanceTesting caching behavior
Phần tiêu đề “Testing caching behavior”def test_integer_caching(): print("Testing integer caching:")
# Test cached range for n in [-6, -5, 0, 1, 100, 256, 257]: a = n b = n is_same = a is b cached = "CACHED" if is_same else "NOT CACHED" print(f"{n:4d}: {cached}")
# Run testtest_integer_caching()Output (trong interactive mode):
Testing integer caching: -6: NOT CACHED -5: CACHED 0: CACHED 1: CACHED 100: CACHED 256: CACHED 257: NOT CACHEDCPython Implementation Detail
Phần tiêu đề “CPython Implementation Detail”# Trong CPython source code (C):# Small integers array#define NSMALLNEGINTS 5 # -5 đến -1#define NSMALLPOSINTS 257 # 0 đến 256
# Pre-allocated khi Python khởi độngstatic PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];⚠️ Lưu ý: Đây là CPython implementation detail, không phải Python language specification. Các implementations khác (PyPy, Jython, IronPython) có thể khác.
Tác động performance
Phần tiêu đề “Tác động performance”Với cached integers
Phần tiêu đề “Với cached integers”import timeit
# Fast - Cacheddef test_cached(): a = 100 b = 100 return a is b
print(timeit.timeit(test_cached, number=10000000))# Rất nhanh - chỉ so sánh pointerVới non-cached integers
Phần tiêu đề “Với non-cached integers”# Slower - Not cacheddef test_not_cached(): a = 1000 b = 1000 return a is b
print(timeit.timeit(test_not_cached, number=10000000))# Có thể tạo objects mới (tùy context)Best practice: Luôn dùng ==
Phần tiêu đề “Best practice: Luôn dùng ==”# Consistent performancedef test_equality(): a = 1000 b = 1000 return a == b
# Luôn reliable, performance vẫn tốtTóm tắt
Phần tiêu đề “Tóm tắt”| Số | Cached? | is behavior |
Lý do |
|---|---|---|---|
| -6 | ❌ | False | Ngoài range |
| -5 | ✅ | True | Trong range |
| 0 | ✅ | True | Trong range |
| 100 | ✅ | True | Trong range |
| 256 | ✅ | True | Trong range |
| 257 | ⚠️ | Tùy context | Ngoài range |
| 1000 | ⚠️ | Tùy context | Ngoài range |
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 == cho integersif count == 100: # ✅ pass
if status == 200: # ✅ pass
# 2. Chỉ dùng is với Noneif value is None: # ✅ pass
# 3. Hiểu caching để debug, không để rely on# Debug: Tại sao a is b?# Answer: Có thể do caching❌ Tránh làm
Phần tiêu đề “❌ Tránh làm”# 1. Dùng is với integersif x is 100: # ❌ pass
# 2. Rely on caching behaviorif a is b: # ❌ Với integers print("Same value")
# 3. Assume caching range# ❌ Không assume implementation detailsGhi nhớ
Phần tiêu đề “Ghi nhớ”Integer caching (-5 to 256) là optimization, không phải feature!
- ✅ Luôn dùng
==để so sánh integers- ❌ Đừng dùng
isvới integers- ⚠️ Caching behavior là CPython implementation detail
- 🔍 Range: -5 đến 256 (inclusive)
Quy tắc vàng:
is→ Identity (cùng object?)==→ Equality (cùng giá trị?)
Với integers, hầu như luôn muốn dùng ==!