Modules: למה מחלקים לקבצים, import, from ו-as
- להסביר מהו Module ולמה מחלקים פרויקט למספר קבצים
- ליצור Module משלכם ולייבא ממנו פונקציות וקבועים
- להבחין בין
import x,from x import yו-import x as z, ולדעת מתי להשתמש בכל אחד - להבין שקוד ברמה הראשית של Module רץ בזמן הייבוא
מה נלמד?
- מהו Module (מודול).
- למה מחלקים פרויקט לכמה קבצים.
- שלוש צורות ייבוא:
import,from ... import,as. - מה בדיוק קורה כש-Python מייבאת קובץ.
למה צריך את זה?
כבר השתמשנו במודולים: import os, import logging, import socket. היום נכתוב משלנו.
פרויקט אמיתי הוא מאות ואלפי שורות. קובץ אחד ענק הוא:
- קשה לנווט — איפה הפונקציה ששולחת מייל? שורה 1,840?
- קשה לעבוד בצוות — שלושה מפתחים עורכים את אותו קובץ ונתקלים זה בזה (ב-Git, מחר).
- קשה לשימוש חוזר — פונקציית ולידציה טובה “תקועה” בתוך קובץ של משחק.
חלוקה לקבצים היא Separation of Concerns ברמת הפרויקט. בנושא Backend ומסדי נתונים השרת יחולק בדיוק כך: main.py (Routes), db.py (גישה למסד), models.py.
הסבר מהבסיס
מהו Module?
Module = כל קובץ .py. זהו. כל קובץ Python שכתבתם הוא כבר מודול, ואפשר לייבא ממנו פונקציות, קבועים ומשתנים לקובץ אחר.
- שם המודול = שם הקובץ בלי
.py. הקובץprices.py← המודולprices. - לכן שמות קבצים חייבים להיות שמות חוקיים ב-Python:
snake_case, בלי מקפים (my-utils.pyלא ייובא!), ולא מתחילים בספרה.
קובץ אחד ענק = מגירה אחת שבה כל הכלים: סכינים, כפות, מלקחיים, מספריים. כדי למצוא משהו, מחפשים בהכול. מודולים = מגירות נפרדות עם תוויות: “סכו"ם”, “כלי אפייה”, “תבלינים”. import = “תביא לי מהמגירה הזו”.
שלוש צורות ייבוא
| צורה | איך משתמשים אחר כך | מתי |
|---|---|---|
import prices | prices.add_vat(100) | ברירת המחדל. ברור מאיפה כל שם מגיע. |
from prices import add_vat | add_vat(100) | כשמשתמשים הרבה בכמה שמות ספציפיים. |
import prices as p / from prices import add_vat as vat | p.add_vat(100) / vat(100) | שם ארוך או התנגשות שמות. מוסכמות מוכרות: import numpy as np. |
מה קורה בזמן import?
- Python מחפשת את הקובץ: קודם בתיקייה של הסקריפט שהרצתם, ואחר כך בספריות של Python.
- Python מריצה את כל הקובץ מלמעלה למטה, פעם אחת: יוצרת את הפונקציות והקבועים, ומריצה כל קוד ברמה הראשית.
- התוצאה נשמרת.
importנוסף לאותו מודול לא מריץ שוב.
סעיף 2 חשוב מאוד, ונראה את ההשלכות שלו בטעות הנפוצה.
__pycache__
אחרי ייבוא ראשון תופיע תיקייה בשם __pycache__ עם קבצי .pyc. זה ה-Bytecode (נושא מחשב, מערכת הפעלה ורשת!) של המודולים, שנשמר כדי שבפעם הבאה הייבוא יהיה מהיר יותר. לא נוגעים בה, ובפרויקטי Git מתעלמים ממנה (נראה ב-.gitignore בנושא Git ו-GitHub).
תחביר
# file: prices.py (the module)
VAT_RATE = 0.17
def add_vat(amount):
return amount * (1 + VAT_RATE)# file: main.py (uses the module; same folder)
import prices
prices.add_vat(100)
from prices import add_vat, VAT_RATE
add_vat(100)
import prices as p
p.add_vat(100)
from prices import add_vat as with_vat
with_vat(100)סדר מקובל בראש קובץ: קודם ייבוא מהספרייה הסטנדרטית (os, json), שורה ריקה, ואז ייבוא מהמודולים שלכם.
דוגמת קוד ראשונה
צרו תיקייה week-2/shop/, ובה שני קבצים.
week-2/shop/prices.py:
VAT_RATE = 0.17
def add_vat(amount):
return round(amount * (1 + VAT_RATE), 2)
def apply_discount(amount, percent):
return round(amount - amount * percent / 100, 2)week-2/shop/main.py:
import prices
base_price = 200
discounted = prices.apply_discount(base_price, 10)
final_price = prices.add_vat(discounted)
print("Base:", base_price)
print("After 10% discount:", discounted)
print("With VAT (" + str(prices.VAT_RATE) + "):", final_price)הרצה (מתוך week-2/shop): python main.py
פלט:
Base: 200
After 10% discount: 180.0
With VAT (0.17): 210.6פירוק הקוד שורה אחר שורה
| שורה | הסבר |
|---|---|
import prices | Python מחפשת prices.py באותה תיקייה של main.py, מריצה אותו, ויוצרת את השם prices שמייצג את המודול. |
prices.apply_discount(...) | ניגשים לפונקציה דרך שם המודול, עם נקודה. בדיוק כמו os.getcwd(). |
prices.VAT_RATE | גם קבועים נגישים כך. |
main.py לא מגדיר שום פונקציית מחיר | כל הלוגיקה של המחירים נמצאת במקום אחד. אם המע"מ משתנה, משנים רק את prices.py. |
דוגמה נוספת
שלוש צורות הייבוא, ומודול שני. הוסיפו את week-2/shop/validation.py:
MIN_NAME_LENGTH = 2
def is_valid_price(price):
return isinstance(price, (int, float)) and price >= 0
def is_valid_name(name):
return len(name.strip()) >= MIN_NAME_LENGTHועדכנו את main.py:
import prices as p
from validation import is_valid_price, is_valid_name
products = [("Laptop", 3500), ("X", 20), ("Mouse", -5), ("Desk", 900)]
for name, price in products:
if not is_valid_name(name):
print("Skipping invalid name:", repr(name))
elif not is_valid_price(price):
print("Skipping invalid price for", name, ":", price)
else:
print(name, "->", p.add_vat(price), "NIS incl. VAT")פלט:
Laptop -> 4095.0 NIS incl. VAT
Skipping invalid name: 'X'
Skipping invalid price for Mouse : -5
Desk -> 1053.0 NIS incl. VATפירוק:
import prices as p— כינוי קצר. ניגשים עםp.add_vat.from validation import is_valid_price, is_valid_name— מייבאים רק את שתי הפונקציות, ומשתמשים בהן ישירות, בלי קידומת.repr(name)— מציגה את הערך עם מירכאות ('X'), כמו%rבלוגים.- מבנה הפרויקט:
prices.py= לוגיקה של מחירים.validation.py= כללי תקינות.main.py= מחבר ומריץ. כל קובץ באחריות אחת.
טעות נפוצה
הטעות 1: קוד “בדיקה” ברמה הראשית של מודול. נניח שבתחתית prices.py הוספתם בדיקה:
print("Testing prices module:", add_vat(100))עכשיו כל import prices, מכל קובץ, ידפיס את השורה הזו, כי ייבוא מריץ את הקובץ. בשיעור הבא נלמד את הפתרון: if __name__ == "__main__":.
הטעות 2: שם קובץ שמתנגש עם מודול סטנדרטי.
יצרתם קובץ בשם random.py (או logging.py, json.py), ובקובץ אחר כתבתם import random:
AttributeError: module 'random' has no attribute 'randint'Python מוצאת קודם את random.py שלכם, באותה תיקייה, ומייבאת אותו במקום המודול האמיתי. לעולם אל תקראו לקבצים בשמות של מודולים סטנדרטיים.
הטעות 3: ModuleNotFoundError.
ModuleNotFoundError: No module named 'prices'הסיבות הנפוצות: הקובץ לא באותה תיקייה כמו הסקריפט שהרצתם, שגיאת כתיב בשם, או שם קובץ עם מקף (my-prices.py).
הטעות 4: from module import *. מייבא את כל השמות מהמודול. לא ברור מאיפה כל שם הגיע, ושמות עלולים לדרוס זה את זה בשקט. מייבאים במפורש את מה שצריך.
תרגול
התרגילים מבוססים רק על מה שנלמד בשיעור הזה ובשיעורים שלפניו. נסו לבד, ורק אז פתחו את הפתרון.
תרגיל 1: המודול הראשון
צרו קובץ prices.py עם הקבוע VAT_RATE = 0.17 ועם שתי פונקציות: add_vat(amount) ו-apply_discount(amount, percent) (שתיהן מעגלות לשתי ספרות). בקובץ main.py ייבאו את המודול, חשבו מחיר של 200 אחרי 10% הנחה, ואז הוסיפו לו מע"מ.
פלט צפוי (הרצת main.py):
After discount: 180.0
With VAT: 210.6הצג פתרון
prices.py
VAT_RATE = 0.17
def add_vat(amount):
return round(amount * (1 + VAT_RATE), 2)
def apply_discount(amount, percent):
return round(amount - amount * percent / 100, 2)main.py
import prices
discounted = prices.apply_discount(200, 10)
final_price = prices.add_vat(discounted)
print("After discount:", discounted)
print("With VAT:", final_price)import prices טוען את הקובץ prices.py שנמצא באותה תיקייה. ניגשים לתוכן שלו עם נקודה: prices.add_vat(...).
תרגיל 2: שלוש צורות ייבוא
כתבו את אותה שורה — חישוב מע"מ על 100 — בשלוש צורות הייבוא: import, from ... import ו-import ... as. מתי כדאי להשתמש בכל אחת?
הצג פתרון
import prices
print(prices.add_vat(100))
from prices import add_vat
print(add_vat(100))
import prices as p
print(p.add_vat(100))import prices— ברור תמיד מאיפה כל שם מגיע (prices.add_vat). מתאים כשמשתמשים בהרבה דברים מהמודול.from prices import add_vat— כותבים פחות. מתאים לכמה שמות ספציפיים שמשתמשים בהם הרבה.import prices as p— שם קצר למודול עם שם ארוך.
תרגיל 3: Standard Library
בלי לכתוב מודול משלכם, השתמשו במודולים המובנים של Python:
math— שורש של 81, ו-math.piמעוגל לשתי ספרות.random— עםrandom.seed(1), הגרילו מספר בין 1 ל-6.
פלט צפוי:
9.0
3.14
2הצג פתרון
import math
import random
print(math.sqrt(81))
print(round(math.pi, 2))
random.seed(1)
print(random.randint(1, 6))9.0
3.14
2math ו-random שייכים ל-Standard Library — מגיעים עם Python, ומייבאים אותם בדיוק כמו מודול שכתבנו בעצמנו. random.seed(1) מקבע את ההגרלה, כדי שהתוצאה תהיה זהה בכל הרצה.
תרגיל 4: קוד שרץ בזמן import
בקובץ helpers.py:
def shout(text):
return text.upper() + "!"
print("helpers loaded")בקובץ main.py:
from helpers import shout
print(shout("hi"))- מה יודפס כשמריצים
python main.py, ולמה? - איך מונעים את השורה הראשונה?
הצג פתרון
- יודפס:
helpers loaded
HI!import מריץ את כל הקובץ — כולל print שנמצא ברמה הראשית. גם from helpers import shout מריץ את כל helpers.py, לא רק את הפונקציה.
- מודול שמיועד לייבוא לא צריך קוד ברמה הראשית — רק הגדרות. מוחקים את ה-
print. (בשיעור הבא נלמד אתif __name__ == "__main__":, שמאפשר להשאיר קוד בדיקה שרץ רק בהרצה ישירה.)
תרגיל 5: מודול טמפרטורות
צרו מודול temperature.py עם c_to_f(celsius) ועם describe(celsius) שמחזירה "hot" מ-30 מעלות, "cold" מתחת ל-15, ו-"pleasant" בין לבין. ב-main.py הדפיסו לכל עיר את הטמפרטורה בשתי היחידות ואת התיאור.
cities = ["Eilat", "Haifa", "Jerusalem"]
temperatures = [35, 22, 9]פלט צפוי:
Eilat: 35 C / 95.0 F - hot
Haifa: 22 C / 71.6 F - pleasant
Jerusalem: 9 C / 48.2 F - coldהצג פתרון
temperature.py
HOT_FROM = 30
COLD_BELOW = 15
def c_to_f(celsius):
return round(celsius * 9 / 5 + 32, 1)
def describe(celsius):
if celsius >= HOT_FROM:
return "hot"
if celsius < COLD_BELOW:
return "cold"
return "pleasant"main.py
from temperature import c_to_f, describe
cities = ["Eilat", "Haifa", "Jerusalem"]
temperatures = [35, 22, 9]
for i in range(len(cities)):
celsius = temperatures[i]
print(cities[i] + ":", celsius, "C /", c_to_f(celsius), "F -", describe(celsius))הלוגיקה של הטמפרטורות נמצאת במודול, ו-main.py רק משתמש בה ומדפיס.
סיכום
- Module = קובץ
.py. שם המודול = שם הקובץ בלי.py(snake_case, בלי מקפים). import m→m.func().from m import func→func().import m as x→x.func().- Python מחפשת מודולים קודם בתיקיית הסקריפט. לא קוראים לקבצים
random.py,json.pyוכו'. - ייבוא מריץ את המודול פעם אחת. קוד ברמה הראשית רץ גם כשמייבאים.
- חלוקה לקבצים = Separation of Concerns ברמת הפרויקט. בלי
import *.
דוקומנטציה רשמית
- Python Tutorial — Modules — import, from ו-as
- More on Modules — from module import name