لیست، دیکشنری و مجموعه
دادهی چندتایی را نگه دار: لیست (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 “تشبیه: قفسه، دفترچه تلفن و کیسهی مهره”لیست مثل یک قفسه با خانههای شمارهدار است: هر چیز جای ثابتی دارد (خانهی ۰، ۱، ۲…) و ترتیب مهم است. دیکشنری مثل دفترچه تلفن است: دنبال «علی» میگردی، نه دنبال «سطر سوم». مجموعه مثل کیسهی مهرههای رنگی است: فقط مهم است چه رنگهایی داخلش هست؛ هر رنگ یک بار، بدون ترتیب.
مثالهای عملی
Section titled “مثالهای عملی”مثال ۱: اولین لیست
Section titled “مثال ۱: اولین لیست”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 “مثال ۲: مرتبسازی، حساب و برش”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 کن» آنقدر رایج است که پایتون یک شکل کوتاه برایش دارد:
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 | db1split متن را با یک جداکننده میشکند و لیست میدهد؛ join (که روی جداکننده صدا زده میشود) اعضای یک لیست متنی را با آن جداکننده به هم میچسباند. این دو برای کار با خطهای فایل و CSV خیلی به کار میآیند.
مثال ۵: شماره و عضو با هم، enumerate
Section titled “مثال ۵: شماره و عضو با هم، enumerate”for i, name in enumerate(["web1", "web2", "db1"], start=1): print(i, name)1 web12 web23 db1enumerate در هر دور دو چیز میدهد: شماره و عضو. start=1 یعنی شمارش از یک (برای نمایش به آدمها).
بعد از nums = [3, 1, 2] و x = sorted(nums)، مقدار nums چیست؟
جواب درست: [3, 1, 2]
sorted یک لیست جدید برمیگرداند و اصلی را دست نمیزند؛ nums.sort() خود لیست را عوض میکرد.
مثال ۶: دیکشنری، مقدار با اسم
Section titled “مثال ۶: دیکشنری، مقدار با اسم”user = {"name": "Ali", "age": 30, "city": "Tehran"}print(user)print(user["name"], user["age"])user["age"] = 31user["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 FalseNone نداردname: Aliage: 31city: Tehranemail: ali@example.com['name', 'age', 'city']- دیکشنری با
{ }و جفتهایکلید: مقدارساخته میشود. d["کلید"]مقدار را میدهد. دادن مقدار به کلیدی که هست آن را عوض میکند، و به کلیدی که نیست، آن را اضافه میکند.inروی دیکشنری کلیدها را میسنجد.get(کلید, پیشفرض)امن است: اگر کلید نبود، بهجای خطاNoneیا مقدار پیشفرض را میدهد.items()در هر دور کلید و مقدار را با هم میدهد؛keys()وvalues()فقط یکی را.del d["کلید"]یک جفت را حذف میکند.
مثال ۷: شمردن با دیکشنری
Section titled “مثال ۷: شمردن با دیکشنری”مهمترین کاربرد روزمرهی دیکشنری: شمارش. کدهای وضعیت یک لاگ:
log = ["200", "404", "200", "500", "200", "404"]counts = {}for code in log: counts[code] = counts.get(code, 0) + 1print(counts)for code, n in sorted(counts.items()): print(code, n){'200': 3, '404': 2, '500': 1}200 3404 2500 1خط وسط را اینطور بخوان: «تعداد فعلی این کد را بگیر (اگر نبود صفر)، یکی اضافه کن و برگردان». این همان کاری است که در لینوکس با sort | uniq -c میکردی، حالا داخل یک برنامه.
مثال ۸: مجموعه، حذف تکراریها
Section titled “مثال ۸: مجموعه، حذف تکراریها”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 = [ {"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 برای لیست کپی نمیسازد؛ فقط اسم دوم به همان لیست میدهد (اشتباهات رایج ۴).
جدولهای مرجع
Section titled “جدولهای مرجع”| لیست | کار |
|---|---|
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 |
اشتراک / اجتماع / تفاضل |
اشتباهات رایج
Section titled “اشتباهات رایج”۱) اندیس بیرون از لیست
Section titled “۱) اندیس بیرون از لیست”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] است.
۲) کلیدی که نیست
Section titled “۲) کلیدی که نیست”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 = ab.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 = [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 را در درس تابع میبینی.)
دیدن جواب
text = "the cat and the dog and the bird"counts = {}for w in text.split(): counts[w] = counts.get(w, 0) + 1for word, n in sorted(counts.items(), key=lambda kv: kv[1], reverse=True): print(word, n)the 3and 2cat 1dog 1bird 1split() بدون آرگومان با هر تعداد فاصله میشکند. برای کلمههای با تعداد برابر، sorted ترتیب اولیه را نگه میدارد.
دو لیست داری: کاربرهایی که دیروز وارد شدند و کاربرهایی که امروز وارد شدند. با مجموعهها بگو: چه کسانی هر دو روز آمدند، چه کسانی فقط امروز (کاربر جدید)، و تعداد کل کاربرهای متفاوت. خروجی را مرتب چاپ کن تا هر بار یکسان باشد.
دیدن جواب
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']تعداد کل: 5sorted روی مجموعه یک لیست مرتب برمیگرداند؛ برای همین خروجی هر بار یکسان است (خود مجموعه ترتیب ثابتی ندارد). ali دو بار در دیروز آمده، ولی مجموعه یک بار نگهش داشت.
آزمونک
Section titled “آزمونک”servers = ["a", "b", "c"]؛ servers[-1] چیست؟
جواب درست: "c"
-1 یعنی عضو آخر.
فرق sorted(x) و x.sort()؟
جواب درست: sorted لیست جدید برمیگرداند؛ x.sort() خود x را مرتب میکند
x.sort() مقدار None برمیگرداند.
برای خواندن کلیدی که شاید نباشد، بدون خطا؟
جواب درست: d.get("k")
d["k"] اگر کلید نباشد KeyError میدهد.
len(set(["a", "b", "a", "c", "b"])) چند است؟
جواب درست: 3
مجموعه تکراریها را حذف میکند.
بعد از a = [1]؛ b = a؛ b.append(2)، مقدار a چیست؟
جواب درست: [1, 2]
a و b به یک لیست اشاره میکنند؛ برای کپی a.copy().
کدام برای «آیا این IP در فهرست یک میلیونی هست؟» سریعتر است؟
جواب درست: set
set و dict با hash مستقیم پیدا میکنند.
جمعبندی
Section titled “جمعبندی”- لیست
[ ]: ترتیب دارد، با اندیس از صفر، قابلتغییر (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 | اشتراک / اجتماع / تفاضل |