برنامه‌نویسی پایتون فهرست مطالب
فصل ۴۰

ارزشیابی پایانی

این جلسه، جلسه آزمون است و مطلب تازه‌ای ندارد. این فصل برای مرور نوشته شده است: خلاصه فصل ۳۰ تا ۳۹، راهنمای دفاع از پروژه پایانی، یک نمونه آزمون کامل، و در پایان نقشه راهی برای ادامه مسیر بعد از این دوره.

در این فصل می‌خوانید
  1. چه چیزهایی ارزشیابی می‌شود
  2. مرور پایگاه داده و ORM
  3. مرور رابط گرافیکی
  4. مرور همروندی
  5. مرور وب
  6. دفاع از پروژه پایانی
  7. چک‌لیست آمادگی
  8. اشتباه‌های پرتکرار
  9. نمونه آزمون تشریحی
  10. نمونه آزمون عملی
  11. پاسخ کوتاه سؤال‌های مفهومی
  12. نقشه راه ادامه مسیر

۱. چه چیزهایی ارزشیابی می‌شود

فصلموضوعوزن تقریبی
۳۰SQL: جدول، شرط، تجمیع، JOINزیاد
۳۱sqlite3، پارامتر، تراکنشزیاد
۳۲ORM، SQLAlchemy، رابطه‌هازیاد
۳۳الگوهای طراحیمتوسط
۳۴ و ۳۵tkinter، چیدمان، رویداد، Treeviewزیاد
۳۶PySide6، Designer، PyInstallerمتوسط
۳۷نخ، قفل، صف، asyncioمتوسط
۳۸Flask: مسیر، قالب، فرم، نشست، APIزیاد
۳۹معماری پروژه، Git و GitHubزیاد

۲. مرور پایگاه داده و ORM

سؤالپاسخ کوتاه
کلید اصلی و کلید خارجی چه فرقی دارنداولی هر سطر را یکتا می‌کند، دومی به سطری در جدول دیگر اشاره می‌کند
WHERE و HAVINGاولی روی سطرها پیش از گروه‌بندی، دومی روی گروه‌ها بعد از آن
سه نوع JOININNER فقط مشترک‌ها، LEFT همه چپ، RIGHT همه راست
چرا پارامتر ?تنها دفاع در برابر تزریق SQL؛ مقدار هرگز به کد تبدیل نمی‌شود
تراکنش یعنی چهچند دستور که یا همه انجام می‌شوند یا هیچ‌کدام
PRAGMA foreign_keys = ONدر هر اتصال لازم است؛ بدون آن ON DELETE هیچ کاری نمی‌کند
ORM چیستجدول به کلاس، سطر به شیء، ستون به صفت
flush و commitاولی شناسه می‌سازد، دومی تراکنش را می‌بندد
مسئله N+1یک پرس‌وجو برای هر سطر؛ درمانش selectinload یا joinedload

۳. مرور رابط گرافیکی

موضوعtkinterQt (PySide6)
شروعTk() و mainloop()QApplication و app.exec()
پاسخ به کلیکcommand=funcbutton.clicked.connect(func)
چیدمانpack و gridQVBoxLayout و خانواده‌اش
جدولttk.TreeviewQTableWidget
پنجره دومToplevel + grab_set + wait_windowQDialog.exec()
بستن پنجرهprotocol('WM_DELETE_WINDOW', f)closeEvent
کار زمان‌بندی‌شدهafter()QTimer

سه قانونی که در هر دو یکسان‌اند: ویجت را با مختصات پیکسلی نچینید؛ هرگز از نخ دیگری به ویجت دست نزنید؛ و ویجت را انبار داده خود نکنید.

۴. مرور همروندی

برنامه وقتش را صرف چه می‌کندابزار
انتظار، با تعداد کم کارthreading یا ThreadPoolExecutor
انتظار، با صدها یا هزاران کارasyncio
محاسبهProcessPoolExecutor

۵. مرور وب

موضوعنکته کلیدی
@app.routeآدرس به تابع نما وصل می‌شود
url_forهرگز لینک را دستی ننویسید
۴۰۴ در برابر ۵۰۰«وجود ندارد» در برابر «من اشتباه کردم»
گریز خودکار Jinjaمحافظ XSS؛ safe روی داده کاربر ممنوع
POST / Redirect / GETتا F5 فرم را دوباره نفرستد
نشستکوکی امضاشده ولی خواندنی
رمز عبورفقط درهم ذخیره می‌شود، هرگز خام
کد وضعیت API۲۰۱ برای ساخت، ۴۰۰ برای خطای مشتری، ۲۰۴ برای حذف
debug=Trueهرگز روی سرور واقعی

۶. دفاع از پروژه پایانی

در این جلسه پروژه پایانی‌تان را ارائه و از آن دفاع می‌کنید. سؤال‌هایی که تقریباً حتماً پرسیده می‌شوند:

  1. برنامه‌تان را در سه جمله توصیف کنید. چه مسئله‌ای را حل می‌کند؟
  2. یک کلیک را از دکمه تا پایگاه داده دنبال کنید و همه توابعی را که اجرا می‌شوند نام ببرید.
  3. یک قانون کسب‌وکار پروژه‌تان را نشان دهید و بگویید دقیقاً کجا نوشته شده است. اگر در دو جا نوشته شده، چرا؟
  4. اگر بخواهیم همین برنامه را فردا به شکل یک سایت دربیاوریم، چه بخشی از کد را باید دوباره بنویسید؟
  5. کدام عمل برنامه شما باید حتماً در یک تراکنش باشد و چرا؟
  6. یک ورودی اشتباه به من نشان دهید و بگویید برنامه با آن چه می‌کند.
  7. در .gitignore چه گذاشته‌اید و هر خطش جلوی چه اشتباهی را می‌گیرد؟
  8. تاریخچه کامیت‌هایتان را نشان دهید. پیام‌ها گویا هستند؟
آنچه ارزیاب واقعاً می‌بیند

برنامه‌ای که ده امکان دارد و هنگام کلیک اول می‌ترکد، از برنامه‌ای با چهار امکان که همه‌شان بی‌نقص کار می‌کنند ضعیف‌تر است. اول کار کردن، بعد کامل بودن. پیش از ارائه، برنامه را روی یک پوشه خالی و تازه یک بار کامل اجرا کنید.

۷. چک‌لیست آمادگی

باید بتوانم
۱جدولی با کلید خارجی بسازم و سه نوع JOIN را بنویسم
۲از پایتون به SQLite وصل شوم و چهار عمل اصلی را با پارامتر بنویسم
۳یک عمل دومرحله‌ای را در یک تراکنش امن کنم
۴دو مدل SQLAlchemy با رابطه یک‌به‌چند تعریف کنم
۵یک زنجیره if/elif را با الگوی استراتژی بازنویسی کنم
۶فرمی با اعتبارسنجی کامل در tkinter بسازم
۷یک Treeview را از داده واقعی پر کنم و مرتبش کنم
۸دیالوگ مودالی بنویسم که مقدار برگرداند
۹یک پنجره PySide6 با سیگنال و اسلات بسازم
۱۰برنامه‌ام را به یک فایل اجرایی تبدیل کنم و بدانم فایل داده کجا نوشته می‌شود
۱۱کار طولانی را به نخ ببرم و از راه صف با پنجره حرف بزنم
۱۲چند کوروتین را با gather همزمان اجرا کنم
۱۳سایتی با قالب پایه، فرم و هدایت بعد از POST بنویسم
۱۴ورود کاربر با رمز درهم‌شده و دکوراتور محافظ بسازم
۱۵یک REST API با کدهای وضعیت درست بنویسم
۱۶پروژه‌ای را با core/ و gui/ لایه‌بندی کنم
۱۷مخزنی بسازم، شاخه بزنم، تعارض را حل کنم و روی GitHub منتشر کنم

۸. اشتباه‌های پرتکرار

اشتباهدرستش
ساختن SQL با f-stringپارامتر ? یا پارامتر ORM
فراموش کردن commitهیچ خطایی نمی‌گیرید، فقط داده غیبش می‌زند
خواندن fetchone() بدون بررسی Noneif row is None: ...
دو دستور با دو commit جدایک تراکنش
خواندن داده از داخل ویجتداده واقعی را در پایتون نگه دارید
command=func() با پرانتزcommand=func
لامبدا در حلقه بدون x=xاتصال دیرهنگام؛ مقدار را منجمد کنید
قاطی کردن pack و gridبرنامه بی‌صدا قفل می‌شود
گم شدن مرجع PhotoImageروی self نگهش دارید
time.sleep در تابع پاسخ یا کوروتینafter()، نخ، یا asyncio.sleep
دست زدن به ویجت از نخ کارگراز راه صف پیام بفرستید
نخواندن future.result()خطا برای همیشه پنهان می‌ماند
قورت دادن CancelledErrorبگیرید، تمیزکاری کنید، دوباره پرتابش کنید
رندر کردن صفحه بعد از POST موفقهدایت کنید
صفحه ۵۰۰ برای ورودی غلط کاربر۴۰۴ یا ۴۰۰
ذخیره رمز عبور خامفقط درهم
کامیت کردن .venv و .db و توکن.gitignore پیش از اولین کامیت
نوشتن فایل داده کنار __file__ در برنامه منجمدکنار sys.executable

۹. نمونه آزمون تشریحی

بخش اول: مفهومی، هر سؤال یک نمره

  1. تفاوت INNER JOIN و LEFT JOIN را با یک مثال از جدول کتاب و امانت توضیح دهید.
  2. چرا WHERE نمی‌تواند روی نتیجه COUNT شرط بگذارد؟ چه چیزی می‌تواند؟
  3. یک عملیات در برنامه‌تان نام ببرید که باید داخل یک تراکنش انجام شود و توضیح دهید اگر تراکنش به درستی مدیریت نشود، چه مشکلی ممکن است برای داده‌ها ایجاد شود.
  4. سه سود ORM و دو موردی که SQL خام بهتر است را بنویسید.
  5. الگوی استراتژی چه مسئله‌ای را حل می‌کند و کی نباید به وظیفه آن برد؟
  6. چرا در tkinter نباید pack و grid را در یک ظرف قاطی کرد؟ علامت این اشتباه چیست؟
  7. «ویجت انبار داده شما نیست» یعنی چه؟ یک باگ واقعی از این جنس شرح دهید.
  8. سیگنال و اسلات در Qt با command در tkinter چه فرقی دارد؟ یک مزیت Qt بنویسید.
  9. چرا PyInstaller نمی‌تواند روی لینوکس فایل اجرایی ویندوز بسازد؟
  10. تفاوت resource_path و data_path را توضیح دهید و بگویید هر کدام کجا به کار می‌آیند.
  11. شرایط رقابتی را با مثال counter += 1 توضیح دهید. چرا باگی است که ممکن است ماه‌ها پیدا نشود؟
  12. چرا queue.Queue به قفل نیاز ندارد ولی یک لیست ساده دارد؟
  13. GIL چیست و چه تصمیمی را در طراحی برنامه شما عوض می‌کند؟
  14. چرا time.sleep در یک کوروتین یک باگ است و نه فقط یک ناکارآمدی؟
  15. قاعده POST / Redirect / GET چیست و جلوی چه اتفاقی را می‌گیرد؟
  16. چرا فیلتر safe روی داده کاربر یک حفره امنیتی است؟
  17. چرا نباید رمز عبور را ذخیره کرد؟ به جایش چه ذخیره می‌شود و چرا امن‌تر است؟
  18. تفاوت ۴۰۰ و ۴۰۴ و ۵۰۰ را با یک مثال از یک API بنویسید.
  19. چرا در پروژه پایانی، core/ هیچ چیزی از PySide6 وارد نمی‌کند؟
  20. رمزی را کامیت و پوش کرده‌اید. چرا پاک کردن آن خط کافی نیست؟

بخش دوم: خروجی یا اشکال کد را بنویسید

۲۱. این کد چه چاپ می‌کند و چرا؟

import threading

counter = 0

def work():
    global counter
    for _ in range(100_000):
        counter += 1

threads = [threading.Thread(target=work) for _ in range(5)]
for t in threads:
    t.start()
for t in threads:
    t.join()
print(counter)

۲۲. اشکال این تابع کجاست؟

def borrow(conn, book_id, member_id):
    conn.execute('INSERT INTO borrows (book_id, member_id) VALUES (?, ?)',
                 (book_id, member_id))
    conn.commit()
    conn.execute('UPDATE books SET available = 0 WHERE id = ?', (book_id,))
    conn.commit()

۲۳. چرا هر چهار دکمه یک کار می‌کنند؟

for name in ('add', 'sub', 'mul', 'div'):
    button = tk.Button(root, text=name,
                       command=lambda: run(name))
    button.pack()

۲۴. این تابع نما چه کد وضعیتی برمی‌گرداند وقتی مشتری بدنه JSON نفرستد؟ چه باید برگرداند؟

@app.route('/api/books', methods=['POST'])
def create_book():
    payload = request.get_json()
    title = payload['title']
    ...

۲۵. چرا این کوروتین برنامه را کند می‌کند؟

async def fetch_all(urls):
    results = []
    for url in urls:
        results.append(await fetch(url))
    return results

۱۰. نمونه آزمون عملی

سؤال یک: پایگاه داده (۵ نمره)

یک پایگاه داده clinic.db با سه جدول بسازید: بیمار، پزشک و نوبت. کلیدهای خارجی را با ON DELETE درست تعریف کنید. سپس با SQLAlchemy مدل‌هایشان را بنویسید و پرس‌وجویی بسازید که برای هر پزشک، تعداد نوبت‌های امروزش را بدهد.

سؤال دو: تراکنش (۳ نمره)

تابع book_appointment(patient, doctor, when) را بنویسید که نوبت را ثبت کند و فقط در صورتی موفق شود که پزشک در آن ساعت آزاد باشد. بررسی و ثبت باید اتمی باشند. توضیح دهید اگر دو کاربر همزمان دکمه را بزنند چه می‌شود.

سؤال سه: رابط گرافیکی (۵ نمره)

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

سؤال چهار: همروندی (۳ نمره)

به همان برنامه، دکمه «پشتیبان‌گیری» اضافه کنید که کار را در یک نخ انجام دهد، پیشرفت را از راه یک صف به پنجره بدهد، و پنجره در تمام مدت پاسخگو بماند. دکمه باید در حین کار غیرفعال شود.

سؤال پنج: وب (۴ نمره)

یک REST API برای همان نوبت‌ها بنویسید با چهار آدرس: فهرست، ساخت، گرفتن یکی و حذف. کدهای وضعیت باید درست باشند و بدنه غیر JSON باید ۴۰۰ بگیرد نه ۵۰۰. بعد یک مشتری requests با timeout برایش بنویسید.

۱۱. پاسخ کوتاه سؤال‌های مفهومی

اول خودتان جواب دهید

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

سؤالپاسخ کوتاه
۲۱عددی کمتر از ۵۰۰۰۰۰ و هر بار متفاوت. counter += 1 اتمی نیست و افزایش‌ها گم می‌شوند.
۲۲دو commit جدا. اگر دومی شکست بخورد، کتاب هم امانت داده شده و هم موجود می‌ماند. باید یک تراکنش باشد، و بررسی «هنوز آزاد است؟» جزو خود UPDATE.
۲۳اتصال دیرهنگام: همه لامبداها به یک متغیر name نگاه می‌کنند و مقدار نهایی div است. درستش lambda n=name: run(n).
۲۴الان ۵۰۰ می‌دهد، چون payload برابر None است. باید ۴۰۰ بدهد؛ با get_json(silent=True) و بررسی.
۲۵حلقه با await ترتیبی است. باید asyncio.gather به کار برود تا همه با هم اجرا شوند.
۲چون WHERE پیش از گروه‌بندی اجرا می‌شود و هنوز گروهی وجود ندارد. HAVING این کار را می‌کند.
۶چون مدیر چیدمان دوم با اولی بر سر اندازه ظرف بی‌پایان چانه می‌زند. علامتش این است که پنجره بدون هیچ خطایی قفل می‌شود.
۱۲چون put و get قفل را خودشان می‌گیرند؛ یک لیست ساده هیچ محافظتی ندارد.
۲۰چون رمز در تاریخچه مخزن باقی می‌ماند. باید باطلش کنید و رمز تازه بگیرید.

۱۲. نقشه راه ادامه مسیر

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

مسیرقدم بعدی
کیفیت کدآزمون خودکار با pytest، که موضوع دوره جداگانه‌ای است
وبDjango یا FastAPI، بعد از Flask این دوره
دادهpandas، numpy و matplotlib
پایگاه دادهPostgreSQL، مهاجرت ساختار با Alembic، ایندکس‌گذاری
استقرارDocker، سرور لینوکسی، انتشار خودکار
دسکتاپQt پیشرفته: مدل و نما، نخ‌های Qt، ترجمه
مبانیساختمان داده و الگوریتم، که در هر زبانی به کارتان می‌آید
مهم‌ترین توصیه

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

درباره این جلسه

پوشه S40-evaluation فقط یک README.md دارد و هیچ فایل تمرینی ندارد، چون این جلسه، جلسه آزمون است. برای تمرین، به پوشه‌های S30 تا S39 برگردید.

فصل قبل: پروژه پایانی، Git و GitHub بازگشت به فهرست