خطا، ماژول و pip
برنامهای که با خطا نمیمیرد: try و except و else و finally، گرفتن خطای مشخص، raise برای خطای خودت؛ ماژولهای آمادهی پایتون (math، random، datetime، os، sys، subprocess)، ساختن ماژول خودت و if __name__ == "__main__"، و نصب کتابخانه با pip داخل محیط مجازی venv روی Ubuntu.
- حدود ۲۰ دقیقه مطالعه
- ۹ مثال اجراشده
- ۳ تمرین با جواب
- مقدماتی تا متوسط
- بهروزرسانی: ۲۹ مهر ۱۴۰۵
توی این درس یاد میگیری برنامهات با اولین ورودی غلط یا فایل گمشده نمیرد: با try و except خطا را میگیری و کار معقولی میکنی، با finally کاری را که حتماً باید انجام شود تضمین میکنی، و با raise خودت خطا اعلام میکنی. بعد سراغ ماژولها میروی: هزاران تابع آماده که همراه پایتون میآیند (math، random، datetime، os، sys، subprocess)، و یاد میگیری کد خودت را در یک فایل جدا بگذاری و import کنی. در آخر کتابخانههای بیرونی را با pip داخل یک محیط مجازی (venv) نصب میکنی؛ روشی که روی Ubuntu جدید تنها راه درست است.
مسئله: یک ورودی غلط، کل برنامه را میکشد
Section titled “مسئله: یک ورودی غلط، کل برنامه را میکشد”در درس متغیرها دیدی int("بیست") با ValueError برنامه را متوقف کرد. در یک اسکریپت شبانه که ۵۰۰ فایل را پردازش میکند، یک فایل خراب نباید باعث شود ۴۹۹ فایل دیگر پردازش نشوند. و از طرف دیگر، لازم نیست هر چیزی را خودت از صفر بنویسی: کسی قبلاً تابع جذر، تاریخ، ارسال درخواست HTTP و خواندن ایمیل را نوشته است.
تشبیه: تور ایمنی و جعبهابزار
Section titled “تشبیه: تور ایمنی و جعبهابزار”try/except مثل تور ایمنی زیر بندباز است: بندباز (کد) کارش را میکند؛ اگر افتاد (خطا)، تور او را میگیرد و نمایش ادامه پیدا میکند، بهجای اینکه همهچیز تمام شود. ماژول مثل جعبهابزار است: لازم نیست خودت چکش بسازی؛ جعبه را باز میکنی (import) و ابزار را برمیداری. pip هم فروشگاه ابزار است، و venv کارگاه جدای هر پروژه که ابزارهای یک پروژه با پروژهی دیگر قاطی نشود.
مثالهای عملی
Section titled “مثالهای عملی”مثال ۱: اولین try/except
Section titled “مثال ۱: اولین try/except”text = input("یک عدد: ")try: n = int(text) print("دو برابرش:", n * 2)except ValueError: print(f"«{text}» عدد نیست!")print("برنامه ادامه داد.")یک عدد: ۱۲دو برابرش: 24برنامه ادامه داد.یک عدد: دوازده«دوازده» عدد نیست!برنامه ادامه داد.پایتون کد داخل try را اجرا میکند. اگر خطایی از نوع ValueError رخ داد، بقیهی try را رها میکند و بلوک except را اجرا میکند؛ بعد برنامه ادامه میدهد. اسم خطا را از همان خط آخر پیامهای خطا (درسهای قبل) برمیداری.
مثال ۲: دوباره بپرس تا درست جواب بدهد
Section titled “مثال ۲: دوباره بپرس تا درست جواب بدهد”ترکیب while True (درس حلقه) و try:
while True: try: age = int(input("سن: ")) break except ValueError: print("لطفاً فقط عدد بنویس.")print("سن ثبت شد:", age)سن: بیستلطفاً فقط عدد بنویس.سن: 20سن ثبت شد: 20break فقط وقتی اجرا میشود که int موفق شده باشد. این الگو را برای هر ورودی کاربر استفاده کن.
مثال ۳: چند نوع خطا
Section titled “مثال ۳: چند نوع خطا”def read_config(path): try: with open(path, encoding="utf-8") as f: return f.read() except FileNotFoundError: print(f"هشدار: {path} نیست؛ از تنظیمات پیشفرض استفاده میکنم.") return "" except PermissionError: print(f"خطا: اجازهی خواندن {path} را ندارم.") return ""
print(repr(read_config("missing.conf")))print(repr(read_config("/etc/shadow")))هشدار: missing.conf نیست؛ از تنظیمات پیشفرض استفاده میکنم.''خطا: اجازهی خواندن /etc/shadow را ندارم.''هر except یک نوع خطا را میگیرد و پایتون اولین except جور را اجرا میکند. برای هر نوع، واکنش مناسب همان را بنویس: نبودن فایل تنظیمات قابلچشمپوشی است؛ نبودن اجازه احتمالاً نه.
مثال ۴: else، finally و as e
Section titled “مثال ۴: else، finally و as e”def divide(a, b): try: result = a / b except ZeroDivisionError as e: print("خطا:", e) return None else: print("تقسیم موفق بود") return result finally: print("(این همیشه اجرا میشود)")
print(divide(10, 2))print(divide(1, 0))تقسیم موفق بود(این همیشه اجرا میشود)5.0خطا: division by zero(این همیشه اجرا میشود)Noneas eخود خطا را درeمیگذارد تا پیامش را چاپ کنی یا در لاگ بنویسی.elseفقط وقتی اجرا میشود کهtryبدون خطا تمام شده باشد.finallyهمیشه اجرا میشود، حتی وقتیreturnزده شده؛ برای کارهای «جمعوجور کردن» (بستن اتصال، پاک کردن فایل موقت). همان کاری کهtrap EXITدر دورهی Bash میکند.
کدام بلوک حتی وقتی داخل try دستور return اجرا شده، باز هم اجرا میشود؟
جواب درست: finally
finally برای کارهایی است که در هر حال باید انجام شوند.
مثال ۵: اعلام خطای خودت، raise
Section titled “مثال ۵: اعلام خطای خودت، raise”گاهی ورودی از نظر پایتون مشکلی ندارد ولی از نظر منطق برنامه غلط است:
def set_port(port): if not 1 <= port <= 65535: raise ValueError(f"پورت {port} خارج از بازهی ۱ تا ۶۵۵۳۵ است") return port
print(set_port(8080))print(set_port(70000))8080Traceback (most recent call last): File "/home/ali/py/port.py", line 7, in <module> print(set_port(70000)) ^^^^^^^^^^^^^^^ File "/home/ali/py/port.py", line 3, in set_port raise ValueError(f"پورت {port} خارج از بازهی ۱ تا ۶۵۵۳۵ است")ValueError: پورت 70000 خارج از بازهی ۱ تا ۶۵۵۳۵ استraise یک خطا پرتاب میکند؛ هر کسی که set_port را صدا زده میتواند با try/except ValueError آن را بگیرد. تابع نباید خودش تصمیم بگیرد برنامه چه کند؛ فقط باید واضح بگوید چه چیزی غلط است. (1 <= port <= 65535 هم یعنی «بین ۱ و ۶۵۵۳۵»؛ پایتون مقایسهی زنجیرهای را قبول میکند.)
مثال ۶: ماژولهای آماده
Section titled “مثال ۶: ماژولهای آماده”پایتون با صدها ماژول میآید (به آن «کتابخانهی استاندارد» میگویند):
import mathimport randomfrom datetime import datetime, timedelta
print(math.sqrt(16), math.pi, math.ceil(2.1))random.seed(42)print(random.randint(1, 6), random.choice(["web1", "web2", "db1"]))now = datetime(2026, 10, 21, 14, 30)print(now.strftime("%Y-%m-%d %H:%M"))print((now + timedelta(days=7)).date())4.0 3.141592653589793 36 web12026-10-21 14:302026-10-28| شکل import | استفاده |
|---|---|
import math |
math.sqrt(16): اسم ماژول جلوی هر تابع |
from datetime import datetime |
datetime(...): مستقیم، بدون اسم ماژول |
import numpy as np |
np....: اسم کوتاه (رایج برای کتابخانههای بزرگ) |
random.seed(42)عددهای تصادفی را تکرارپذیر میکند (برای همین خروجی بالا هر بار یکی است). بدون آن، هر اجرا عدد دیگری میدهد. برای رمز و توکن هرگز ازrandomاستفاده نکن؛ ماژولsecretsبرای همین ساخته شده (دورهی امنیت).strftimeتاریخ را با قالب دلخواه به متن تبدیل میکند (%Yسال،%mماه،%dروز،%H:%Mساعت)، همان قالبهای دستورdate +%Fلینوکس. برای «الان»:datetime.now().
مثال ۷: پایتون و سیستمعامل
Section titled “مثال ۷: پایتون و سیستمعامل”import osimport sysimport platformprint(os.getcwd())print(os.environ.get("HOME"), os.environ.get("NOPE", "تعریف نشده"))print(sys.argv)print(platform.system(), platform.python_version())python3 system.py a b/home/ali/py/home/ali تعریف نشده['system.py', 'a', 'b']Linux 3.12.3os.getcwd()همانpwdاست وos.environمتغیرهای محیطی (درس متغیرهای محیطی در دورهی لینوکس).sys.argvلیست آرگومانهای خط فرمان است: عضو اول اسم اسکریپت، بقیه هر چه بعدش نوشتهای. با این، اسکریپتهایت مثل دستورهای لینوکس آرگومان میگیرند (تمرین سخت).
و برای اجرای یک دستور لینوکس از داخل پایتون، subprocess:
import subprocessr = subprocess.run(["df", "-h", "/"], capture_output=True, text=True)print(r.returncode)print(r.stdout.splitlines()[0])0Filesystem Size Used Avail Use% Mounted onدستور به شکل لیست داده میشود (هر کلمه یک عضو)، returncode همان کد خروج ($?) است و stdout خروجی دستور به شکل متن.
مثال ۸: ماژول خودت
Section titled “مثال ۸: ماژول خودت”هر فایل .py یک ماژول است. در یک پوشه دو فایل بساز:
"""ابزارهای کوچک من."""
def human_size(n): for unit in ["B", "KB", "MB", "GB"]: if n < 1024: return f"{n:.1f} {unit}" n /= 1024 return f"{n:.1f} TB"
if __name__ == "__main__": print(human_size(123456789))import toolsfrom tools import human_size
print(tools.human_size(2048))print(human_size(5_000_000_000))python3 tools.pypython3 main.py117.7 MB2.0 KB4.7 GBimport toolsفایلtools.pyکنارmain.pyرا پیدا میکند و تابعهایش را در دسترس میگذارد.if __name__ == "__main__":یعنی «این بلوک فقط وقتی اجرا شود که این فایل مستقیم اجرا شده، نه وقتیimportشده». برای همینpython3 tools.pyخط تست را چاپ کرد ولیimport toolsدرmain.pyآن را چاپ نکرد. این الگو را در هر فایلی که هم ابزار است و هم اسکریپت میبینی.5_000_000_000همان پنج میلیارد است؛_فقط برای خوانایی عددهای بزرگ است.
مثال ۹: کتابخانههای بیرونی، venv و pip
Section titled “مثال ۹: کتابخانههای بیرونی، venv و pip”خیلی از ابزارهای معروف (مثل requests برای HTTP، یا flask در درس API) همراه پایتون نیستند و باید از PyPI (مخزن کتابخانههای پایتون) نصب شوند. روی Ubuntu 24.04 اگر مستقیم pip install بزنی:
error: externally-managed-environment
× This environment is externally managed╰─> To install Python packages system-wide, try apt install python3-xyz, where xyz is the package you are trying to install.
If you wish to install a non-Debian-packaged Python package, create a virtual environment using python3 -m venv path/to/venv. Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make sure you have python3-full installed.Ubuntu اجازه نمیدهد کتابخانهها را روی پایتونِ خود سیستم نصب کنی، چون ممکن است ابزارهای سیستمی را خراب کنی. راه درست، محیط مجازی (virtual environment) است: یک پوشهی جدا برای هر پروژه با پایتون و کتابخانههای خودش. یک بار بستهاش را نصب کن:
sudo apt install python3-venv python3-pipحالا در پوشهی پروژه:
python3 -m venv .venvsource .venv/bin/activatewhich python(.venv) ali@pc:~/py$ which python/home/ali/py/.venv/bin/pythonبعد از activate، پرامپت با (.venv) شروع میشود و python و pip مال همین محیطاند (در محیط فعال، python بدون ۳ هم کار میکند). حالا نصب آزاد است:
pip install requestspip freeze > requirements.txtCollecting requests Downloading requests-2.32.3-py3-none-any.whl (64 kB)...Successfully installed certifi-2024.8.30 charset-normalizer-3.4.0 idna-3.10 requests-2.32.3 urllib3-2.2.3pip freeze > requirements.txt فهرست دقیق کتابخانهها و نسخههایشان را در یک فایل مینویسد؛ هر کس دیگری (یا سرور) با pip install -r requirements.txt همان محیط را میسازد. برای خروج از محیط: deactivate.
پشت پرده: خطا از کجا بالا میآید؟
Section titled “پشت پرده: خطا از کجا بالا میآید؟”وقتی خطایی رخ میدهد، پایتون یک شیء خطا میسازد و آن را به بالا پرتاب میکند: اول در همان تابع دنبال except جور میگردد؛ اگر نبود، تابع تمام میشود و خطا به تابعی که صدایش زده میرسد، و همینطور بالا میرود. اگر تا بالای برنامه هیچ exceptی آن را نگرفت، پایتون Traceback (فهرست همهی تابعهایی که خطا از آنها رد شد) را چاپ میکند و برنامه میمیرد. برای همین در مثال ۵، خطای set_port در Traceback دو خط داشت: جایی که raise شد و جایی که صدا زده شد.
نوعهای خطا هم خانواده دارند: FileNotFoundError و PermissionError هر دو زیرمجموعهی OSError هستند، و تقریباً همهچیز زیرمجموعهی Exception. پس except OSError: هر دو را میگیرد. ولی except Exception: (یا بدتر، except: خالی) همهچیز را میگیرد، از جمله باگهای خودت مثل غلط تایپی (NameError)؛ برای همین فقط خطاهایی را بگیر که انتظارشان را داری.
و import چطور ماژول را پیدا میکند؟ در فهرست پوشههای sys.path به ترتیب میگردد: اول پوشهی اسکریپت، بعد پوشههای کتابخانهی استاندارد، بعد کتابخانههای نصبشده (در venv، پوشهی .venv/lib/python3.12/site-packages). اولین فایلی که با آن اسم پیدا کند برنده است (اشتباهات رایج ۴).
جدولهای مرجع
Section titled “جدولهای مرجع”| ساختار | کار |
|---|---|
try: ... except X: ... |
گرفتن خطای نوع X |
except (X, Y): |
گرفتن هر کدام از چند نوع |
except X as e: |
گرفتن و نگه داشتن خود خطا |
else: |
فقط اگر خطایی نبود |
finally: |
همیشه |
raise ValueError("پیام") |
اعلام خطا |
| خطای رایج | کی |
|---|---|
ValueError |
مقدار نامناسب (int("x")) |
TypeError |
نوع نامناسب ("a" + 1) |
KeyError / IndexError |
کلید یا اندیس نیست |
FileNotFoundError / PermissionError |
فایل نیست / اجازه نیست |
ZeroDivisionError |
تقسیم بر صفر |
ModuleNotFoundError |
ماژول نصب نیست یا اسمش غلط است |
| دستور | کار |
|---|---|
python3 -m venv .venv |
ساختن محیط مجازی |
source .venv/bin/activate / deactivate |
فعال / غیرفعال کردن |
pip install اسم |
نصب کتابخانه (در محیط فعال) |
pip freeze > requirements.txt |
ثبت نسخهها |
pip install -r requirements.txt |
نصب از فهرست |
اشتباهات رایج
Section titled “اشتباهات رایج”۱) except خالی
Section titled “۱) except خالی”try: n = int(input("عدد: "))except: print("خطا!")این حتی Ctrl+C (KeyboardInterrupt) و غلطهای تایپی خودت را هم میبلعد و پیدا کردن باگ را غیرممکن میکند. راهحل: همیشه نوع خطا را بنویس: except ValueError:.
۲) گذاشتن کد زیاد داخل try
Section titled “۲) گذاشتن کد زیاد داخل try”اگر بیست خط را در try بگذاری، معلوم نیست except ValueError کدام خط را گرفته. راهحل: فقط خطی که ممکن است خطا بدهد را در try بگذار؛ بقیه را در else یا بعد از کل بلوک.
۳) ماژولی که نصب نیست
Section titled “۳) ماژولی که نصب نیست”ModuleNotFoundError: No module named 'requests'یا نصبش نکردهای، یا در محیطی نصب کردهای که الان فعال نیست. راهحل: source .venv/bin/activate و بعد pip install requests؛ با which python مطمئن شو پایتون محیط را اجرا میکنی.
۴) اسم فایل خودت، همنام یک ماژول
Section titled “۴) اسم فایل خودت، همنام یک ماژول”اگر فایلت را random.py یا json.py بنامی، import random در هر فایل آن پوشه فایل تو را میآورد (چون پوشهی اسکریپت اول جستجو میشود) و خطاهای عجیبی مثل AttributeError: module 'random' has no attribute 'randint' میگیری. راهحل: اسمهای اختصاصی (my_random_test.py)، و پاک کردن پوشهی __pycache__ اگر ساخته شده.
۵) python3 -m venv روی Ubuntu تازه خطا میدهد
Section titled “۵) python3 -m venv روی Ubuntu تازه خطا میدهد”The virtual environment was not created successfully because ensurepip is notavailable. On Debian/Ubuntu systems, you need to install the python3-venvpackage using the following command.
apt install python3.12-venvراهحل: همان که میگوید: sudo apt install python3-venv.
برنامهای بنویس که دو عدد بگیرد و حاصل تقسیم اولی بر دومی را چاپ کند. اگر کاربر چیزی غیر از عدد نوشت یا دومی صفر بود، بهجای مردن، پیام مناسب بدهد.
دیدن جواب
try: a = float(input("عدد اول: ")) b = float(input("عدد دوم: ")) print("حاصل:", a / b)except ValueError: print("فقط عدد بنویس.")except ZeroDivisionError: print("تقسیم بر صفر ممکن نیست.")عدد اول: 10عدد دوم: 0تقسیم بر صفر ممکن نیست.تمرین اصلی: تابع load_servers(path) بنویس که یک فایل JSON (لیستی از سرورها، مثل درس قبل) بخواند. اگر فایل نبود، لیست خالی برگرداند و هشدار بدهد؛ اگر JSON خراب بود (json.JSONDecodeError)، پیام خطا را با شمارهی خط چاپ کند و لیست خالی برگرداند. با یک فایل سالم، یک فایل ناموجود و یک فایل خراب امتحانش کن.
دیدن جواب
import json
def load_servers(path): try: with open(path, encoding="utf-8") as f: return json.load(f) except FileNotFoundError: print(f"هشدار: {path} پیدا نشد") return [] except json.JSONDecodeError as e: print(f"خطا: {path} خط {e.lineno}: {e.msg}") return []
with open("good.json", "w", encoding="utf-8") as f: f.write('[{"name": "web1"}]')with open("bad.json", "w", encoding="utf-8") as f: f.write('[{"name": "web1",}]')
print(load_servers("good.json"))print(load_servers("none.json"))print(load_servers("bad.json"))[{'name': 'web1'}]هشدار: none.json پیدا نشد[]خطا: bad.json خط 1: Expecting property name enclosed in double quotes[]ویرگول اضافه بعد از آخرین عضو در JSON مجاز نیست (برخلاف پایتون). خطای JSONDecodeError خودش lineno و msg دارد.
یک ابزار خط فرمان بساز: size.py که مسیر یک فایل را از sys.argv بگیرد و اندازهاش را با human_size (مثال ۸، import از tools.py) چاپ کند. اگر آرگومان داده نشد، راهنما چاپ کند و با کد خروج ۱ تمام شود؛ اگر فایل نبود، خطای خوانا بدهد و با کد ۲ تمام شود. با echo $? کد خروج را بسنج.
دیدن جواب
import sysfrom pathlib import Pathfrom tools import human_size
if len(sys.argv) < 2: print("استفاده: python3 size.py <مسیر فایل>") sys.exit(1)
path = Path(sys.argv[1])try: print(f"{path}: {human_size(path.stat().st_size)}")except FileNotFoundError: print(f"خطا: {path} وجود ندارد") sys.exit(2)python3 size.pyecho $?python3 size.py /usr/bin/python3.12python3 size.py /no/fileecho $?استفاده: python3 size.py <مسیر فایل>1/usr/bin/python3.12: 7.6 MBخطا: /no/file وجود ندارد2sys.exit(کد) برنامه را با آن کد خروج تمام میکند؛ پس این اسکریپت در Bash مثل هر دستور دیگری با && و || (درس ترکیب دستورها) کار میکند. اندازهی فایل پایتون روی سیستم تو ممکن است فرق کند.
آزمونک
Section titled “آزمونک”کدام شکل except بهتر است؟
جواب درست: except ValueError:
فقط خطایی را بگیر که انتظارش را داری.
بلوک else در try کی اجرا میشود؟
جواب درست: فقط اگر خطایی رخ نداد
finally همیشه است.
raise ValueError("...") چه میکند؟
جواب درست: یک خطا اعلام (پرتاب) میکند
صدازننده میتواند با except آن را بگیرد.
if __name__ == "__main__": یعنی…
جواب درست: فقط وقتی این فایل مستقیم اجرا شده، نه import
تا کد تست با import اجرا نشود.
روی Ubuntu 24.04 pip install خطای externally-managed-environment میدهد. راه درست؟
جواب درست: ساختن venv و نصب داخل آن
python3 -m venv .venv و source .venv/bin/activate.
برای ساختن توکن امن کدام ماژول؟
جواب درست: secrets
random قابلپیشبینی است.
جمعبندی
Section titled “جمعبندی”try+except نوعخطای مشخص را میگیرد و برنامه ادامه میدهد؛elseاگر خطایی نبود؛finallyهمیشه؛as eخود خطا.raiseبرای اعلام خطای منطقی؛ هرگزexceptخالی.import ماژول/from ماژول import چیز؛ کتابخانهی استاندارد:math،random،datetime،os،sys(argv،exit)،subprocess،json،csv،pathlib.- هر فایل
.pyیک ماژول است؛if __name__ == "__main__":برای کد اجرای مستقیم. - کتابخانهی بیرونی:
python3 -m venv .venv،source .venv/bin/activate،pip install؛requirements.txtبرای تکرارپذیری.
| دستور | کاری که میکند |
|---|---|
try: ... except ValueError: ... | گرفتن یک خطای مشخص |
except OSError as e: print(e) | گرفتن و دیدن پیام خطا |
finally: | همیشه اجرا شود |
raise ValueError("پیام") | اعلام خطا |
from datetime import datetime | وارد کردن از ماژول |
sys.argv / sys.exit(1) | آرگومانها / خروج با کد |
subprocess.run([...], capture_output=True, text=True) | اجرای دستور لینوکس |
if __name__ == "__main__": | فقط در اجرای مستقیم |
python3 -m venv .venv && source .venv/bin/activate | محیط مجازی |
pip install x / pip freeze > requirements.txt | نصب و ثبت کتابخانهها |