# Dictionary trong Python (dict): tạo, phương thức, duyệt, sắp xếp và frozendict

URL: https://skillaria.top/vi/guides/python/dict-python-tu-dien

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:

- `frozendict` kế thừa từ `object`, không kế thừa từ `dict`, nên `isinstance(cfg, dict)` sẽ trả về `False`. Để kiểm tra «bất kỳ dict nào», dùng `collections.abc.Mapping` hoặc `isinstance(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ưng `hash()` 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`, `pickle` và `copy` đều hỗ trợ frozendict.

## Lỗi thường gặp

- **KeyError** khi đọc — dùng `get` hoặc kiểm tra bằng `in`.
- **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ặc `deepcopy`.
- **`{}` 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.

## FAQ

**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.


---
Source: Skillaria.Top (https://skillaria.top)
