Fix RuntimeError: dictionary changed size during iteration trong Python

beginner🐍 Python2026-03-23| Python 3.x (tất cả nền tảng: Linux, macOS, Windows)

Error Message

RuntimeError: dictionary changed size during iteration
#python#runtimeerror#dictionary#iteration#collections

Lỗi Gặp Phải

Bạn đang lặp qua một dictionary và xóa hoặc thêm key ngay trong vòng lặp đó. Python sẽ ném ra lỗi ngay lập tức:

RuntimeError: dictionary changed size during iteration

Đây là ví dụ điển hình — lọc bỏ các giá trị chẵn bằng cách xóa key:

data = {'a': 1, 'b': 2, 'c': 3, 'd': 4}

for key in data:
    if data[key] % 2 == 0:
        del data[key]  # RuntimeError xảy ra ở đây

Thêm key trong khi đang lặp cũng gặp vấn đề tương tự:

cache = {'x': 10}

for key in cache:
    cache[key + '_copy'] = cache[key]  # RuntimeError: dictionary changed size during iteration

Nguyên Nhân

Câu lệnh for key in dict của Python tạo ra một iterator nội bộ theo dõi kích thước của dictionary. Khi bạn thêm hoặc xóa một key, kích thước thay đổi. Python phát hiện điều này ngay lập tức và ném ra RuntimeError — đây là hành vi có chủ đích.

Nếu không có cơ chế bảo vệ này, iterator có thể bỏ qua key, duyệt qua cùng một key nhiều lần, hoặc gặp lỗi không thể đoán trước. Python 3 biến điều này thành lỗi cứng. Python 2 cho phép làm điều này mà không báo lỗi, dẫn đến dữ liệu bị hỏng gần như không thể debug được.

Lưu ý quan trọng: thay đổi giá trị của một key đã tồn tại là hoàn toàn hợp lệ. Chỉ có thêm hoặc xóa key (làm thay đổi kích thước dict) mới kích hoạt lỗi này.

Cách Sửa Nhanh: Lặp Qua Bản Sao

Bọc đối tượng lặp trong list(). Cách này tạo một snapshot danh sách các key trước khi bắt đầu lặp — dict có thể thay đổi thoải mái bên dưới mà không ảnh hưởng.

data = {'a': 1, 'b': 2, 'c': 3, 'd': 4}

for key in list(data.keys()):  # list() tạo một snapshot
    if data[key] % 2 == 0:
        del data[key]

print(data)  # {'a': 1, 'c': 3}

Cần cả key lẫn value? Cách tương tự áp dụng với .items():

for key, value in list(data.items()):
    if value % 2 == 0:
        del data[key]

Chỉ thay đổi một dòng, không cần học thêm khái niệm mới. Phù hợp để vá nhanh trên code hiện có.

Cách Sửa Gọn Hơn: Dict Comprehension

Với các trường hợp lọc dữ liệu, hãy bỏ qua việc thay đổi trực tiếp và xây dựng một dict mới:

data = {'a': 1, 'b': 2, 'c': 3, 'd': 4}

data = {k: v for k, v in data.items() if v % 2 != 0}

print(data)  # {'a': 1, 'c': 3}

Không thay đổi trực tiếp nghĩa là không có nguy cơ gặp lỗi. Code cũng dễ đọc hơn — điều kiện nằm ngay trong biểu thức. Đây là cách được khuyến nghị khi lọc dictionary trong Python 3.

Cách Sửa Khi Thêm Key Trong Vòng Lặp

Cần tạo các entry mới trong khi đang xử lý các entry hiện có? Hãy gom chúng vào một dict riêng, rồi merge sau khi vòng lặp kết thúc.

cache = {'x': 10, 'y': 20}

# Sai: thêm vào cache ngay trong vòng lặp
# for key in cache:
#     cache[key + '_copy'] = cache[key]  # RuntimeError

# Đúng: tạm thời lưu các entry mới ở chỗ khác
new_entries = {}
for key, value in cache.items():
    new_entries[key + '_copy'] = value

cache.update(new_entries)
print(cache)  # {'x': 10, 'y': 20, 'x_copy': 10, 'y_copy': 20}

Vòng lặp chỉ đọc từ cache. new_entries tiếp nhận toàn bộ thao tác ghi. Sau đó một lệnh update() duy nhất merge tất cả một cách an toàn.

Cách Sửa Khi Đổi Tên Key

Đổi tên key phức tạp hơn — bạn vừa thêm vừa xóa. Hãy dùng mô hình thu thập trước, áp dụng sau:

config = {'debug_mode': True, 'debug_level': 3, 'timeout': 30}

# Đổi tên tất cả key bắt đầu bằng 'debug_' → 'log_'
to_rename = {k: k.replace('debug_', 'log_') for k in config if k.startswith('debug_')}

for old_key, new_key in to_rename.items():
    config[new_key] = config.pop(old_key)

print(config)  # {'timeout': 30, 'log_mode': True, 'log_level': 3}

Lần đầu: xác định những gì cần đổi tên (không thay đổi gì). Lần hai: áp dụng việc đổi tên. Tách biệt rõ ràng, không có xung đột iterator.

Dictionary Lồng Nhau

Thay đổi dict bên trong trong khi lặp qua dict bên ngoài là hợp lệ — iterator ngoài không quan tâm. Nhưng nếu chính dict ngoài thay đổi kích thước, bạn sẽ gặp lỗi như thường:

users = {
    'alice': {'score': 10, 'active': True},
    'bob': {'score': 5, 'active': False},
    'charlie': {'score': 8, 'active': True},
}

# Xóa người dùng không hoạt động — an toàn vì chúng ta copy key ngoài trước
for name in list(users):
    if not users[name]['active']:
        del users[name]

print(users)  # {'alice': {...}, 'charlie': {...}}

Kiểm Tra Kết Quả

Sau khi áp dụng cách sửa, chạy code và kiểm tra kết quả có đúng như mong đợi không:

data = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
data = {k: v for k, v in data.items() if v % 2 != 0}
assert data == {'a': 1, 'c': 3}, f"Kết quả không như mong đợi: {data}"
print("OK:", data)

Kết quả mong đợi:

OK: {'a': 1, 'c': 3}

Không có RuntimeError, key và value đúng — bạn đã xong.

Tóm Tắt

  • Không bao giờ thêm hoặc xóa key khỏi dict trong khi đang lặp trực tiếp qua nó.
  • Sửa nhanh: bọc đối tượng lặp trong list() — ví dụ: for key in list(data).
  • Cách sửa được ưu tiên khi lọc: dùng dict comprehension để tạo dict mới thay vì thay đổi dict cũ.
  • Khi thêm key: gom các entry mới vào một dict riêng, rồi gọi .update() sau vòng lặp.
  • Khi đổi tên key: xây dựng bản đồ đổi tên trước, áp dụng trong lần duyệt thứ hai.

Related Error Notes