ترفندهای دیکشنری پایتون که همیشه باید به خاطر داشته باشید
دیکشنریها در پایتون یکی از پرکاربردترین ساختارهای داده هستند. از تنظیمات پروژه گرفته تا پردازش دادههای JSON و کار با پاسخهای API، تقریبا هر جایی سر و کلهشان پیدا میشود. اما اکثر برنامهنویسهای تازهکار فقط با مبانی آشنا میشوند؛ ساختن یک دیکشنری، دسترسی به یک کلید، و آپدیت کردن یک مقدار. همین و نه بیشتر.
در حالی که دیکشنریهای پایتون خیلی بیشتر از اینها دارند. در این مقاله ۷ تکنیک کاربردی را بررسی میکنیم که کدت را تمیزتر، کوتاهتر و پایتونیکتر میکنند. اگر این الگوها را بلد باشی، نه تنها کدت خواناتر میشود، بلکه از خیلی از باگهای رایج هم در امان میمانی.
استفاده از .get() به جای [] برای دسترسی به کلیدها
فرض کن داری با یک دیکشنری کار میکنی و میخواهی به یک مقدار دسترسی پیدا کنی. اما اگر آن کلید وجود نداشته باشد چه اتفاقی میافتد؟ مثلا یک دیکشنری از تنظیمات داری و میخواهی کلید timeout را چاپ کنی:
config = {"debug": True, "verbose": False}
print(config["timeout"])
خروجی:
KeyError: 'timeout'
این کد خراب میشود. یک KeyError میگیری چون timeout در دیکشنری نیست. به جای این روش، باید از متد .get() استفاده کنی. هم امنتر است و هم میتوانی یک مقدار پیشفرض برای حالتی که کلید وجود ندارد تعریف کنی:
config = {"debug": True, "verbose": False}
print(config.get("timeout", 30))
خروجی:
30
این کد عدد ۳۰ را چاپ میکند که همان مقدار پیشفرضی است که تعریف کردیم. البته یک نکته مهم هم هست: اگر نبودن یک کلید نشانهی یک باگ است، از همان براکت معمولی استفاده کن. میخواهی آن خطا همان لحظه نمایش داده شود، نه اینکه پنهان بماند.
استفاده از defaultdict برای گروهبندی دادهها
فرض کن یک لیست از کلمات داری و میخواهی بشماری هر کلمه چند بار تکرار شده. معمولا اینطور کد میزنی:
words = ["apple", "banana", "apple", "cherry", "banana", "banana"]
counts = {}
for word in words:
if word not in counts:
counts[word] = 0
counts[word] += 1
print(counts)
خروجی:
{'apple': 2, 'banana': 3, 'cherry': 1}
کد کار میکند، اما کمی پر حرف است. هر بار باید بررسی کنی که کلید وجود دارد یا نه و بعد مقدار اولیه را بگذاری. defaultdict پایتون این مشکل را حل میکند:
from collections import defaultdict
words = ["apple", "banana", "apple", "cherry", "banana", "banana"]
counts = defaultdict(int)
for word in words:
counts[word] += 1
print(counts)
خروجی:
defaultdict(<class 'int'>, {'apple': 2, 'banana': 3, 'cherry': 1})
چون از defaultdict(int) استفاده کردیم، پایتون به صورت خودکار برای هر کلید جدیدی که اضافه میشود مقدار پیشفرض ۰ میگذارد. دیگر نیازی به بررسی دستی نیست.
ادغام دیکشنریها با عملگر |
در نسخههای مدرن پایتون، تمیزترین روش برای ادغام دو دیکشنری استفاده از عملگر | است:
defaults = {"color": "blue", "size": "medium"}
overrides = {"size": "large", "weight": "heavy"}
merged = defaults | overrides
print(merged)
خروجی:
{'color': 'blue', 'size': 'large', 'weight': 'heavy'}
وقتی دو کلید یکسان وجود داشته باشد، دیکشنری سمت راست برنده میشود. اگر میخواهی ادغام را درجا (in-place) انجام دهی، از عملگر |= استفاده کن:
defaults |= overrides
print(defaults)
خروجی:
{'color': 'blue', 'size': 'large', 'weight': 'heavy'}
این روش از پایتون ۳.۹ به بعد در دسترس است و نسبت به روشهای قدیمیتر مثل {**a, **b} خیلی خواناتر است.
آنپک کردن دیکشنریها به عنوان آرگومان تابع
فرض کن یک تابع داری و یک دیکشنری که کلیدهایش با پارامترهای تابع مطابقت دارند. به جای اینکه هر کلید را جداگانه پاس بدهی، میتوانی از عملگر ** استفاده کنی:
def create_user(name, age, role="viewer"):
return {"name": name, "age": age, "role": role}
user_data = {
"name": "David",
"age": 33
}
روش معمولی:
user = create_user(
name=user_data["name"],
age=user_data["age"]
)
print(user)
روش با **:
print(create_user(**user_data))
خروجی در هر دو حالت:
{'name': 'David', 'age': 33, 'role': 'viewer'}
یک نکته مهم هم اینجا وجود دارد: در روش معمولی اگر سعی کنی به کلیدی دسترسی داشته باشی که در دیکشنری نیست (مثل role)، یک KeyError میگیری. اما روش ** به صورت هوشمندانه مقدار پیشفرض تابع را جایگزین میکند. هم تمیزتر است، هم مقاومتر.
استفاده از عملگر Walrus با دیکشنریها
پایتون ۳.۸ عملگر Walrus یا := را معرفی کرد که اجازه میدهد مقداردهی و استفاده از یک مقدار را در یک خط انجام بدهی. این ویژگی وقتی با دیکشنریها ترکیب میشود خیلی کاربردی میشود.
فرض کن میخواهی دادههای کاربر را از یک دیکشنری تودرتو بخوانی:
data = {
"user": {
"name": "Bryan",
"email": "bryan@gmail.com"
}
}
if data.get("user") is not None:
user = data.get("user")
name = user.get("name")
print(name)
خروجی:
Bryan
این کد کار میکند، اما دو بار همان جستجو را در دیکشنری انجام میدهد. با عملگر Walrus میتوانی این را خلاصه کنی:
if (user := data.get("user")) is not None:
name = user.get("name")
print(name)
خروجی:
Bryan
جستجو و مقداردهی در یک قدم انجام میشود. این ویژگی به خصوص وقتی با ساختارهای تودرتو کار میکنی خیلی به کار میآید و کد را فشردهتر میکند.
استفاده از TypedDict برای دادههای ساختاریافته
دیکشنریها انعطاف زیادی دارند، اما همین انعطاف گاهی دردسرساز میشود. مثلا:
def greet(user):
return f"Hello, {user['name']}!"
user = {
"name": "Clair",
"age": "thirty"
}
print(greet(user))
خروجی:
Hello, Clair!
کد اجرا میشود، اما یک مشکل پنهان وجود دارد: فیلد age قرار است عدد باشد، نه رشته. پایتون خودش هیچ اعتراضی نمیکند و این میتواند در پروژههای بزرگتر به باگهای عجیب منجر شود.
TypedDict ساختار مورد انتظار دیکشنری را صریح تعریف میکند:
from typing import TypedDict
class UserProfile(TypedDict):
name: str
age: int
def greet(user: UserProfile) -> str:
return f"Hello, {user['name']}!"
حالا ابزارهایی مثل mypy میتوانند قبل از اجرای کد خطاها را شناسایی کنند:
user: UserProfile = {
"name": "Clair",
"age": "thirty",
}
print(greet(user))
خروجی mypy:
error: Incompatible types (expression has type "str", TypedDict item "age" has type "int")
Found 1 error in 1 file (checked 1 source file)
برای اعتبارسنجیهای پیچیدهتر، ابزارهایی مثل dataclasses یا Pydantic معمولا انتخاب بهتری هستند.
تکرار راحت با .items()، .keys() و .values()
دیکشنریهای پایتون متدهای داخلی خوبی برای تکرار دارند. اکثر توسعهدهندهها از آنها خبر دارند، اما به اندازه کافی استفاده نمیکنند. خیلیها اینطور حلقه میزنند:
scores = {
"David": 92,
"Bryan": 87,
"Clair": 95
}
for name in scores:
print(name, scores[name])
خروجی:
David 92
Bryan 87
Clair 95
کار میکند، اما بهترین روش نیست. هر بار که به scores[name] دسترسی داری، یک جستجوی اضافه در دیکشنری انجام میدهی. متد .items() هم کلید و هم مقدار را با هم برمیگرداند:
for name, score in scores.items():
print(name, score)
خروجی:
David 92
Bryan 87
Clair 95
این روش جستجوی اضافه را حذف میکند و کد را خواناتر میکند. اگر فقط به کلیدها نیاز داری، از .keys() استفاده کن. اگر فقط مقادیر برایت مهم است، .values() همان چیزی است که میخواهی.
سخن پایانی
دیکشنریهای پایتون در نگاه اول ساده به نظر میرسند، اما یاد گرفتن چند الگوی کلیدی میتواند کد تو را به طرز چشمگیری بهتر کند. متدهایی مثل .get()، استفاده از defaultdict، آنپک کردن دیکشنریها در تابع، و تعریف ساختار با TypedDict همه ابزارهایی هستند که کد تکراری را کاهش میدهند و برنامه را قابل اطمینانتر میکنند.
دفعه بعدی که داشتی با دیکشنری کار میکردی، یه لحظه صبر کن و فکر کن آیا یکی از این تکنیکها میتواند کدت را سادهتر کند. احتمالا جوابت مثبت است.