List nhân với số - [[]] * 3 có gì lạ?
Hiện tượng lạ
Phần tiêu đề “Hiện tượng lạ”Bạn muốn tạo một lưới (grid) 3x3 để chơi tic-tac-toe:
# Tạo 3 hàng, mỗi hàng có 3 ôboard = [[0, 0, 0]] * 3print(board)# [[0, 0, 0], [0, 0, 0], [0, 0, 0]] <- Trông có vẻ đúng!
# Đánh dấu ô (0, 0) là Xboard[0][0] = "X"print(board)# [['X', 0, 0], ['X', 0, 0], ['X', 0, 0]] <- What the Python?!Tại sao thay đổi 1 ô lại ảnh hưởng đến cả 3 hàng?! 😱
Giải thích
Phần tiêu đề “Giải thích”List multiplication tạo references, không phải copies!
Phần tiêu đề “List multiplication tạo references, không phải copies!”Khi bạn dùng [[0, 0, 0]] * 3, Python không tạo 3 list riêng biệt. Thay vào đó, nó tạo 3 references (tham chiếu) đến cùng 1 list!
# Tạo listinner = [0, 0, 0]board = [inner] * 3
# Cả 3 phần tử đều trỏ đến cùng 1 objectprint(board[0] is board[1]) # Trueprint(board[1] is board[2]) # Trueprint(board[0] is board[2]) # True
# ID (địa chỉ bộ nhớ) giống nhauprint(id(board[0])) # Ví dụ: 140234567890print(id(board[1])) # 140234567890 - GIỐNG NHAU!print(id(board[2])) # 140234567890 - GIỐNG NHAU!Minh họa bằng hình
Phần tiêu đề “Minh họa bằng hình”# Bạn nghĩ sẽ là:board[0] → [0, 0, 0] (list riêng)board[1] → [0, 0, 0] (list riêng)board[2] → [0, 0, 0] (list riêng)
# Thực tế là:board[0] ┐board[1] ├→ [0, 0, 0] (cùng 1 list!)board[2] ┘So sánh các trường hợp
Phần tiêu đề “So sánh các trường hợp”1. Với immutable objects (int, str, tuple) - OK
Phần tiêu đề “1. Với immutable objects (int, str, tuple) - OK”# Nhân với int - Không saonumbers = [0] * 5print(numbers) # [0, 0, 0, 0, 0]
numbers[0] = 1print(numbers) # [1, 0, 0, 0, 0] - Chỉ thay đổi 1 phần tử ✅
# Tại sao OK?# Vì int là immutable, không thể thay đổi được# Khi gán numbers[0] = 1, Python tạo reference mới, không sửa object cũ# Nhân với string - Không saowords = ["hello"] * 3print(words) # ['hello', 'hello', 'hello']
words[0] = "world"print(words) # ['world', 'hello', 'hello'] ✅
# String cũng immutable nên OK2. Với mutable objects (list, dict) - NGUY HIỂM!
Phần tiêu đề “2. Với mutable objects (list, dict) - NGUY HIỂM!”# Nhân với list - NGUY HIỂM!matrix = [[0] * 3] * 3 # [[0,0,0]] * 3matrix[0][0] = 1print(matrix)# [[1, 0, 0], [1, 0, 0], [1, 0, 0]] ❌ Tất cả đều thay đổi!
# Nhân với dict - NGUY HIỂM!users = [{}] * 3users[0]["name"] = "Alice"print(users)# [{'name': 'Alice'}, {'name': 'Alice'}, {'name': 'Alice'}] ❌Cách tạo list 2D đúng
Phần tiêu đề “Cách tạo list 2D đúng”❌ SAI - Dùng multiplication
Phần tiêu đề “❌ SAI - Dùng multiplication”# SAIboard = [[0] * 3] * 3✅ ĐÚNG - Dùng list comprehension
Phần tiêu đề “✅ ĐÚNG - Dùng list comprehension”# ĐÚNG - Mỗi hàng là list riêng biệtboard = [[0] * 3 for _ in range(3)]
# Hoặcboard = [[0 for _ in range(3)] for _ in range(3)]
# Testboard[0][0] = "X"print(board)# [['X', 0, 0], [0, 0, 0], [0, 0, 0]] ✅ Chỉ thay đổi 1 ô!
# Kiểm tra - các hàng là list riêng biệtprint(board[0] is board[1]) # False ✅✅ ĐÚNG - Dùng loop
Phần tiêu đề “✅ ĐÚNG - Dùng loop”# Tạo từng hàng riêng biệtboard = []for i in range(3): row = [0] * 3 # Tạo list mới mỗi lần board.append(row)✅ ĐÚNG - Dùng copy.deepcopy
Phần tiêu đề “✅ ĐÚNG - Dùng copy.deepcopy”import copy
# Tạo templaterow = [0, 0, 0]board = [copy.deepcopy(row) for _ in range(3)]Ví dụ thực tế - Bugs phổ biến
Phần tiêu đề “Ví dụ thực tế - Bugs phổ biến”Bug 1: Tic-Tac-Toe board
Phần tiêu đề “Bug 1: Tic-Tac-Toe board”# SAIclass TicTacToe: def __init__(self): self.board = [[" "] * 3] * 3 # BUG!
def mark(self, row, col, player): self.board[row][col] = player
game = TicTacToe()game.mark(0, 0, "X")print(game.board)# [['X', ' ', ' '], ['X', ' ', ' '], ['X', ' ', ' ']] ❌
# ĐÚNGclass TicTacToe: def __init__(self): self.board = [[" " for _ in range(3)] for _ in range(3)]Bug 2: Tracking student scores
Phần tiêu đề “Bug 2: Tracking student scores”# SAI - Tạo danh sách điểm cho 5 học sinhscores = [[]] * 5
# Thêm điểm cho học sinh đầu tiênscores[0].append(95)scores[0].append(87)
print(scores)# [[95, 87], [95, 87], [95, 87], [95, 87], [95, 87]] ❌# Tất cả học sinh có cùng điểm!
# ĐÚNGscores = [[] for _ in range(5)]scores[0].append(95)scores[0].append(87)print(scores)# [[95, 87], [], [], [], []] ✅Bug 3: Shopping carts
Phần tiêu đề “Bug 3: Shopping carts”# SAIcarts = [{"items": []}] * 3
# Khách hàng 0 thêm sản phẩmcarts[0]["items"].append("Apple")
print(carts)# [{'items': ['Apple']}, {'items': ['Apple']}, {'items': ['Apple']}] ❌# Tất cả giỏ hàng đều có Apple!
# ĐÚNGcarts = [{"items": []} for _ in range(3)]# Hoặc tốt hơn:carts = [{"items": [].copy()} for _ in range(3)]Cách debug và phát hiện
Phần tiêu đề “Cách debug và phát hiện”1. Kiểm tra identity với is
Phần tiêu đề “1. Kiểm tra identity với is”# Kiểm tra xem các phần tử có phải cùng 1 object khônglst = [[]] * 3
if lst[0] is lst[1]: print("WARNING: Cùng reference!") # Sẽ in ra2. Kiểm tra id()
Phần tiêu đề “2. Kiểm tra id()”lst = [[]] * 3
print(f"ID of lst[0]: {id(lst[0])}")print(f"ID of lst[1]: {id(lst[1])}")print(f"ID of lst[2]: {id(lst[2])}")
# Nếu giống nhau → Cùng object!3. Test thay đổi
Phần tiêu đề “3. Test thay đổi”# Test bằng cách thay đổi 1 phần tửlst = [[0]] * 3lst[0].append(1)
if len(lst[1]) > 1: # Nếu lst[1] cũng thay đổi print("BUG: Shared reference!")Khi nào multiplication an toàn?
Phần tiêu đề “Khi nào multiplication an toàn?”✅ An toàn với immutable types
Phần tiêu đề “✅ An toàn với immutable types”# Int, float, str, tuple, frozenset, None - OK!zeros = [0] * 10 # ✅ OKspaces = [" "] * 5 # ✅ OKnones = [None] * 3 # ✅ OKtuples = [(1, 2)] * 4 # ✅ OK (tuple immutable)❌ Nguy hiểm với mutable types
Phần tiêu đề “❌ Nguy hiểm với mutable types”# List, dict, set, custom objects - NGUY HIỂM!lists = [[]] * 3 # ❌ NGUY HIỂMdicts = [{}] * 3 # ❌ NGUY HIỂMsets = [set()] * 3 # ❌ NGUY HIỂM
class User: pass
users = [User()] * 3 # ❌ NGUY HIỂM - Cùng 1 user!Best Practices
Phần tiêu đề “Best Practices”1. Luôn dùng list comprehension cho nested lists
Phần tiêu đề “1. Luôn dùng list comprehension cho nested lists”# ✅ LUÔN làm thế nàymatrix = [[0 for _ in range(cols)] for _ in range(rows)]
# ❌ KHÔNG làm thế nàymatrix = [[0] * cols] * rows2. Kiểm tra identity khi debug
Phần tiêu đề “2. Kiểm tra identity khi debug”# Khi debug nested structuresif any(lst[i] is lst[i+1] for i in range(len(lst)-1)): print("WARNING: Shared references detected!")3. Sử dụng factory functions
Phần tiêu đề “3. Sử dụng factory functions”# Tốt cho structures phức tạpdef create_row(): return [0, 0, 0]
board = [create_row() for _ in range(3)]# Mỗi lần gọi create_row() tạo list mớiTóm tắt
Phần tiêu đề “Tóm tắt”| Code | An toàn? | Lý do |
|---|---|---|
[0] * 5 |
✅ | int là immutable |
["hi"] * 3 |
✅ | string là immutable |
[None] * 4 |
✅ | None là immutable |
[(1,2)] * 3 |
✅ | tuple là immutable |
[[]] * 3 |
❌ | list là mutable - shared reference! |
[{}] * 3 |
❌ | dict là mutable - shared reference! |
[set()] * 3 |
❌ | set là mutable - shared reference! |
Ghi nhớ
Phần tiêu đề “Ghi nhớ”List multiplication (
* n) tạo n references đến cùng 1 object!
- ✅ An toàn với immutable objects (int, str, tuple)
- ❌ Nguy hiểm với mutable objects (list, dict, set)
- ✅ Luôn dùng list comprehension cho nested lists!
# Ghi nhớ công thức này:nested_list = [[initial_value for _ in range(cols)] for _ in range(rows)]