{} là dict, không phải set!
Hiện tượng lạ
Phần tiêu đề “Hiện tượng lạ”# Tạo set với các phần tửmy_set = {1, 2, 3}print(type(my_set)) # <class 'set'>
# Vậy tạo set rỗng cũng dùng {}?empty = {}print(type(empty)) # <class 'dict'> <- What the Python?!Tại sao {1, 2, 3} là set nhưng {} lại là dict?! 🤔
Giải thích
Phần tiêu đề “Giải thích”Dictionary có trước, Set có sau
Phần tiêu đề “Dictionary có trước, Set có sau”Python được tạo ra năm 1991, Dictionary (dict) đã có từ đầu và sử dụng cú pháp {}.
Set (set) chỉ được thêm vào Python 2.4 (năm 2004), 13 năm sau!
# Dictionary - Có từ Python 1.0 (1991)my_dict = {"key": "value"}
# Set - Thêm vào Python 2.4 (2004)my_set = {1, 2, 3}{} đã được “chiếm chỗ” bởi dict
Phần tiêu đề “{} đã được “chiếm chỗ” bởi dict”Khi set được thêm vào, {} đã được dùng cho dict rỗng. Để tránh breaking changes (phá vỡ code cũ), Python giữ nguyên {} = dict rỗng.
Cách tạo set và dict rỗng
Phần tiêu đề “Cách tạo set và dict rỗng”Dictionary rỗng
Phần tiêu đề “Dictionary rỗng”# Cách 1: Dùng {}empty_dict = {}print(type(empty_dict)) # <class 'dict'>
# Cách 2: Dùng dict()empty_dict = dict()print(type(empty_dict)) # <class 'dict'>
# Cả 2 cách đều OKSet rỗng
Phần tiêu đề “Set rỗng”# KHÔNG THỂ dùng {}empty_set = {} # ❌ Đây là dict, không phải set!
# PHẢI dùng set()empty_set = set()print(type(empty_set)) # <class 'set'> ✅
# Đây là CÁCH DUY NHẤT để tạo set rỗngSo sánh Set và Dict
Phần tiêu đề “So sánh Set và Dict”Cú pháp với elements
Phần tiêu đề “Cú pháp với elements”# Set - chỉ có valuesmy_set = {1, 2, 3}print(my_set) # {1, 2, 3}
# Dict - có key:value pairsmy_dict = {"a": 1, "b": 2, "c": 3}print(my_dict) # {'a': 1, 'b': 2, 'c': 3}
# Python phân biệt bằng dấu :# - Không có : → Set# - Có : → DictCú pháp rỗng
Phần tiêu đề “Cú pháp rỗng”# Set rỗng - PHẢI dùng constructorempty_set = set()
# Dict rỗng - Dùng {} hoặc constructorempty_dict = {}empty_dict = dict() # Cũng OKCác trường hợp dễ nhầm
Phần tiêu đề “Các trường hợp dễ nhầm”1. Khởi tạo set rỗng
Phần tiêu đề “1. Khởi tạo set rỗng”# SAI - Vô tình tạo dictdef process_unique_items(): seen = {} # ❌ Đây là dict! seen.add(1) # AttributeError: 'dict' object has no attribute 'add'
# ĐÚNGdef process_unique_items(): seen = set() # ✅ seen.add(1) # OK!2. Default value trong function
Phần tiêu đề “2. Default value trong function”# SAI - Default là dict, không phải setdef track_ids(new_ids, existing={}): # ❌ {} là dict existing.add(new_ids) # Lỗi!
# ĐÚNGdef track_ids(new_ids, existing=None): if existing is None: existing = set() # ✅ existing.add(new_ids)3. Type annotation
Phần tiêu đề “3. Type annotation”from typing import Set, Dict
# Nếu dùng type hints, dễ phát hiện lỗidef get_unique_items() -> Set[int]: return {} # ❌ Type checker sẽ báo lỗi: Expected Set, got Dict
# ĐÚNGdef get_unique_items() -> Set[int]: return set() # ✅Tại sao cần set rỗng?
Phần tiêu đề “Tại sao cần set rỗng?”Use case 1: Tracking unique items
Phần tiêu đề “Use case 1: Tracking unique items”def find_unique_chars(text): unique = set() # Bắt đầu với set rỗng for char in text: unique.add(char) return unique
print(find_unique_chars("hello")) # {'h', 'e', 'l', 'o'}Use case 2: Filtering duplicates
Phần tiêu đề “Use case 2: Filtering duplicates”def remove_duplicates(items): seen = set() # Set rỗng để track result = []
for item in items: if item not in seen: seen.add(item) result.append(item)
return result
print(remove_duplicates([1, 2, 2, 3, 1, 4])) # [1, 2, 3, 4]Use case 3: Set operations từ rỗng
Phần tiêu đề “Use case 3: Set operations từ rỗng”# Bắt đầu với set rỗng, sau đó unionall_tags = set()
for article in articles: all_tags = all_tags.union(article.tags)
# Hoặcall_tags = set()for article in articles: all_tags |= set(article.tags) # Union assignmentCách debug và phát hiện
Phần tiêu đề “Cách debug và phát hiện”1. Kiểm tra type
Phần tiêu đề “1. Kiểm tra type”mystery = {}
# Debugprint(f"Type: {type(mystery)}") # <class 'dict'>print(f"Is dict: {isinstance(mystery, dict)}") # Trueprint(f"Is set: {isinstance(mystery, set)}") # False2. Lỗi khi dùng set methods
Phần tiêu đề “2. Lỗi khi dùng set methods”data = {}
# Nếu vô tình tạo dict thay vì setdata.add(1) # AttributeError: 'dict' object has no attribute 'add'# → Lỗi này cho biết data là dict, không phải set!3. Dùng IDE/Linter
Phần tiêu đề “3. Dùng IDE/Linter”# PyCharm, VS Code với Pylint sẽ cảnh báounique_items: Set[int] = {} # Warning: Expected Set, got dictSo sánh đầy đủ
Phần tiêu đề “So sánh đầy đủ”| Mục đích | Cú pháp | Type | Ví dụ |
|---|---|---|---|
| Set rỗng | set() |
set | s = set() |
| Set có phần tử | {1, 2, 3} |
set | s = {1, 2, 3} |
| Dict rỗng | {} hoặc dict() |
dict | d = {} |
| Dict có phần tử | {"a": 1} |
dict | d = {"a": 1} |
Set comprehension vs Dict comprehension
Phần tiêu đề “Set comprehension vs Dict comprehension”# Set comprehension - Không có :squares_set = {x**2 for x in range(5)}print(squares_set) # {0, 1, 4, 9, 16}print(type(squares_set)) # <class 'set'>
# Dict comprehension - Có :squares_dict = {x: x**2 for x in range(5)}print(squares_dict) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}print(type(squares_dict)) # <class 'dict'>
# Comprehension rỗng?# - Set: {x for x in []} → set() (rỗng)# - Dict: {x: x for x in []} → {} (rỗng)Bảng tổng hợp methods
Phần tiêu đề “Bảng tổng hợp methods”Set methods (không có trong dict)
Phần tiêu đề “Set methods (không có trong dict)”s = set()s.add(1) # ✅ Set cós.remove(1) # ✅ Set cós.discard(1) # ✅ Set cós.union({2, 3}) # ✅ Set cóDict methods (không có trong set)
Phần tiêu đề “Dict methods (không có trong set)”d = {}d["key"] = "value" # ✅ Dict cód.get("key") # ✅ Dict cód.keys() # ✅ Dict cód.values() # ✅ Dict cód.items() # ✅ Dict có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 set() cho set rỗngunique_items = set() # ✅ Rõ ràng
# 2. Dùng type hintsfrom typing import Set
def get_tags() -> Set[str]: return set() # ✅
# 3. Kiểm tra type khi debugif not isinstance(data, set): raise TypeError(f"Expected set, got {type(data)}")❌ Tránh làm
Phần tiêu đề “❌ Tránh làm”# 1. Dùng {} cho set rỗngunique_items = {} # ❌ Nhầm lẫn!
# 2. Không kiểm tra typedef process(data): data.add(1) # ❌ Nếu data là dict sẽ lỗi
# 3. Mixed types không rõ ràngdef create_collection(is_unique): return set() if is_unique else {} # ❌ ConfusingLịch sử Python - Tại sao như vậy?
Phần tiêu đề “Lịch sử Python - Tại sao như vậy?”Timeline
Phần tiêu đề “Timeline”- 1991: Python ra đời, dict dùng
{} - 2004: Python 2.4 - Set được thêm vào
- Vấn đề:
{}đã được dùng cho dict rỗng - Giải pháp:
- Giữ
{}= dict rỗng (backward compatibility) - Dùng
set()cho set rỗng - Dùng
{1, 2, 3}cho set có phần tử (phân biệt bằng không có:)
- Giữ
Lý do không thay đổi
Phần tiêu đề “Lý do không thay đổi”# Nếu thay đổi {} thành set:# - Hàng triệu dòng code cũ sẽ bị hỏng# - Dict rỗng phải dùng dict() (không tiện)# - Breaking change quá lớnTóm tắt
Phần tiêu đề “Tóm tắt”| Cú pháp | Kiểu | Ghi chú |
|---|---|---|
{} |
dict | Dict rỗng |
{1, 2, 3} |
set | Set với phần tử |
{"a": 1} |
dict | Dict với key:value |
set() |
set | Set rỗng (cách duy nhất) |
dict() |
dict | Dict rỗng (giống {}) |
Ghi nhớ
Phần tiêu đề “Ghi nhớ”
{}là dict, không phải set!
- ✅ Set rỗng:
set()(cách DUY NHẤT)- ✅ Dict rỗng:
{}hoặcdict()- 🔍 Python phân biệt set/dict bằng dấu
:
- Không có
:→ Set:{1, 2, 3}- Có
:→ Dict:{"a": 1, "b": 2}
Tip: Nhớ rằng dictionary “có trước” nên “chiếm” được {}!