فصل ۴: ساختارهای داده — list، tuple، dict، set و collections

collections: Counter، defaultdict، namedtuple و deque

ابزارهای آماده برای کارهای تکراری

ماژول collections در کتابخانه‌ی استاندارد، نسخه‌های تخصصی dict، tuple و list را دارد که کدهای رایج را کوتاه‌تر، سریع‌تر و کم‌خطاتر می‌کنند. هر برنامه‌نویس پایتون باید این چهار ابزار را بشناسد.

Counter: شمارش در یک خط

from collections import Counter

sold = ["کاشان", "تبریز", "کاشان", "مشهد", "کاشان", "تبریز"]
c = Counter(sold)
print(c)                     # Counter({'کاشان': 3, 'تبریز': 2, 'مشهد': 1})
print(c.most_common(2))      # [('کاشان', 3), ('تبریز', 2)]
print(c["اصفهان"])           # 0 — کلید نبود، KeyError نمی‌دهد

c.update(["مشهد", "مشهد"])   # افزودن شمارش
words = Counter("فرش دستباف کاشان فرش ماشینی".split())
print(sum(c.values()))       # جمع کل

Counter از جمع و تفریق هم پشتیبانی می‌کند: farvardin + ordibehesht فروش دو ماه را جمع می‌کند و stock - sold موجودی باقی‌مانده را می‌دهد (نتیجه‌های صفر و منفی حذف می‌شوند).

defaultdict: دیکشنری با مقدار پیش‌فرض خودکار

from collections import defaultdict

by_city = defaultdict(list)
for city, code in [("کاشان", "K1"), ("تبریز", "T1"), ("کاشان", "K2")]:
    by_city[city].append(code)       # بدون get و setdefault

totals = defaultdict(int)            # int() یعنی 0
for city, amount in [("کاشان", 5), ("کاشان", 3)]:
    totals[city] += amount

آرگومان defaultdict یک تابع سازنده است (list، int، set)، نه یک مقدار. هر بار کلید ناموجودی خوانده شود، آن تابع صدا زده می‌شود.

namedtuple: tuple با نام فیلد

from collections import namedtuple

Rug = namedtuple("Rug", "code width length material")
r = Rug("KSH-101", 200, 300, "ابریشم")
print(r.width * r.length)            # 60000 — به‌جای r[1] * r[2]
print(r._asdict())                   # تبدیل به dict
r2 = r._replace(width=250)           # نسخه‌ی جدید؛ اصلی تغییرناپذیر است

namedtuple برای رکوردهای سبک و تغییرناپذیر عالی است. اگر به مقدار پیش‌فرض، متد یا تغییرپذیری نیاز داشتید، سراغ dataclass بروید (فصل ۷).

deque: صف دوطرفه

from collections import deque

recent = deque(maxlen=3)             # فقط سه عضو آخر نگه داشته می‌شود
for code in ["K1", "K2", "K3", "K4"]:
    recent.append(code)
print(recent)                        # deque(['K2', 'K3', 'K4'], maxlen=3)

queue = deque(["سفارش ۱", "سفارش ۲"])
queue.append("سفارش ۳")              # ورود از انتها
first = queue.popleft()              # خروج از ابتدا — سریع
ابزارجایگزین چه کدی
Counterd[k] = d.get(k, 0) + 1 در حلقه
defaultdictd.setdefault(k, []).append(v)
namedtupletuple با اندیس‌های جادویی r[2]
dequelist.pop(0) و list.insert(0, x)

نکته‌هایی که کمتر کسی می‌داند

  • خواندن کلید ناموجود از defaultdict آن کلید را می‌سازد؛ حتی یک print(d["x"]) ساده دیکشنری را بزرگ می‌کند. برای بررسی، از "x" in d استفاده کنید.
  • Counter("کاشان") حروف را می‌شمارد، نه کلمه را؛ برای کلمه اول split() بزنید.
  • deque(maxlen=n) ساده‌ترین راه ساختن «n رکورد آخر» یا تاریخچه‌ی محدود است؛ عضوهای قدیمی خودکار بیرون می‌افتند.
  • Counter.total() (3.10+) جمع همه‌ی شمارش‌ها را می‌دهد و از sum(c.values()) خواناتر است.
  • قبل از json.dump کردن defaultdict، آن را با dict(d) تبدیل کنید تا خروجی و repr تمیز باشد.

برای ذخیره‌ی پیشرفت و شرکت در آزمون، وارد شوید — رایگان است.