رفتن به محتوا
پایتون از صفراصول زباندرس ۴ از ۱۰

لیست، دیکشنری و مجموعه

داده‌ی چندتایی را نگه دار: لیست (append، remove، sort، برش، in)، حلقه روی لیست و enumerate، list comprehension، split و join؛ دیکشنری (کلید و مقدار، get، items)، شمردن با دیکشنری، مجموعه (set) برای حذف تکراری‌ها، tuple، و تله‌ی کپی لیست.

  • حدود ۱۸ دقیقه مطالعه
  • ۹ مثال اجراشده
  • ۳ تمرین با جواب
  • مقدماتی تا متوسط
  • به‌روزرسانی: ۲۹ مهر ۱۴۰۵

توی این درس یاد می‌گیری به‌جای ده‌ها متغیر جدا (server1، server2، …) داده‌ها را در ساختار نگه داری. لیست (list) فهرستی مرتب از مقدارهاست که می‌شود به آن اضافه و از آن کم کرد، مرتبش کرد و رویش حلقه زد. دیکشنری (dict) هر مقدار را با یک کلید (اسم) نگه می‌دارد، مثل {"name": "Ali", "age": 30}. مجموعه (set) تکراری‌ها را خودکار حذف می‌کند. با ترکیب این‌ها (لیستی از دیکشنری‌ها) تقریباً هر داده‌ای را می‌شود مدل کرد؛ همان شکلی که داده در JSON و APIها دارد.

مسئله: صد متغیر برای صد سرور؟

Section titled “مسئله: صد متغیر برای صد سرور؟”

با چیزهایی که تا اینجا یاد گرفتی، برای نگه داشتن اسم ۳ سرور باید ۳ متغیر بسازی. برای ۳۰۰ سرور چه؟ و اگر تعداد از قبل معلوم نباشد (مثلاً خط‌های یک فایل لاگ)؟ لازم است یک متغیر چند مقدار را با هم نگه دارد.

تشبیه: قفسه، دفترچه تلفن و کیسه‌ی مهره

Section titled “تشبیه: قفسه، دفترچه تلفن و کیسه‌ی مهره”

لیست مثل یک قفسه با خانه‌های شماره‌دار است: هر چیز جای ثابتی دارد (خانه‌ی ۰، ۱، ۲…) و ترتیب مهم است. دیکشنری مثل دفترچه تلفن است: دنبال «علی» می‌گردی، نه دنبال «سطر سوم». مجموعه مثل کیسه‌ی مهره‌های رنگی است: فقط مهم است چه رنگ‌هایی داخلش هست؛ هر رنگ یک بار، بدون ترتیب.

لیست با شماره (اندیس) دسترسی می‌دهد و ترتیب دارد؛ دیکشنری با کلید؛ مجموعه فقط عضویت را می‌داند و تکراری نگه نمی‌دارد.
list1.py
servers = ["web1", "web2", "db1"]
print(servers)
print(len(servers), servers[0], servers[-1])
servers.append("cache1")
print(servers)
servers.remove("web2")
print(servers)
servers.insert(0, "lb1")
print(servers)
print("db1" in servers, "web2" in servers)
for s in servers:
print("بررسی", s)
خروجی
['web1', 'web2', 'db1']
3 web1 db1
['web1', 'web2', 'db1', 'cache1']
['web1', 'db1', 'cache1']
['lb1', 'web1', 'db1', 'cache1']
True False
بررسی lb1
بررسی web1
بررسی db1
بررسی cache1
  • لیست با [ ] و ویرگول بین اعضا ساخته می‌شود.
  • شماره‌گذاری (اندیس) مثل متن از صفر است و [-1] عضو آخر.
  • append به آخر اضافه می‌کند، insert(i, x) در جای i، و remove(x) اولین x را برمی‌دارد.
  • in می‌گوید عضوی در لیست هست یا نه، و for روی هر عضو یک دور می‌زند.

برخلاف متن، لیست قابل‌تغییر (mutable) است: append و remove خود لیست را عوض می‌کنند و لازم نیست نتیجه را دوباره در متغیر بگذاری.

مثال ۲: مرتب‌سازی، حساب و برش

Section titled “مثال ۲: مرتب‌سازی، حساب و برش”
list2.py
nums = [42, 7, 19, 3, 25]
print(sorted(nums), nums)
nums.sort(reverse=True)
print(nums)
print(sum(nums), min(nums), max(nums), sum(nums) / len(nums))
print(nums[1:3])
خروجی
[3, 7, 19, 25, 42] [42, 7, 19, 3, 25]
[42, 25, 19, 7, 3]
96 3 42 19.2
[25, 19]

دو راه مرتب‌سازی هست و فرقشان مهم است: sorted(nums) یک لیست جدید مرتب برمی‌گرداند و اصلی دست‌نخورده می‌ماند؛ nums.sort() خود لیست را مرتب می‌کند (و چیزی برنمی‌گرداند). reverse=True یعنی بزرگ به کوچک. sum، min، max و len روی هر لیست عددی کار می‌کنند، و برش [1:3] مثل متن است.

مثال ۳: ساختن لیست از لیست، comprehension

Section titled “مثال ۳: ساختن لیست از لیست، comprehension”

الگوی «یک لیست خالی بساز و در حلقه append کن» آن‌قدر رایج است که پایتون یک شکل کوتاه برایش دارد:

comp.py
squares = [n * n for n in range(1, 6)]
print(squares)
nums = [42, 25, 19, 7, 3]
big = [n for n in nums if n > 10]
print(big)
خروجی
[1, 4, 9, 16, 25]
[42, 25, 19]

[n * n for n in range(1, 6)] را این‌طور بخوان: «برای هر n در ۱ تا ۵، n * n را در لیست بگذار». با if آخرش فقط عضوهایی می‌آیند که شرط را دارند. این شکل list comprehension نام دارد؛ اگر اول گیج‌کننده بود، همان حلقه‌ی معمولی هم کاملاً درست است.

مثال ۴: متن به لیست و برعکس، split و join

Section titled “مثال ۴: متن به لیست و برعکس، split و join”
words = "web1,web2,db1".split(",")
print(words, " | ".join(words))
خروجی
['web1', 'web2', 'db1'] web1 | web2 | db1

split متن را با یک جداکننده می‌شکند و لیست می‌دهد؛ join (که روی جداکننده صدا زده می‌شود) اعضای یک لیست متنی را با آن جداکننده به هم می‌چسباند. این دو برای کار با خط‌های فایل و CSV خیلی به کار می‌آیند.

مثال ۵: شماره و عضو با هم، enumerate

Section titled “مثال ۵: شماره و عضو با هم، enumerate”
for i, name in enumerate(["web1", "web2", "db1"], start=1):
print(i, name)
خروجی
1 web1
2 web2
3 db1

enumerate در هر دور دو چیز می‌دهد: شماره و عضو. start=1 یعنی شمارش از یک (برای نمایش به آدم‌ها).

⚡ بررسی سریع

بعد از nums = [3, 1, 2] و x = sorted(nums)، مقدار nums چیست؟

مثال ۶: دیکشنری، مقدار با اسم

Section titled “مثال ۶: دیکشنری، مقدار با اسم”
dict1.py
user = {"name": "Ali", "age": 30, "city": "Tehran"}
print(user)
print(user["name"], user["age"])
user["age"] = 31
user["email"] = "ali@example.com"
print(user)
print(len(user), "city" in user, "phone" in user)
print(user.get("phone"), user.get("phone", "ندارد"))
for key, value in user.items():
print(f"{key}: {value}")
del user["email"]
print(list(user.keys()))
خروجی
{'name': 'Ali', 'age': 30, 'city': 'Tehran'}
Ali 30
{'name': 'Ali', 'age': 31, 'city': 'Tehran', 'email': 'ali@example.com'}
4 True False
None ندارد
name: Ali
age: 31
city: Tehran
email: ali@example.com
['name', 'age', 'city']
  • دیکشنری با { } و جفت‌های کلید: مقدار ساخته می‌شود.
  • d["کلید"] مقدار را می‌دهد. دادن مقدار به کلیدی که هست آن را عوض می‌کند، و به کلیدی که نیست، آن را اضافه می‌کند.
  • in روی دیکشنری کلیدها را می‌سنجد.
  • get(کلید, پیش‌فرض) امن است: اگر کلید نبود، به‌جای خطا None یا مقدار پیش‌فرض را می‌دهد.
  • items() در هر دور کلید و مقدار را با هم می‌دهد؛ keys() و values() فقط یکی را.
  • del d["کلید"] یک جفت را حذف می‌کند.

مثال ۷: شمردن با دیکشنری

Section titled “مثال ۷: شمردن با دیکشنری”

مهم‌ترین کاربرد روزمره‌ی دیکشنری: شمارش. کدهای وضعیت یک لاگ:

count.py
log = ["200", "404", "200", "500", "200", "404"]
counts = {}
for code in log:
counts[code] = counts.get(code, 0) + 1
print(counts)
for code, n in sorted(counts.items()):
print(code, n)
خروجی
{'200': 3, '404': 2, '500': 1}
200 3
404 2
500 1

خط وسط را این‌طور بخوان: «تعداد فعلی این کد را بگیر (اگر نبود صفر)، یکی اضافه کن و برگردان». این همان کاری است که در لینوکس با sort | uniq -c می‌کردی، حالا داخل یک برنامه.

مثال ۸: مجموعه، حذف تکراری‌ها

Section titled “مثال ۸: مجموعه، حذف تکراری‌ها”
sets.py
ips = ["10.0.0.1", "10.0.0.2", "10.0.0.1", "10.0.0.3", "10.0.0.2"]
unique = set(ips)
print(unique)
print(len(ips), len(unique))
a = {"ali", "sara", "reza"}
b = {"sara", "mina"}
print(a & b, a | b, a - b)
خروجی (ترتیب اعضای مجموعه ممکن است فرق کند)
{'10.0.0.3', '10.0.0.1', '10.0.0.2'}
5 3
{'sara'} {'reza', 'sara', 'mina', 'ali'} {'reza', 'ali'}

set(لیست) تکراری‌ها را حذف می‌کند: ۵ درخواست از ۳ IP متفاوت. مجموعه ترتیب ندارد (برای همین اندیس [0] هم ندارد) و چاپش ممکن است هر بار ترتیب دیگری داشته باشد. عملگرهای مجموعه: & اشتراک (در هر دو)، | اجتماع (در هر کدام)، - تفاضل (در اولی و نه دومی).

یک ساختار چهارم هم زیاد می‌بینی: tuple با پرانتز، مثل point = (35.7, 51.4). مثل لیست است ولی غیرقابل‌تغییر؛ برای چیزهایی که نباید عوض شوند (مثل مختصات یک نقطه).

مثال ۹: لیستی از دیکشنری‌ها

Section titled “مثال ۹: لیستی از دیکشنری‌ها”

داده‌ی واقعی معمولاً این شکلی است (و دقیقاً همین شکل را در JSON و API می‌بینی):

servers.py
servers = [
{"name": "web1", "disk": 72},
{"name": "web2", "disk": 95},
{"name": "db1", "disk": 91},
]
for s in servers:
if s["disk"] > 90:
print(f"هشدار: دیسک {s['name']} {s['disk']}٪ پر است")
خروجی
هشدار: دیسک web2 95٪ پر است
هشدار: دیسک db1 91٪ پر است

هر عضو لیست یک دیکشنری است. داخل f-string که خودش با " شروع شده، کلید را با ' نوشتیم (s['name']) تا کوتیشن‌ها قاطی نشوند. ویرگول بعد از عضو آخر مجاز است و تغییرات بعدی را تمیزتر می‌کند.

پشت پرده: چرا دیکشنری این‌قدر سریع است؟

Section titled “پشت پرده: چرا دیکشنری این‌قدر سریع است؟”

برای پیدا کردن یک عضو در لیست، پایتون باید از اول یکی‌یکی نگاه کند؛ در یک لیست میلیونی یعنی تا میلیون مقایسه. دیکشنری و مجموعه از جدول درهم‌سازی (hash table) استفاده می‌کنند: از روی خود کلید یک عدد (hash) حساب می‌شود که مستقیم می‌گوید مقدار کجای حافظه است. پس "x" in my_dict تقریباً همیشه یک قدم است، هر چقدر هم بزرگ باشد. برای همین:

  • وقتی دنبال «آیا X هست؟» در داده‌ی زیاد می‌گردی، از set یا dict استفاده کن، نه list.
  • کلید دیکشنری و عضو مجموعه باید غیرقابل‌تغییر باشد (متن، عدد، tuple)؛ لیست نمی‌تواند کلید باشد، چون اگر عوض شود hashش هم عوض می‌شود.

یک نکته‌ی مهم‌تر هم درباره‌ی حافظه: b = a برای لیست کپی نمی‌سازد؛ فقط اسم دوم به همان لیست می‌دهد (اشتباهات رایج ۴).

لیست کار
x = [1, 2, 3] / [] ساختن / لیست خالی
x[0] / x[-1] / x[1:3] عضو اول / آخر / برش
x.append(v) / x.insert(i, v) اضافه به آخر / در جای i
x.remove(v) / x.pop() حذف اولین v / حذف و برگرداندن آخری
x.sort() / sorted(x) مرتب کردن خود لیست / لیست مرتب جدید
len، sum، min، max اندازه و حساب
v in x عضویت
", ".join(x) / s.split(",") لیست به متن / متن به لیست
دیکشنری کار
d = {"k": v} / {} ساختن / خالی
d["k"] / d.get("k", پیش‌فرض) خواندن (خطا اگر نبود) / خواندن امن
d["k"] = v افزودن یا عوض کردن
del d["k"] حذف
"k" in d آیا این کلید هست؟
d.keys() / d.values() / d.items() کلیدها / مقدارها / جفت‌ها
مجموعه کار
set(لیست) / {"a", "b"} ساختن (set() برای خالی، نه {})
s.add(v) افزودن
a & b / a | b / a - b اشتراک / اجتماع / تفاضل
nums = [1, 2, 3]
print(nums[3])
خروجی
Traceback (most recent call last):
File "/home/ali/py/idx.py", line 2, in <module>
print(nums[3])
~~~~^^^
IndexError: list index out of range

لیست سه‌عضوی اندیس‌های ۰ و ۱ و ۲ دارد. راه‌حل: آخرین عضو nums[-1] یا nums[len(nums) - 1] است.

user = {"name": "Ali"}
print(user["phone"])
خروجی
Traceback (most recent call last):
File "/home/ali/py/key.py", line 2, in <module>
print(user["phone"])
~~~~^^^^^^^^^
KeyError: 'phone'

راه‌حل: اگر ممکن است کلید نباشد، user.get("phone") یا اول if "phone" in user:.

۳) گذاشتن نتیجه‌ی sort() در متغیر

Section titled “۳) گذاشتن نتیجه‌ی sort() در متغیر”

x = nums.sort() باعث می‌شود x برابر None شود، چون sort چیزی برنمی‌گرداند. راه‌حل: یا nums.sort() تنها، یا x = sorted(nums).

۴) فکر کردن اینکه b = a کپی می‌سازد

Section titled “۴) فکر کردن اینکه b = a کپی می‌سازد”
a = [1, 2, 3]
b = a
b.append(4)
print(a)
c = a.copy()
c.append(5)
print(a, c)
خروجی
[1, 2, 3, 4]
[1, 2, 3, 4] [1, 2, 3, 4, 5]

b و a دو اسم برای یک لیست‌اند؛ تغییر یکی، دیگری را هم عوض می‌کند. راه‌حل: برای کپی واقعی a.copy() (یا list(a)).

۵) {} برای مجموعه‌ی خالی

Section titled “۵) {} برای مجموعه‌ی خالی”

{} یک دیکشنری خالی است، نه مجموعه. راه‌حل: مجموعه‌ی خالی را با set() بساز.

تمرینآسان

لیستی از نمره‌ها بساز ([17, 12, 19, 8, 15]) و میانگین، بیشترین و کمترین را چاپ کن. بعد نمره‌های قبولی (۱۰ و بالاتر) را با comprehension جدا کن.

دیدن جواب
scores.py
scores = [17, 12, 19, 8, 15]
print(f"میانگین: {sum(scores) / len(scores):.1f}")
print("بیشترین:", max(scores), "کمترین:", min(scores))
passed = [s for s in scores if s >= 10]
print("قبول‌ها:", passed)
خروجی
میانگین: 14.2
بیشترین: 19 کمترین: 8
قبول‌ها: [17, 12, 19, 15]
تمرینمتوسط

تمرین اصلی: یک متن طولانی (مثلاً "the cat and the dog and the bird") را به کلمه‌ها بشکن و با یک دیکشنری بشمار هر کلمه چند بار آمده. بعد کلمه‌ها را از پرتکرار به کم‌تکرار چاپ کن. (راهنما: sorted(counts.items(), key=lambda kv: kv[1], reverse=True) جفت‌ها را بر اساس مقدار مرتب می‌کند؛ lambda را در درس تابع می‌بینی.)

دیدن جواب
words.py
text = "the cat and the dog and the bird"
counts = {}
for w in text.split():
counts[w] = counts.get(w, 0) + 1
for word, n in sorted(counts.items(), key=lambda kv: kv[1], reverse=True):
print(word, n)
خروجی
the 3
and 2
cat 1
dog 1
bird 1

split() بدون آرگومان با هر تعداد فاصله می‌شکند. برای کلمه‌های با تعداد برابر، sorted ترتیب اولیه را نگه می‌دارد.

تمرینسخت

دو لیست داری: کاربرهایی که دیروز وارد شدند و کاربرهایی که امروز وارد شدند. با مجموعه‌ها بگو: چه کسانی هر دو روز آمدند، چه کسانی فقط امروز (کاربر جدید)، و تعداد کل کاربرهای متفاوت. خروجی را مرتب چاپ کن تا هر بار یکسان باشد.

دیدن جواب
users.py
yesterday = ["ali", "sara", "reza", "ali"]
today = ["sara", "mina", "reza", "omid"]
y = set(yesterday)
t = set(today)
print("هر دو روز:", sorted(y & t))
print("فقط امروز:", sorted(t - y))
print("تعداد کل:", len(y | t))
خروجی
هر دو روز: ['reza', 'sara']
فقط امروز: ['mina', 'omid']
تعداد کل: 5

sorted روی مجموعه یک لیست مرتب برمی‌گرداند؛ برای همین خروجی هر بار یکسان است (خود مجموعه ترتیب ثابتی ندارد). ali دو بار در دیروز آمده، ولی مجموعه یک بار نگهش داشت.

آزمونک
  1. servers = ["a", "b", "c"]؛ servers[-1] چیست؟

  2. فرق sorted(x) و x.sort()؟

  3. برای خواندن کلیدی که شاید نباشد، بدون خطا؟

  4. len(set(["a", "b", "a", "c", "b"])) چند است؟

  5. بعد از a = [1]؛ b = a؛ b.append(2)، مقدار a چیست؟

  6. کدام برای «آیا این IP در فهرست یک میلیونی هست؟» سریع‌تر است؟

  • لیست [ ]: ترتیب دارد، با اندیس از صفر، قابل‌تغییر (append، remove، sort)، for رویش.
  • sorted لیست جدید می‌دهد، .sort() خود لیست را عوض می‌کند؛ comprehension برای ساختن لیست از لیست.
  • دیکشنری {کلید: مقدار}: با کلید بخوان، get برای خواندن امن، items() برای حلقه؛ بهترین ابزار شمارش.
  • مجموعه set(): بدون تکرار و بدون ترتیب؛ &، |، -؛ عضویت را خیلی سریع می‌سنجد.
  • لیستی از دیکشنری‌ها شکل رایج داده‌ی واقعی (و JSON) است.
  • b = a کپی نیست؛ a.copy().
برگه‌ی تقلب این درس
دستورکاری که می‌کند
x = ["a", "b"]لیست
x.append(v) / x.remove(v)افزودن / حذف
sorted(x) / x.sort()مرتب جدید / مرتب کردن خود لیست
[f(n) for n in x if ...]ساختن لیست با comprehension
d = {"k": v}دیکشنری
d.get("k", 0)خواندن امن با پیش‌فرض
for k, v in d.items():حلقه روی دیکشنری
d[k] = d.get(k, 0) + 1الگوی شمارش
set(x)حذف تکراری‌ها
a & b / a | b / a - bاشتراک / اجتماع / تفاضل