Dictionary trong Python (dict): tạo, phương thức, duyệt, sắp xếp và frozendict
Dictionary trong Python (dict) là tập hợp có thể thay đổi gồm các cặp «khóa — giá trị», tìm giá trị theo khóa gần như tức thì; được tạo bằng dấu ngoặc nhọn {"cà phê": 250} hoặc hàm dict(). Khóa phải là duy nhất và băm được; từ Python 3.7 thứ tự thêm vào được giữ nguyên, còn Python 3.15 bổ sung kiểu bất biến tích hợp frozendict.
Dictionary trong Python (dict) là tập hợp các cặp «khóa — giá trị», trong đó giá trị được tìm theo khóa gần như tức thì, dù có bao nhiêu bản ghi. Khi cần tính chi tiêu theo danh mục, lưu cấu hình hay tìm người dùng theo id, bạn sẽ dùng dict. Dưới đây là tất cả những gì cần biết để làm việc với python dict: cách tạo, các phương thức, duyệt, sắp xếp, gộp và frozendict — tính năng mới trong 3.15.
Dictionary trong Python là gì
Dict lưu các cặp, trong đó mỗi khóa là duy nhất: nếu ghi giá trị vào một khóa đã tồn tại, giá trị cũ sẽ bị thay thế. Bên trong, dict là một bảng băm, nên tìm kiếm, thêm và xóa theo khóa đều hoạt động trung bình trong thời gian hằng số, không cần duyệt từng phần tử.
Khóa phải là một đối tượng băm được: chuỗi, số, tuple gồm các giá trị bất biến. List không thể làm khóa — sẽ gặp TypeError: unhashable type: 'list'. Còn giá trị thì có thể là bất kỳ thứ gì, kể cả dict hay list khác.
Từ Python 3.7, dict đảm bảo nhớ thứ tự thêm vào: khi duyệt, bạn sẽ nhận các khóa theo đúng thứ tự đã thêm.
Cách tạo dictionary
Có nhiều cách tạo dict, và tất cả đều xuất hiện trong code thực tế.
spent = {"Ăn uống": 1200, "Taxi": 450} # literal
empty = {} # dict rỗng
also_empty = dict() # cũng vậy, qua hàm dict
user = dict(name="Lan", age=30) # tham số có tên
pairs = dict([("a", 1), ("b", 2)]) # từ danh sách các cặp
keys = dict.fromkeys(["x", "y"], 0) # {'x': 0, 'y': 0}
Lưu ý: cặp dấu ngoặc nhọn rỗng {} tạo ra một dict rỗng, không phải set. Set rỗng chỉ tạo được bằng set().
Cách tiện để tạo dict từ hai danh sách là zip:
names = ["Ăn uống", "Taxi", "Xem phim"]
sums = [1200, 450, 600]
spent = dict(zip(names, sums))
print(spent) # {'Ăn uống': 1200, 'Taxi': 450, 'Xem phim': 600}
Truy cập theo khóa: d[k] và get
Giá trị được đọc bằng dấu ngoặc vuông. Nếu khóa không tồn tại, Python sẽ báo KeyError. Để tránh lỗi, dùng phương thức get: nó trả về None hoặc một giá trị mặc định.
spent = {"Ăn uống": 1200}
print(spent["Ăn uống"]) # 1200
print(spent.get("Xem phim")) # None
print(spent.get("Xem phim", 0)) # 0
spent["Xem phim"] # KeyError: 'Xem phim'
Kiểm tra khóa có trong dict hay không bằng toán tử in: "Ăn uống" in spent trả về True. Toán tử này chỉ xét khóa, không xét giá trị. Để tìm theo giá trị, viết 1200 in spent.values().
Thêm, sửa và xóa
Thêm hoặc cập nhật một bản ghi chỉ cần một câu gán. Kết hợp với get rất tiện cho việc đếm:
spent = {}
for category, amount in [("Ăn uống", 250), ("Taxi", 300), ("Ăn uống", 400)]:
spent[category] = spent.get(category, 0) + amount
print(spent) # {'Ăn uống': 650, 'Taxi': 300}
Xóa khóa bằng del d[k] (gây KeyError nếu khóa không có) hoặc d.pop(k, default) — cách này còn trả về giá trị đã xóa. Xóa toàn bộ dict bằng d.clear(). Độ dài của dict, tức số cặp, là len(d).
Bảng phương thức của dict
| Phương thức | Tác dụng |
|---|---|
get(k, default) | giá trị theo khóa, hoặc default mà không gây lỗi |
setdefault(k, default) | trả về giá trị; nếu khóa chưa có thì ghi default trước |
update(other) | thêm và ghi đè các cặp từ dict khác |
pop(k, default) | xóa khóa và trả về giá trị |
popitem() | xóa và trả về cặp được thêm vào gần nhất |
keys(), values(), items() | các view của khóa, giá trị và cặp |
copy() | bản sao nông |
clear() | xóa toàn bộ dict |
fromkeys(keys, value) | tạo dict mới với cùng một giá trị |
setdefault: điền dict bằng danh sách
Phương thức setdefault rất hữu ích khi giá trị là danh sách và bạn cần nhóm dữ liệu:
groups = {}
for item, cat in [("cà phê", "Ăn uống"), ("tàu điện", "Đi lại"), ("pizza", "Ăn uống")]:
groups.setdefault(cat, []).append(item)
print(groups) # {'Ăn uống': ['cà phê', 'pizza'], 'Đi lại': ['tàu điện']}
update: cập nhật dict
update thay đổi dict tại chỗ: khóa mới được thêm vào, khóa trùng sẽ bị ghi đè.
cfg = {"debug": False, "lang": "vi"}
cfg.update({"debug": True, "theme": "dark"})
print(cfg) # {'debug': True, 'lang': 'vi', 'theme': 'dark'}
defaultdict: dict với giá trị mặc định
collections.defaultdict là lớp con của dict, khi truy cập một khóa chưa có, nó tự tạo giá trị bằng một hàm factory. Code đếm và nhóm nhờ đó ngắn hơn.
from collections import defaultdict
spent = defaultdict(int)
for cat, amount in [("Ăn uống", 250), ("Taxi", 300), ("Ăn uống", 400)]:
spent[cat] += amount
print(dict(spent)) # {'Ăn uống': 650, 'Taxi': 300}
groups = defaultdict(list)
groups["Ăn uống"].append("cà phê")
Để đếm số lần xuất hiện đơn giản, còn có collections.Counter: Counter(["a", "b", "a"]) cho Counter({'a': 2, 'b': 1}).
Duyệt dictionary: vòng lặp for
Duyệt dict bằng vòng lặp for mặc định là duyệt theo khóa. Để nhận cả khóa và giá trị, hãy duyệt items() — đây là cách phổ biến nhất.
spent = {"Ăn uống": 650, "Taxi": 300}
for cat in spent: # các khóa
print(cat)
for amount in spent.values(): # các giá trị
print(amount)
for cat, amount in spent.items(): # các cặp
print(f"{cat}: {amount} ₫")
Không được thay đổi kích thước dict ngay trong lúc duyệt — sẽ có RuntimeError: dictionary changed size during iteration. Nếu cần xóa một số khóa, hãy duyệt trên bản sao: for k in list(d):.
In dictionary ra màn hình
print(d) in dict trên một dòng. Với dict lớn hoặc lồng nhau, tiện hơn là module pprint hoặc json.dumps(d, ensure_ascii=False, indent=2) — kết quả có thụt đầu dòng và tiếng Việt không bị mã hóa thành ký tự escape.
Sắp xếp dictionary
Bản thân dict không được sắp xếp tại chỗ, nó không có phương thức sort. Sắp xếp dict nghĩa là tạo một dict mới từ các cặp đã sắp xếp bằng sorted.
spent = {"Taxi": 300, "Ăn uống": 650, "Xem phim": 120}
by_key = dict(sorted(spent.items()))
print(by_key) # {'Ăn uống': 650, 'Taxi': 300, 'Xem phim': 120}
by_value = dict(sorted(spent.items(), key=lambda p: p[1], reverse=True))
print(by_value) # {'Ăn uống': 650, 'Taxi': 300, 'Xem phim': 120}
Nếu chỉ cần top N danh mục, đừng chuyển kết quả trở lại thành dict: sorted(spent.items(), key=lambda p: p[1], reverse=True)[:2] trả về một danh sách các cặp.
Để sắp xếp một danh sách dict theo một trường: sorted(users, key=lambda u: u["age"]).
Gộp và cộng dictionary
Từ Python 3.9, dict có thể gộp bằng toán tử |, còn |= cập nhật dict tại chỗ. Khi trùng khóa, toán hạng bên phải thắng.
a = {"x": 1, "y": 2}
b = {"y": 20, "z": 3}
print(a | b) # {'x': 1, 'y': 20, 'z': 3}
print({**a, **b}) # cùng kết quả, qua giải nén
a |= b # tương đương a.update(b)
Lưu ý: a + b với dict không hoạt động, sẽ gây TypeError. Nếu muốn cộng giá trị theo các khóa trùng nhau, dùng Counter: Counter(a) + Counter(b).
Giải nén dictionary
Hai dấu sao ** giải nén dict thành các tham số có tên của hàm hoặc thành một dict khác:
def greet(name, city):
return f"{name} đến từ {city}"
data = {"name": "Lan", "city": "Hà Nội"}
print(greet(**data)) # Lan đến từ Hà Nội
Dict comprehension
Dict comprehension tạo một dict từ bất kỳ dãy nào chỉ trong một dòng:
squares = {n: n * n for n in range(5)}
print(squares) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
big = {k: v for k, v in spent.items() if v > 200}
inverted = {v: k for k, v in {"a": 1, "b": 2}.items()}
print(inverted) # {1: 'a', 2: 'b'}
Ví dụ cuối là cách đảo dict, hoán đổi khóa và giá trị cho nhau. Cách này chỉ đúng khi các giá trị là duy nhất và băm được; nếu không, một số cặp sẽ bị mất.
Dictionary lồng nhau
Giá trị có thể là một dict khác — đây là cách mô tả các cấu trúc như phản hồi JSON của API hay dict của dict người dùng.
users = {
101: {"name": "Lan", "skills": ["python", "sql"]},
102: {"name": "Nam", "skills": ["go"]},
}
print(users[101]["skills"][0]) # python
print(users.get(103, {}).get("name")) # None, không có KeyError
Sao chép dictionary: bản sao và tên thứ hai
Câu lệnh b = a không sao chép dict, nó chỉ gắn thêm một tên thứ hai vào cùng đối tượng. Thay đổi qua b sẽ thấy trong a. Điều tương tự xảy ra khi truyền dict vào hàm: hàm nhận tham chiếu tới đối tượng dùng chung và có thể làm hỏng nó mà không báo lỗi.
import copy
a = {"limits": [0, 500]}
b = a.copy() # bản sao nông
b["limits"].append(1000) # danh sách bên trong vẫn dùng chung!
print(a) # {'limits': [0, 500, 1000]}
c = copy.deepcopy(a) # bản sao độc lập hoàn toàn
So sánh dictionary
Toán tử == so sánh dict theo nội dung: chúng bằng nhau khi mọi cặp trùng khớp. Thứ tự khóa không được xét: {"a": 1, "b": 2} == {"b": 2, "a": 1} cho True. Để tìm các khóa chung, dùng phép toán tập hợp trên các view: a.keys() & b.keys().
Dictionary có kiểu: chú thích
Chú thích kiểu cho dict viết như dict[str, int]. Nếu dict có một tập trường cố định với các kiểu khác nhau, tiện hơn là TypedDict từ module typing:
from typing import TypedDict
class User(TypedDict):
name: str
age: int
u: User = {"name": "Lan", "age": 30}
Chú thích kiểu không được kiểm tra khi chạy chương trình; chúng được IDE và các công cụ phân tích như mypy đọc.
Dictionary bất biến: frozendict trong Python 3.15
Python 3.15 bổ sung kiểu tích hợp frozendict (PEP 814): không cần import, và cố gắng sửa nó sẽ gây TypeError ngay lập tức. Trước đây, dict bất biến phải làm bằng một lớp bọc từ module types hoặc gói bên thứ ba. Đây chính xác là thứ cần cho các cấu hình dùng chung mà không hàm nào được âm thầm thay đổi.
config = frozendict(min_amount=0, max_amount=500)
config["max_amount"] = 1000
# TypeError: 'frozendict' object does not support item assignment
strict = config | {"max_amount": 300} # frozendict mới, bản gốc vẫn nguyên
cache = {config: "báo cáo tháng 5"} # băm được — dùng làm khóa được
Những điều cần biết, theo tài liệu của khóa học:
frozendictkế thừa từobject, không kế thừa từdict, nênisinstance(cfg, dict)sẽ trả vềFalse. Để kiểm tra «bất kỳ dict nào», dùngcollections.abc.Mappinghoặcisinstance(x, (dict, frozendict)).- Nó chỉ băm được khi các khóa và giá trị đều băm được:
frozendict(a=[1])vẫn tạo được, nhưnghash()sẽ lỗi. Tính bất biến là nông, giống như tuple. - Thứ tự được giữ, nhưng
==không xét đến nó. json.dumps,picklevàcopyđều hỗ trợ frozendict.
Lỗi thường gặp
- KeyError khi đọc — dùng
gethoặc kiểm tra bằngin. - List làm khóa — đổi thành tuple:
d[tuple(lst)]. - Thay đổi dict trong vòng lặp — duyệt trên
list(d). - Kỳ vọng phép gán tạo bản sao — hãy dùng
copy()hoặcdeepcopy. {}thay cho set rỗng — đó là dict rỗng.
Tổng kết
Dict trong Python là cấu trúc dữ liệu chủ đạo của ngôn ngữ: tìm kiếm nhanh theo khóa, giữ thứ tự thêm vào, các phương thức tiện dụng như get, setdefault, update, duyệt qua items(), sắp xếp bằng sorted và gộp bằng toán tử |. Hãy nhớ về việc sao chép và khóa phải băm được; với cấu hình trong Python 3.15, dùng frozendict. Dict, list, tuple và set được thực hành trong khóa học «Python 3.15: từ người mới đến chuyên nghiệp» qua ví dụ trình theo dõi chi tiêu.
Câu hỏi thường gặp
Một dict có thể chứa giá trị thuộc nhiều kiểu khác nhau không?
Có, giá trị có thể là bất kỳ và khác kiểu nhau trong cùng một dict: số, chuỗi, list, dict khác. Ràng buộc chỉ áp dụng cho khóa — chúng phải băm được.
Làm sao biết số phần tử trong dict?
Hàm len(d) trả về số cặp khóa — giá trị. Với dict rỗng nó trả về 0, và dict rỗng trong điều kiện if được coi là sai.
dict khác defaultdict ở điểm nào?
defaultdict là lớp con của dict, khi truy cập khóa chưa có nó không gây KeyError mà tạo giá trị bằng một factory như int hoặc list. Ngoài ra nó hoạt động như dict thông thường.
Làm sao lấy khóa theo giá trị?
Không có phương thức trực tiếp, cần duyệt: next((k for k, v in d.items() if v == value), None). Nếu việc này lặp lại thường xuyên, tốt hơn là tạo dict đảo ngược bằng {v: k for k, v in d.items()}.
Làm sao lưu dict vào tệp?
Thường dùng module json: json.dump(d, f, ensure_ascii=False, indent=2) ghi vào tệp mở để ghi, và json.load(f) để đọc. Khi đó các khóa sẽ thành chuỗi.
Chủ đề này được giảng chi tiết kèm thực hành trong khóa học “Python 3.15: от новичка до профи”.
Đến khóa học