پروژهی آخر: حساب خرجها (Final Project)
کل دوره رو تو یه برنامهی کوچیک کنار هم میذاری: خرجهات رو از یه فایل میخونی، با یه دیکشنری بر اساس دسته جمعشون میزنی و یه گزارش چاپ میکنی، قدمبهقدم و هر قدم در حال کار.
نقشه، قبل از هر کدی
- ورودی: یه فایل
expenses.txt، هر خط یه خرج، به شکل category,amount (دسته،مبلغ).
- پردازش: جمع هر دسته
هر خط رو جدا کن، مبلغ رو به عدد تبدیل کن، و توی یه دیکشنری به جمع همون دسته اضافهش کن.
- خروجی: یه گزارش
برای هر دسته یه خط، بعد جمع کل.
- قدمبهقدم بساز، بعد از هر قدم اجرا کن
اول یه خط رو بخون و جدا کن، بعد کل فایل رو حلقه بزن، بعد توی تابع بستهبندیش کن، بعد امکانات اضافه کن.
دادهها
چهار خط متن، جدا شده از روی کاما
فایل expenses.txt نگار چهار خط داره: food,12 و bus,3 و food,8 و rent,50. هر خط به شکل یه رشته خونده میشه که با \n تموم میشه. line.strip() اون \n رو برمیداره، و .split(",") رشته رو از جای کاما میبُره و یه لیست دوتایی از رشتهها میده. میتونی اون لیست رو مستقیم توی دو تا اسم باز کنی (unpack): category, amount = line.strip().split(","). مبلغ هنوز متنه، "12"، پس قبل از هر حسابی int() لازم داره.
"food,12\n" → strip → "food,12" → split(",") → ['food', '12'] → category = 'food'، amount = '12'.
خودت رو امتحان کن
print("food,12\n".strip().split(","))['food', '12']['food', 12]['food,12']['food', '12\n']
جواب رو ببین
['food', '12']درسته. strip اون \n رو برمیداره، split از کاما میبُره، و هر دو تیکه هنوز رشتهان، برای همین 12 توی گیومهست.
totals = {}
with open("expenses.txt") as f:
for line in f:
cat, amt = line.strip().split(",")
old = totals.get(cat, 0)
totals[cat] = old + int(amt)
print(totals){'food': 20, 'bus': 3, 'rent': 50}قدم ۲، با همون فایل چهارخطی. این همون الگوی شمردن درس ۹ـه، فقط بهجای 1 مبلغ رو اضافه میکنه: get(cat, 0) اولین باری که یه دسته میاد 0 میده، پس food اول میشه 12 و بعد 12 + 8 = 20.
خودت رو امتحان کن
بعد از اینکه حلقه food 12، bus 3، food 8 و rent 50 رو خوند، totals["food"] چنده؟
- 8
- 12
- 20
- "128"
جواب رو ببین
20
درسته. خط اول food اون رو 12 میکنه، و خط دوم 8 رو به همون قبلی اضافه میکنه: 20.
قدمبهقدم ببین

expenses.txt: چهار خط متن دادهها اول به شکل یه سند سادهان، expenses.txt، با چهار خط: food,12، bus,3، food,8 و rent,50. برای پایتون هر کدوم فقط یه متنه که مبلغش با رقم نوشته شده.

split(",") و int() جفت میسازن هر خط از میلهی split(",") رد میشه و میشه دو تا کاشی: یه دسته و یه مبلغ، food | 12، bus | 3، food | 8، rent | 50. مبلغها توی ستون int() هستن، با رنگ آبی، چون int() اونها رو از متن به عدد تبدیل کرده.

totals: food 20، bus 3، rent 50 جفتها میریزن توی دیکشنری totals. هر دو جفت food، یعنی 12 و 8، به یه ردیف food میرسن که 20 رو با نارنجی نشون میده؛ bus و rent هر کدوم یه جفت دارن، پس ردیفشون 3 و 50ـه. چهار خط شد سه دسته.

گزارش: food 20، bus 3، rent 50، جمع 73 جدول totals یه پنل گزارش رو سمت راست پر میکنه: food: 20، bus: 3، rent: 50، و بعد زیر یه خط جداکننده، total: 73 با نارنجی. فایل، split، int()، حلقه، دیکشنری و یه تابع چاپ، هر کدوم یه کار، همه با هم یه برنامه شدن.
خودت رو امتحان کن
totals = {}with open("expenses.txt") as f:for line in f:cat, amt = line.strip().split(",")old = totals.get(cat, 0)totals[cat] = old + amt
جواب رو ببین
اشکال توی خط ۶.
درسته. amt از split اومده، پس رشتهی "12"ـه، و 0 + "12" یه TypeErrorـه. بنویس old + int(amt).
قدم ۳: تابعها
دو تا تابع، دو تا کار
وقتی حلقه کار کرد، به هر کار یه اسم بده. load_totals(path) یه فایل رو میخونه و دیکشنری رو return میکنه؛ هیچی چاپ نمیکنه. print_report(totals) یه دیکشنری میگیره و چاپش میکنه؛ از فایل خبر نداره. جدا نگه داشتنشون یعنی میتونی هر کدوم رو جدا امتحان کنی، با همون کد یه فایل دیگه رو بخونی، یا از totalsی که یه جور دیگه ساختی گزارش بگیری. اونوقت خط آخر برنامه فقط میشه print_report(load_totals("expenses.txt")).
sum(totals.values()) مقدارهای یه دیکشنری رو جمع میزنه: 20 + 3 + 50 = 73.
def load_totals(path):
totals = {}
with open(path) as f:
for line in f:
cat, amt = line.strip().split(",")
old = totals.get(cat, 0)
totals[cat] = old + int(amt)
return totals
print(load_totals("expenses.txt")){'food': 20, 'bus': 3, 'rent': 50}همون حلقهی قدم ۲، که رفته داخل یه def و یه سطح بیشتر تورفتگی گرفته. بهجای چاپ کردن، دیکشنری رو return میکنه، پس print پایین فقط برای امتحان کردنشه.
def print_report(totals):
for cat, amt in totals.items():
print(f"{cat}: {amt}")
print("total:", sum(totals.values()))
totals = {"food": 20, "bus": 3, "rent": 50}
print_report(totals)food: 20
bus: 3
rent: 50
total: 73با یه دیکشنری که دستی نوشته شده امتحان شده، تا قبل از وصل کردنش به فایل بدونی گزارش کار میکنه. خط total بیرون حلقهی forـه، پس یه بار چاپ میشه.
خودت رو امتحان کن
خطها رو به ترتیبی بچین که دیکشنری totals رو از روی expenses.txt بسازن.
جواب رو ببین
totals = {}
with open("expenses.txt") as f:
for line in f:
cat, amt = line.strip().split(",")
totals[cat] = totals.get(cat, 0) + int(amt)def add_expense(path, cat, amt):
with open(path, "a") as f:
f.write(f"{cat},{amt}\n")
add_expense("expenses.txt", "tea", 4)
print_report(load_totals("expenses.txt"))food: 20
bus: 3
rent: 50
tea: 4
total: 77قدم ۴، با استفاده از load_totals و print_report که بالاتر نوشتیم. "a" خط tea,4 رو بهعنوان خط پنجم اضافه میکنه و به چهار خط دیگه دست نمیزنه؛ اگه اینجا "w" بود، نگار کل هفتهش رو از دست میداد. دو بار اجراش کنی، tea میشه 8.
rows = ["food,12", "", "food,abc"]
for row in rows:
if not row:
continue
cat, amt = row.split(",")
try:
print(cat, int(amt))
except ValueError:
print("bad line:", row)food 12
bad line: food,abcفایلهای واقعی خط خالی و غلط تایپی دارن. وقتی یه خط خالیه، continue مستقیم میره سراغ دور بعد، و try / except بهجای خوابیدن برنامه، مبلغ خراب رو گزارش میده. توی load_totals هم همینطوری line.strip() رو چک کن.
خودت رو امتحان کن
فایل رضا یه خط food,abc داره، و load_totalsـش هیچ try / exceptی نداره. وقتی به اون خط میرسه چی میشه؟
- اون خط بیسروصدا رد میشه
- به food عدد 0 اضافه میشه
- برنامه با ValueError: invalid literal for int() with base 10: 'abc' متوقف میشه
- food میشه متن "abc"
جواب رو ببین
برنامه با ValueError: invalid literal for int() with base 10: 'abc' متوقف میشه
درسته. int("abc") نمیتونه عدد بسازه، پس پایتون ValueError میده و چون چیزی نیست که بگیرتش، برنامه همونجا وایمیسته.
از اینجا به بعد
- مال خودت کنش. با if وقتی یه دسته از بودجه رد شد هشدار بده، یا با input() خرجهای جدید رو بپرس.
- گزارش رو مرتب کن. sorted(totals.items(), key=...) میتونه بیشترین خرج رو اول بیاره؛ ببین key چطور کار میکنه.
- برنامههای کوچیک بساز. یه لیست کارها، یه آزمونک، یه شمارندهی کلمه: همهشون از همین تیکهها استفاده میکنن.
- آموزش رسمی رو بخون. آموزش پایتون توی docs.python.org درست از جایی شروع میکنه که این دوره تموم میشه.
مرور دوره
- اول نقشه: ورودی (یه فایل)، پردازش (جمع هر دسته)، خروجی (یه گزارش)؛ بعد قدمهای کوچیک بساز و بعد از هر قدم اجرا کن.
- line.strip().split(",") از "food,12\n" میسازه ['food', '12']؛ مبلغ هنوز int() لازم داره.
- totals[cat] = totals.get(cat, 0) + int(amt) هر مبلغ رو به دستهی خودش اضافه میکنه: food 20، bus 3، rent 50.
- load_totals دیکشنری رو return میکنه و print_report چاپش میکنه، که آخرش total: 73ـه.
- حالت append خرجهای جدید رو بیخطر اضافه میکنه، و try / except یه خط خراب رو بهجای خوابیدن برنامه به یه پیام تبدیل میکنه.