diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000..77fa9fe
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,48 @@
+name: Build and Deploy
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - '*'
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Setup Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.10'
+ - name: Install the dependencies
+ run: |
+ python -m pip install -r requirements.txt
+ - name: Build the JupyterLite site
+ run: |
+ cp README.md content
+ jupyter lite build --contents content --output-dir dist
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v1
+ with:
+ path: ./dist
+
+ deploy:
+ needs: build
+ if: github.ref == 'refs/heads/main'
+ permissions:
+ pages: write
+ id-token: write
+
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+
+ runs-on: ubuntu-latest
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v1
diff --git a/.github/workflows/update-downloads.yml b/.github/workflows/update-downloads.yml
new file mode 100644
index 0000000..6dba6ba
--- /dev/null
+++ b/.github/workflows/update-downloads.yml
@@ -0,0 +1,46 @@
+name: Update Downloads
+
+on:
+ push:
+ paths:
+ - 'content/**'
+
+jobs:
+ update-downloads:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Notebooks repo
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+
+ - name: Install zip
+ run: sudo apt-get install zip
+
+ - name: Zip content directories
+ run: |
+ cd content
+ for dir in week*; do
+ zip -r "../$dir.zip" "$dir" -X -q -x "*.git*"
+ done
+ cd ..
+
+ - name: Checkout Downloads repo
+ uses: actions/checkout@v2
+ with:
+ repository: PythonFreeCourse/downloads
+ token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
+ path: downloads
+
+ - name: Copy zip files to Downloads repo
+ run: |
+ cp *.zip downloads/
+
+ - name: Commit and push changes
+ run: |
+ cd downloads
+ git config user.name "${{ github.actor }}"
+ git config user.email "${{ github.actor }}@users.noreply.github.com"
+ git add -A .
+ git commit -m "Update zip files by ${{ github.actor }}" || echo "No changes to commit"
+ git push
diff --git a/AUTHORS.rst b/AUTHORS.rst
new file mode 100644
index 0000000..f5a1c6c
--- /dev/null
+++ b/AUTHORS.rst
@@ -0,0 +1,10 @@
+The notebooks of "Lomdim Python" were lovingly created by (`Yam Mesicka By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. Section 1 – Definitions. Section 2 – Scope. Other rights. Section 3 – License Conditions. Your exercise of the Licensed Rights is expressly made subject to the following conditions. Attribution. If You Share the Licensed Material (including in modified form), You must: Section 4 – Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: Section 5 – Disclaimer of Warranties and Limitation of Liability. Section 6 – Term and Termination. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: Section 7 – Other Terms and Conditions. Section 8 – Interpretation.
+ כאשר אנחנו עושים חלוקת שלמים מהצורה אם יש לי A משולשי פיצה, וחילקתי את כל משלושי הפיצה באופן שווה ל־B ילדים (תזהרו מלחתוך לי את המשולשים!), כמה משולשי פיצה יקבל כל ילד? אם יש לי A משולשי פיצה, וחילקתי את כל משולשי הפיצה באופן שווה ל־B ילדים (תזהרו מלחתוך לי את המשולשים!), כמה משולשי פיצה יקבל כל ילד? לדוגמה: הביטוי \n",
- "דרך קלה לקבל הערכה גסה של כמה קלוריות יש במוצר מזון, היא זו:\n",
+ " דרך קלה לקבל הערכה גסה של כמה קלוריות יש במוצר מזון, היא זו:\n",
" \n",
- " יש נטייה לשכוח את ה־s אחרי ה־end או את ה־start ב־endswith וב־startswith.\n",
+ " יש נטייה לשכוח את ה־s אחרי ה־end או ה־start ב־endswith וב־startswith.\n",
" \n",
- " כעת ענו על השאלה הקודמת באמצעות הפונקציה שכתבתם בסעיף זה, כלומר כתבו פונקציה שמשתמשת בפונקציה המחזירה ערך בוליאני ומדפיסה בהתאם להוראת מהסעיף הקודם.Creative Commons Attribution 4.0 International Public License
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.
+
+
+
+
+
+
+
+
+For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.
+
+
+
diff --git a/README.md b/README.md
index 3838b14..f6e0f9e 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,29 @@
-# Notebooks
\ No newline at end of file
+
+
+
A // B
, אנחנו למעשה מתכוונים לשאול:
\n",
+ "
\n",
"\n",
"4 // 9
, אומר שיש לנו 9 משולשי פיצה ו־4 ילדים רעבים.
אם נחלק את משולשי הפיצה בין הילדים, נגלה שכל ילד יקבל 2 משולשים, ושנשאר משולש אחד שלא ניתן לחלק.
רמז: השתמשו בערך ההחזרה של הפונקציה מהסעיף הקודם, בתוך if.
רמז: השתמשו בערך ההחזרה של הפונקציה מהסעיף הקודם, בתוך if.
פונקציות שימושיות:\n",
- " רמזים נוספים: \n",
- "רמז: המחרוזת כוללת את המילה
split – מתודה של string.
\n",
+ "
split – מתודה של string.
\n",
" האופרטור % (מודולו) – חשבו עם איזה מספר צריך לעשות מודולו.
\n",
" zfill – השתמשו בה במקרה שהשעה חד־ספרתית (לדוגמה 1:05 תהפוך ל־01:05) \n",
" \n",
"
\n",
- " מומלץ להמיר את השעה מ־string ל־int ואז לבצע את פעולות החשבון, ולבסוף להמיר חזרה ל־string\n",
+ " מומלץ להמיר את השעה מ־string ל־int ואז לבצע את פעולות החשבון, ולבסוף להמיר חזרה ל־string\n",
" \n",
"password
.\n",
+ "רמז: המחרוזת כוללת את המילה password
.\n",
"
\n", - " רמז: השתמשו בלולאת while\n", + " רמז: השתמשו בלולאת while\n", "
\n" ] }, @@ -720,8 +720,8 @@ "רמזים:
\n", "\n", "\n",
+ " מתכנתים העוסקים בקביעות במלאכתם, יזכו לעיתים תכופות להיתקל באתגרים בתחומים שונים.
\n",
+ "
\n", + " דוגמה טובה לאתגר נפוץ שכזה היא יצירת מידע אקראי:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " דוגמה טובה נוספת היא עבודה עם תאריכים:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " פתירת האתגרים הללו עשויה להיות משימה מורכבת, ולטמון בחובה בעיות ומקרי קצה רבים.
\n",
+ " תארו לעצמכם כמה זמן היה נחסך לו היה מישהו פותר את הבעיות הנפוצות הללו עבור כל המתכנתים!
\n",
+ "
\n",
+ " כרעיון, מודול הוא פיסת תוכנה עצמאית המשרתת מטרה מוגדרת.
\n",
+ " המטרה יכולה להיות, לדוגמה, טיפול בתאריכים, יצירת נתונים אקראיים או תקשורת עם אתרי אינטרנט.
\n",
+ " בפייתון, מודול הוא קובץ המאגד הגדרות ופקודות, שיחדיו יוצרות אוסף כלים בתחום מסוים.
\n",
+ "
\n",
+ " ניקח כדוגמה את המודול random
, שמטרתו לעזור לנו ליצור מידע אקראי.
\n",
+ " לפני שנוכל להשתמש ביכולותיו של המודול, נצטרך לבקש מפייתון לטעון אותו בעזרת מילת המפתח import
:\n",
+ "
\n",
+ " פעולה זו טוענת את המודול ומאפשרת לנו להשתמש בו בהמשך הקוד. נהוג להגיד שייבאנו את המודול random
.
\n",
+ " עכשיו, כשהמודול יובא, אפשר להשתמש בו בקוד התוכנית שלנו באופן שיענה על הצורך ליצירת דברים אקראיים.
\n",
+ " כדי להבין איך להשתמש במודול ומהן יכולותיו, נוכל לקרוא מידע נוסף על אודותיו במגוון דרכים:\n",
+ "
python documentation randomבמנועי חיפוש.
dir(random)
.random.
ונלחץ Tab ↹.\n",
+ " במשחק הפוקימון \"פוקימון אדום\" אפשר לבחור בתור הפוקימון ההתחלתי את בלבזאור, את סקווירטל או את צ'רמנדר.
\n",
+ " על הפוקימון הטוב ביותר לבחירה ניטשים ויכוחים רבים עוד מאז שחרורו של המשחק ב־1996.
\n",
+ " כדי לא להיקלע לעין הסערה, נבנה תוכנה שתבחר את הפוקימון עבורנו באופן אקראי.\n",
+ "
\n",
+ " התיעוד של המודול random
, כולל פונקציה בשם choice, שבאמצעותה נוכל לבחור איבר אקראי מתוך iterable.
\n",
+ " השימוש בתיעוד מומלץ במיוחד בהקשרי מודולים, שכן ההסברים שם בהירים, ופעמים רבות מובאות שם דוגמאות לשימוש בפונקציות של המודול.
\n",
+ " נייבא את random
ואז נשתמש ב־choice:\n",
+ "
\n",
+ " לא היינו יכולים להשתמש ב־random.choice
לולא היינו מייבאים את random
.
\n",
+ " מרגע שייבאנו את המודול – נוכל להשתמש בו לאורך כל הקוד.\n",
+ "
\n",
+ " השתמשו בפונקציה הנמצאת במודול random
כדי לדמות הטלת קובייה בעלת 20 פאות (D20).
\n",
+ " הגרילו מספר בין 1 ל־20, הפעם בעזרת פונקציה שהיא לא choice
.
\n",
+ " השתמשו במקורות המידע שצוינו כדי למצוא את הפונקציה המתאימה למשימה.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n", + " אלו, בין היתר, היתרונות של שימוש במודולים הקיימים בפייתון:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " רוב התוכנות שבהן אתם משתמשים נעזרות במספר לא מועט של מודולים.
\n",
+ " כרגע נתמקד בשימוש במודולים שמגיעים עם פייתון, אך בעתיד נשתמש במודולים שיצרו מתכנתים אחרים, ואף ניצור מודולים בעצמנו.\n",
+ "
\n", + " איש דג החרב מגדיר \"מחולל סיסמאות חזקות\" כך:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " כתבו מחולל סיסמאות חזקות.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " על אף שזהו תרגיל נחמד, לעולם לא נשתמש ב־random
לצורכי אבטחת מידע.
\n",
+ " להרחבה, קראו על יתרונותיו של המודול secrets
.\n",
+ "
\n",
+ " עצרת של המספר $n$ (המסומנת כ־$n!$) היא מכפלת כל המספרים השלמים החיוביים עד $n$, כולל.
\n",
+ " נחשב את העצרת של 6 באמצעות המודול math
:\n",
+ "
\n",
+ " במקרה שהצגנו, אין לנו באמת צורך בכל הפונקציות הנמצאות במודול math
, אלא רק בפונקציה factorial.
\n",
+ " תעלול נחמד שאפשר לעשות הוא לייבא רק את factorial באמצעות מילת המפתח from
:
\n",
+ "
\n",
+ " כדאי לשים לב שלאחר שייבאנו בעזרת from
נשתמש ישירות ב־factorial, מבלי להזכיר את השייכות שלה למודול math
.
\n",
+ " אפשר גם לייבא יותר משם אחד מאותו מודול, במכה אחת:\n",
+ "
\n",
+ " באופן כללי, העדיפו לייבא את המודול כולו ולא חלקים מתוכו.
\n",
+ " זה יעזור לכם להימנע מיצירת מקרים מבלבלים כמו זה:
\n",
+ "
\n", + " אם ממשיכים בכיוון, פייתון מאפשרת לנו לעשות את הדבר המזעזע הבא:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from math import *" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " יבוא בעזרת כוכבית יגרום לכך שכל מה שמוגדר במודול \"יישפך\" לתוך התוכנית שלנו.
\n",
+ " זה יאפשר לנו להשתמש בכל התוכן של math
בלי להזכיר את שם המודול:\n",
+ "
\n",
+ " פרקטיקה זו נחשבת מאוד לא מנומסת, ואתם מתבקשים לא להשתמש בה, אלא אם כן אלו ההוראות הכתובות בתיעוד של המודול.
\n",
+ " ישנן לא מעט סיבות הגיוניות מאחורי איסור זה:\n",
+ "
\n",
+ " המודול turtle
הוא דרך פופולרית ללמד ילדים תכנות באמצעים גרפיים.
\n",
+ " תכנות ב־turtle הוא מעין משחק: ישנו צב שהולך במרחב, ומשאיר צבע בכל מקום שאליו הוא מגיע.
\n",
+ " אפשר להורות לצב בכמה מעלות להסתובב לכל כיוון, ולאיזה מרחק ללכת בכיוון שאליו הוא מופנה.
\n",
+ " כך, בסוף מסלול הטיול של הצב, מתקבל ציור מלא חן.
\n",
+ " הרעיון נוצר כחלק משפת התכנות Logo בשנת 1967.\n",
+ "
\n",
+ " נראה דוגמה לתוצר של ריצת תוכנית שכזו, שבה אנחנו משרטטים 100 ריבועים בהיסט של מעלה אחת בכל פעם.
\n",
+ " אנחנו ממליצים לשחק עם המודול קצת (זה כיף!) ולראות אם אתם מצליחים לצייר כוכב, לדוגמה :)\n",
+ "
\n",
+ " הרצת קוד של turtle
תפתח לכם חלון חדש בו יצויר פלט התוכנית.
\n",
+ " כדי להמשיך להריץ את התאים במחברת, סגרו את החלון.\n",
+ "
\n",
+ " כדי לשנות את השם שבו אנחנו מתייחסים למודול, ניעזר במילת המפתח as
.
\n",
+ " לדוגמה:\n",
+ "
\n",
+ " כך אפשר לקצר את שם המודול שאנחנו מייבאים, ולהימנע מסרבול מיותר.
\n",
+ " למרות זאת, יבוא מודול תחת שם אחר נחשב פרקטיקה לא מנומסת, שעלולה לבלבל קוראים שבקיאים בשמות המודולים הקיימים.\n",
+ "
\n",
+ " ישנם שני מקרים יוצאי דופן, שבהם השימוש ב־as
נחשב רצוי:\n",
+ "
as
במסמכי התיעוד של המודול.\n",
+ " לשימוש במודולים יש כללי סגנון שמוסכמים על רוב המתכנתים.
\n",
+ " הם מופיעים במסמך בשם PEP8, שמטרתו להגדיר איך נראה קוד פייתון המסוגנן כראוי.
\n",
+ " הנה כמה כללים שראוי שתעקבו אחריהם:\n",
+ "
from
ליבוא של יותר משם אחד – השמות צריכים להיות מסודרים בסדר מילוני.\n", + " מה התאריך היום?\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import datetime\n", + "print(\"What is the time now?\")\n", + "print(datetime.datetime.now())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " נשתמש במודול calendar
כדי להדפיס את לוח השנה של החודש הנוכחי:\n",
+ "
import
על כך שאנחנו הולכים להשתמש במודול מסוים בקוד שלנו.\n",
+ " בתרגילים הבאים השתמשו באינטרנט כדי למצוא מודולים ופונקציות שיסייעו לכם לפתור את התרגיל.
\n",
+ " נסו להימנע מחיפוש ומקריאה של פתרונות לתרגיל המסוים המופיע במחברת (כמו חיפושים הכוללים \"חפיסת קלפים\").\n",
+ "
\n",
+ " כתבו פונקציה שמקבלת נתיב לתיקייה, ומחזירה את רשימת כל הקבצים שמתחילים ברצף האותיות \"deep\" באותה תיקייה.
\n",
+ " בדקו שהפעלת הפונקציה על התיקייה images מחזירה שני קבצים.\n",
+ "
\n", + " בחפיסת קלפים רגילה, שבה 52 קלפים, יש לכל קלף שתי תכונות:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " כל צירוף של ערך וצורה מופיע בחפיסה בדיוק פעם אחת.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כתבו פונקציה שמקבלת תאריך עתידי בתצורה YYYY-MM-DD, ומדפיסה את מספר הימים שנשארו עד שנגיע לתאריך המיוחל.
\n",
+ " לדוגמה, אם התאריך היום הוא 2020-05-04 וקיבלנו כקלט 2020-05-25, הפונקציה תחזיר 21.\n",
+ "
\n",
+ " כתבו תוכנה שמקבלת כקלט מהמשתמש שני תאריכים בתצורה: YYYY-MM-DD.
\n",
+ " התוכנה תגריל תאריך חדש שנמצא בין שני התאריכים שהמשתמש הזין כקלט.
\n",
+ " לדוגמה, עבור הקלטים 1912-06-23 ו־1954-06-07, פלט אפשרי הוא 1939-09-03.
\n",
+ " כיוון שאני הולך למכולת רק בימי שני ואני צרכן כבד של רוטב ויניגרט, אם התאריך נופל על יום שני, הדפיסו: \"אין לי ויניגרט!\"
\n",
+ " רמז: קראו על EPOCH.\n",
+ "
\n", + " עד כה למדנו להכיר את עולמן של הפונקציות מקרוב:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " במחברת זו נרכוש כלים נוספים שיאפשרו לנו גמישות רבה יותר בהגדרת פונקציות ובשימוש בהן.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## שימוש מתקדם בפונקציות" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### העברת ארגומנטים בעזרת שם" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כאשר אנחנו קוראים לפונקציה, יישלחו לפי הסדר הארגומנטים שנעביר אל הפרמטרים שמוגדרים בכותרת הפונקציה.
\n",
+ " מצב כזה נקרא positional arguments (\"ארגומנטים לפי מיקום\").
\n",
+ " נסתכל על פונקציה שמקבלת טווח (סוף והתחלה, בסדר הזה) ומחזירה רשימה של כל המספרים השלמים בטווח:\n",
+ "
\n",
+ " לפעמים נרצה לשנות את סדר הארגומנטים שאנחנו שולחים לפונקציה.
\n",
+ " נעשה זאת בקריאה לפונקציה, על־ידי העברת שם הארגומנט ולאחר מכן העברת הערך שאנחנו רוצים להעביר אליו: \n",
+ "
\n",
+ " בשורה הזו הפכנו את סדר הארגומנטים.
\n",
+ " כיוון שבקריאה כתבנו את שמות הפרמטרים התואמים לכותרת הפונקציה, הערכים נשלחו למקום הנכון.
\n",
+ " השיטה הזו נקראת keyword arguments (\"ארגומנטים לפי שם\"), ובה אנחנו מעבירים את הארגומנטים שלנו לפי שמות הפרמטרים בכותרת הפונקציה.
\n",
+ " אנחנו משתמשים בשיטה הזו אפילו כשאנחנו לא רוצים לשנות את סדר הארגומנטים, אלא רק לעשות קצת סדר בקוד.
\n",
+ " נבחן, לדוגמה, את המקרה של הפונקציה random.randrange
– נעים יותר לראות קריאה לפונקציה עם שמות הפרמטרים:\n",
+ "
\n",
+ " למרות השימוש בסימן =
, לא מדובר פה בהשמה במובן הקלאסי שלה.
\n",
+ " זוהי צורת כתיבה מיוחדת בקריאה לפונקציות שהמטרה שלה היא לסמן \"העבר לפרמטר ששמו כך־וכך את הערך כך־וכך\".\n",
+ "
\n",
+ " נזכר בפונקציה get
של מילון, שמאפשרת לקבל ממנו ערך לפי מפתח מסוים.
\n",
+ " אם המפתח שאנחנו מחפשים לא קיים במילון, הפונקציה מחזירה None:\n",
+ "
\n",
+ " נממש את הפונקציה get
בעצמנו. לשם הנוחות, ייראה השימוש שונה במקצת:
\n",
+ "
\n",
+ " המימוש שלנו לא מושלם. הפעולה המקורית, get
על מילון, פועלת בצורה מתוחכמת יותר.
\n",
+ " אפשר להעביר לה פרמטר נוסף, שקובע מה יחזור אם המפתח שהעברנו בפרמטר הראשון לא נמצא במילון:\n",
+ "
\n",
+ " שימו לב להתנהגות המיוחדת של הפעולה get
!
\n",
+ " אם המפתח שהעברנו בארגומנט הראשון לא קיים במילון, היא מחזירה את הערך שכתוב בארגומנט השני.
\n",
+ " אפשר להעביר לה ארגומנט אחד, ואפשר להעביר לה שני ארגומנטים. היא מתפקדת כראוי בשני המצבים.
\n",
+ " זו לא פעם ראשונה שאנחנו רואים פונקציות כאלו. למעשה, בשבוע שעבר למדנו על פעולות builtins רבות שמתנהגות כך:
\n",
+ " range
, enumerate
ו־round
, כולן יודעות לקבל מספר משתנה של ארגומנטים.\n",
+ "
\n",
+ " נניח לפעולה get
בינתיים. אל דאגה, נחזור אליה בקרוב.
\n",
+ " בזמן שאנחנו נחים מפעולות על מילונים יום האהבה מתקרב, וחנות הוורדים הקרובה מעוניינת להעלות את מחירי כל מוצריה בשקל אחד.
\n",
+ " התבקשנו לבנות עבורם פונקציה שמקבלת רשימת מחירים, ומחזירה רשימה שבה כל איבר גדול ב־1 מרשימת המחירים המקורית.
\n",
+ " ניגש לעבודה:\n",
+ "
\n",
+ " בתוך זמן קצר הפונקציה שבנינו הופכת ללהיט היסטרי בחנויות הוורדים.
\n",
+ " מנהל קרטל הוורדים הבין־לאומי ג'וזפה ורדי יוצר איתנו קשר, ומבקש לשכלל התוכנה כך שיוכל להעלות את מחירי המוצרים כרצונו.
\n",
+ " כדי לעמוד בדרישה, נבנה פונקציה שמקבלת רשימה, ובנוסף אליה את המחיר שיתווסף לכל איבר ברשימה זו.
\n",
+ " כך, אם הקורא לפונקציה יעביר כארגומנט השני את הערך 2, כל איבר ברשימה יגדל ב־2.
\n",
+ " נממש בקלילות:\n",
+ "
\n",
+ " ורדי פוצח בשירה מרוב אושר, ומבקש שכלול אחרון לפונקציה, אם אפשר.
\n",
+ " אם הקורא לפונקציה העביר לה רק את רשימת המחירים, העלו את כל המחירים בשקל, כברירת מחדל.
\n",
+ " אם כן הועבר הארגומנט השני, העלו את המחיר לפי הערך שצוין באותו ארגומנט.
\n",
+ "
\n", + " הפעם אנחנו מתחבטים קצת יותר, מגרדים בראש, קוראים כמה מדריכי פייתון ומגיעים לבסוף לתשובה הבאה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_new_prices(l, increment_by=1):\n", + " l2 = []\n", + " for item in l:\n", + " l2.append(item + increment_by)\n", + " return l2\n", + "\n", + "\n", + "prices = [42, 73, 300]\n", + "print(prices)\n", + "print(get_new_prices(prices))\n", + "print(get_new_prices(prices, 5))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כשאנחנו רוצים להגדיר פרמטר עם ערך ברירת מחדל, נוכל לקבוע את ערך ברירת המחדל שלו בכותרת הפונקציה.
\n",
+ " אם יועבר ארגומנט שכזה לפונקציה – פייתון תשתמש בערך שהועבר.
\n",
+ " אם לא – יילקח ערך ברירת המחדל שהוגדר בכותרת הפונקציה. \n",
+ "
\n",
+ " במקרה שלנו הגדרנו את הפרמטר increment_by
עם ערך ברירת המחדל 1.
\n",
+ " קריאה לפונקציה עם ארגומנט אחד בלבד (רשימת המחירים) תגדיל את כל המחירים ב־1, שהרי הוא ערך ברירת המחדל.
\n",
+ " קריאה לפונקציה עם שני ארגומנטים (רשימת המחירים, סכום ההעלאה) תגדיל את כל המחירים בסכום ההעלאה שהועבר.\n",
+ "
\n", + " חשוב להבין שקריאה לפונקציה עם ערכים במקום ערכי ברירת המחדל, לא תשנה את ערך ברירת המחדל בקריאות הבאות: \n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(get_new_prices(prices, 5))\n", + "print(get_new_prices(prices))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " ממשו את פונקציית get
המלאה. הפונקציה תקבל מילון, מפתח ו\"ערך לשעת חירום\".
\n",
+ " החזירו את הערך השייך למפתח שהתקבל. אחרת – החזירו את הערך לשעת החירום שהועבר לפונקציה.
\n",
+ " אם לא הועבר ערך לשעת חירום והמפתח לא נמצא במילון, החזירו None. \n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " נדגים את אותו עיקרון עם כמה ערכי ברירת מחדל.
\n",
+ " אם הדרישה הייתה, לדוגמה, להוסיף לפונקציה גם אפשרות להנחה במחירי הפרחים, היינו יכולים לממש זאת כך:\n",
+ "
\n",
+ " אך מה יקרה כשנרצה לתת רק הנחה?
\n",
+ " במקרה כזה, כשנרצה \"לדלג\" מעל אחד מערכי ברירת המחדל, נצטרך להעביר את שמות הפרמטרים בקריאה לפונקציה.
\n",
+ " בדוגמה הבאה אנחנו מעלים את המחיר ב־1 (כי זו ברירת המחדל), ומורידים אותו ב־5: \n",
+ "
\n", + " זה אמנם עניין של סגנון, אבל יש יופי וסדר בציון שמות הפרמטרים גם כשלא חייבים:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(get_new_prices(prices, increment_by=10, discount=1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### מספר משתנה של ארגומנטים" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " הפונקציה הפייתונית max
, למשל, מתנהגת באופן משונה.
\n",
+ " היא יודעת לקבל כל מספר שהוא של ארגומנטים, ולהחליט מי מהם הוא הגדול ביותר.
\n",
+ " ראו בעצמכם!\n",
+ "
\n",
+ " נוכל גם אנחנו לממש פונקציה שמקבלת מספר משתנה של פרמטרים די בקלות.
\n",
+ " נתחיל מלממש פונקציה טיפשית למדי, שמקבלת מספר משתנה של פרמטרים ומדפיסה אותם:\n",
+ "
\n",
+ " מה התרחש בדוגמה האחרונה, בעצם?
\n",
+ " כשפרמטר מוגדר בכותרת הפונקציה עם הסימן כוכבית, אפשר לשלוח אל אותו פרמטר מספר בלתי מוגבל של ארגומנטים.
\n",
+ " הערך שייכנס לפרמטר יהיה מסוג tuple
, שאיבריו הם כל האיברים שהועברו כארגומנטים.\n",
+ "
\n", + " לצורך ההדגמה, נבנה פונקציה שמקבלת פרמטרים ומדפיסה אותם בזה אחר זה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def silly_function2(*parameters):\n", + " print(f\"Printing all the items in {parameters}:\")\n", + " for parameter in parameters:\n", + " print(parameter)\n", + " print(\"-\" * 20)\n", + "\n", + "\n", + "silly_function2('Shmulik', 'Shlomo')\n", + "silly_function2('Shmulik', 1, 1, 2, 3, 5, 8, 13)\n", + "silly_function2()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " שחקו עם הפונקציה silly_function2
וודאו שהבנתם מה מתרחש בה.
\n",
+ " כשתסיימו, נסו לממש את הפונקציה max
בעצמכם.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " נממש את max
:\n",
+ "
\n",
+ " כותרת הפונקציה יכולה לכלול משתנים נוספים לפני הכוכבית.
\n",
+ " נראה לדוגמה פונקציה שמקבלת גובה הנחה ואת מחירי כל המוצרים שקנינו, ומחזירה את הסכום הסופי שעלינו לשלם:\n",
+ "
\n",
+ " אף שבמבט ראשון הפונקציה get_final_price
עשויה להיראות מגניבה, כדאי להיזהר משימוש מוגזם בתכונה הזו של פייתון.
\n",
+ " הדוגמה הזו אמנם מדגימה גמישות יוצאת דופן של פייתון, אבל ככלל היא דוגמה גרועה מאוד לשימוש בכוכבית.\n",
+ "
\n",
+ " שימו לב כמה נוח יותר להבין את המימוש הבא ל־get_final_price
, וכמה נוח יותר להבין את הקריאה לפונקציה הזו:\n",
+ "
\n",
+ " כתבו פונקציה בשם create_path
שיכולה לקבל מספר בלתי מוגבל של ארגומנטים.
\n",
+ " הפרמטר הראשון יהיה אות הכונן שבו הקבצים מאוחסנים (לרוב \"C\"), והפרמטרים שאחריו יהיו שמות של תיקיות וקבצים.
\n",
+ " שרשרו אותם בעזרת התו \\
כדי ליצור מהם מחרוזת המייצגת נתיב במחשב. אחרי האות של הכונן שימו נקודתיים.
\n",
+ " הניחו שהקלט שהמשתמש הכניס הוא תקין.\n",
+ "
\n", + " הנה כמה דוגמאות לקריאות לפונקציה ולערכי ההחזרה שלה:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "create_path(\"C\", \"Users\", \"Yam\")
תחזיר \"C:\\Users\\Yam\"create_path(\"C\", \"Users\", \"Yam\", \"HaimonLimon.mp4\")
תחזיר \"C:\\Users\\Yam\\HaimonLimon.mp4\"create_path(\"D\", \"1337.png\")
תחזיר \"D:\\1337.png\"create_path(\"C\")
תחזיר \"C:\"create_path()
תגרום לשגיאה\n", + " בתחילת המחברת למדנו כיצד מעבירים לפונקציות ארגומנטים בעזרת שם:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def print_introduction(name, age):\n", + " return f\"My name is {name} and I am {age} years old.\"\n", + "\n", + "\n", + "print_introduction(age=2019, name=\"Gandalf\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אבל מה אם נרצה להעביר לפונקציה שלנו מספר בלתי מוגבל של ארגומנטים לפי שם?
\n",
+ " נביא כדוגמה את הפעולה format
על מחרוזות.
\n",
+ " format
היא פונקציה גמישה בכל הנוגע לכמות ולשמות של הארגומנטים שמועברים לה לפי שם.
\n",
+ " נראה שתי דוגמאות לשימוש בה, שימוש שבמבט ראשון עשוי להיראות קסום:\n",
+ "
\n",
+ " נכתוב גם אנחנו פונקציה שמסוגלת לקבל מספר בלתי מוגבל של ארגומנטים לפי שם.
\n",
+ " ניעזר תחילה בידידתנו הוותיקה, silly_function
, כדי לראות איך הקסם קורה:\n",
+ "
\n",
+ " ההתנהגות הזו מתרחשת כיוון שהשתמשנו בשתי כוכביות לפני שם המשתנה.
\n",
+ " השימוש בשתי כוכביות מאפשר לנו להעביר מספר בלתי מוגבל של ארגומנטים עם שם, באופן שמזכיר קצת את השימוש בכוכבית שראינו קודם.
\n",
+ " המשתנה שבו נשמרים הנתונים הוא מסוג מילון, ובו המפתחות יהיו שמות הארגומנטים שהועברו, והערכים – הערכים שהועברו לאותם שמות.\n",
+ "
\n",
+ " אחרי שהבנו איך הסיפור הזה עובד, בואו ננסה ליצור פונקציה מעניינת יותר.
\n",
+ " הפונקציה שנכתוב תקבל כארגומנטים כמה גרם מכל רכיב צריך כדי להכין סושי, ותדפיס לנו מתכון:\n",
+ "
\n",
+ " בדוגמה זו השתמשנו בעובדה שהפרמטר שמוגדר בעזרת שתי כוכביות הוא בהכרח מילון.
\n",
+ " עברנו על כל המפתחות והערכים שבו בעזרת הפעולה items
, והדפסנו את המתכון, רכיב אחר רכיב.\n",
+ "
\n",
+ " גרמו לפונקציה print_sushi_recipe
להדפיס את הרכיבים לפי סדר משקלם, מהנמוך לגבוה.\n",
+ "
\n", + " פרמטר המוגדר בעזרת שתי כוכביות תמיד יופיע בסוף רשימת הפרמטרים.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### תרגול ביניים: גזור פזורפ" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כתבו פונקציה בשם my_format
שמקבלת מחרוזת, ומספר בלתי מוגבל של פרמטרים עם שמות.
\n",
+ " הפונקציה תחליף כל הופעה של {key}
במחרוזת, אם key
הועבר כפרמטר לפונקציה.
\n",
+ " הערך שבו {key}
יוחלף הוא הערך שהועבר ל־key
במסגרת העברת הארגומנטים לפונקציה.
\n",
+ " הפונקציה לא תשתמש בפעולה format
של מחרוזות או בפונקציות שלא למדנו עד כה.\n",
+ "
\n", + " הנה כמה דוגמאות לקריאות לפונקציה ולערכי ההחזרה שלה:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "my_format(\"I'm Mr. {name}, look at me!\", name=\"Meeseeks\")
my_format(\"{a} {b} {c} {c}\", a=\"wubba\", b=\"lubba\", c=\"dub\")
my_format(\"The universe is basically an animal\", animal=\"Chicken\")
my_format(\"The universe is basically an animal\")
\n",
+ " נוכל לשלב יחד את כל הטכניקות שלמדנו עד עכשיו לפונקציה אחת.
\n",
+ " ניצור, לדוגמה, פונקציה שמחשבת עלות הכנה של עוגה.
\n",
+ "
\n", + " הפונקציה תקבל:\n", + "
\n", + "\n", + "\n", + " לצורך פישוט התרגיל, נתעלם לרגע מעניין הכמויות במתכון :)\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def calculate_cake_price(apply_discount, *ingredients, discount_rate=10, **prices):\n", + " if not apply_discount:\n", + " discount_rate = 0\n", + "\n", + " final_price = 0\n", + " for ingredient in ingredients:\n", + " final_price = final_price + prices.get(ingredient)\n", + " \n", + " final_price = final_price - (final_price * discount_rate / 100)\n", + " return final_price\n", + "\n", + "\n", + "calculate_cake_price(True, 'chocolate', 'cream', chocolate=30, cream=20, water=5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " הפונקציה נכתבה כדי להדגים את הטכניקה, והיא נראית די רע.
\n",
+ " ראו כמה קשה להבין איזה ארגומנט שייך לאיזה פרמטר בקריאה לפונקציה.
\n",
+ " יש להפעיל שיקול דעת לפני שימוש בטכניקות של קבלת פרמטרים מרובים.\n",
+ "
\n", + " שימו לב לסדר הפרמטרים בכותרת הפונקציה:\n", + "
\n", + "\n", + "apply_discount
).ingredients
).discount_rate
).prices
).\n", + " נסו לחשוב: למה נקבע דווקא הסדר הזה?\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " איך הייתם כותבים את אותה הפונקציה בדיוק בלי שימוש בטכניקות שלמדנו?
\n",
+ " השימוש בערכי ברירת מחדל מותר.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " יש מקרה קצה של ערכי ברירת מחדל שגורם לפייתון להתנהג קצת מוזר.
\n",
+ " זה קורה כשערך ברירת המחדל שהוגדר בכותרת הפונקציה הוא mutable:\n",
+ "
\n",
+ " עד כאן נראה כאילו הפונקציה פועלת באופן שהיינו מצפים ממנה.
\n",
+ " ערך ברירת המחדל של הפרמטר l
הוא רשימה ריקה, ולכן בקריאה השנייה חוזרת רשימה עם איבר בודד, ['a'].\n",
+ "
\n", + " נקרא לפונקציה עוד כמה פעמים, ונגלה משהו מוזר:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(append('b'))\n", + "print(append('c'))\n", + "print(append('d'))\n", + "print(append(4, [1, 2, 3]))\n", + "print(append('e'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " משונה ולא הגיוני! ציפינו לקבל את הרשימה ['b'] ואז את הרשימה ['c'] וכן הלאה.
\n",
+ " במקום זה בכל פעם מצטרף איבר חדש לרשימה. למה?\n",
+ "
\n",
+ " הסיבה לכך היא שפייתון קוראת את כותרת הפונקציה רק פעם אחת – בשלב ההגדרה של הפונקציה.
\n",
+ " בשלב הזה שבו פייתון תקרא את כותרת הפונקציה, ערך ברירת המחדל של l
יצביע לרשימה ריקה.
\n",
+ " מאותו רגע, בכל פעם שלא נעביר ל־l
ערך, l
תהיה אותה רשימת ברירת מחדל שהגדרנו בהתחלה.
\n",
+ " נדגים זאת בעזרת הדפסת ה־id
של הרשימה:\n",
+ "
\n",
+ " כיצד נפתור את הבעיה?
\n",
+ " דבר ראשון – נשתדל שלא להגדיר משתנים מטיפוס שהוא mutable בתוך כותרת הפונקציה.
\n",
+ " אם נרצה בכל זאת שהפרמטר יקבל רשימה כברירת מחדל, נעשה זאת כך:\n",
+ "
\n", + " שימו לב שהתופעה לא משתחזרת במבנים שהם immutable, כיוון שכשמם כן הם – אי אפשר לשנותם:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def increment(i=0):\n", + " i = i + 1\n", + " return i\n", + "\n", + "\n", + "print(increment(100))\n", + "print(increment())\n", + "print(increment())\n", + "print(increment())\n", + "print(increment(100))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### דוגמאות נוספות" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### חיקוי מדויק של פונקציית get למילונים:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " נרענן את זיכרוננו בנוגע ל־unpacking:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "range_arguments = [1, 10, 3]\n", + "range_result = range(*range_arguments)\n", + "print(list(range_result))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " או:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "preformatted_message = \"My name is {me}, and my sister is {sister}\"\n", + "parameters = {'me': 'Mei', 'sister': 'Satsuki'}\n", + "message = preformatted_message.format(**parameters)\n", + "print(message)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " אם כך, נוכל לכתוב:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get(dictionary, *args, **kwargs):\n", + " return dictionary.get(*args, **kwargs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " שימו לב שהכוכביות בשורה הראשונה עוזרות לנו לקבל מספר משתנה של ארגומנטים.
\n",
+ " הכוכביות בשורה השנייה הן unpacking, כפי שלמדנו בשבוע שעבר.\n",
+ "
\n", + " החיקוי הזה לא מועיל לנו במיוחד כרגע, אבל הוא יעבוד עבור כל סוג של פעולה.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## מונחים" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*args
. מאפשר לנו לקבל מספר בלתי מוגבל של ארגומנטים לפי מיקום.**kwargs
. מאפשר לנו לקבל מספר בלתי מוגבל של ארגומנטים לפי שם.\n", + " כתבו פונקציה בשם avg שמקבלת מספר בלתי מוגבל של ארגומנטים, ומדפיסה את הממוצע שלהם.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "avg(5, 6)
\n",
+ " תחזיר 5.5\n",
+ " avg(10, 5, 3)
\n",
+ " תחזיר 6\n",
+ " avg(2)
\n",
+ " תחזיר 2\n",
+ " avg()
\n",
+ " תחזיר None או שגיאה, לבחירתכם\n",
+ " \n",
+ " כתבו פונקציה בשם join, שמקבלת מספר בלתי מוגבל של רשימות, כל רשימה כפרמטר.
\n",
+ " על הפונקציה להיות מסוגלת לקבל פרמטר נוסף בשם sep
.
\n",
+ " על הפונקציה להחזיר רשימה אחת המורכבת מכלל הרשימות שהתקבלו כפרמטרים.
\n",
+ " אם סופק הפרמטר sep, יש לשרשר אותו כאיבר בין כל שתי רשימות. אם הוא לא סופק, יש לשרשר את התו \"-\"
במקום. \n",
+ "
join([1, 2], [8], [9, 5, 6], sep='@')
\n",
+ " תחזיר [1, 2, '@', 8, '@', 9, 5, 6]\n",
+ " join([1, 2], [8], [9, 5, 6])
\n",
+ " תחזיר [1, 2, '-', 8, '-', 9, 5, 6]\n",
+ " join([1])
\n",
+ " תחזיר [1]\n",
+ " join()
\n",
+ " תחזיר None או שגיאה, לבחירתכם\n",
+ " \n",
+ " ממשו פונקציה בשם get_recipe_price, שלה יש:
\n",
+ "
\n", + " הפונקציה תחזיר את המחיר שעלינו לשלם על קניית כל המצרכים.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "get_recipe_price({'chocolate': 18, 'milk': 8}, chocolate=200, milk=100)
get_recipe_price({'chocolate': 18, 'milk': 8}, optionals=['milk'], chocolate=300)
get_recipe_price({})
\n",
+ " הפונקציות שיצרנו עד כה נבנו כך שיחזירו ערך אחד בכל קריאה.
\n",
+ " הערך הזה יכול היה להיות מכל טיפוס שהוא: בוליאני, מחרוזת, tuple וכדומה.
\n",
+ " אם נרצה להחזיר כמה ערכים יחד, תמיד נוכל להחזיר אותם כרשימה או כ־tuple.
\n",
+ " אבל מה קורה כשאנחנו רוצים להחזיר סדרת ערכים גדולה מאוד או אפילו אין־סופית?\n",
+ "
\n", + " למשל:\n", + "
\n", + "\n", + "\n",
+ " בכלים שיש בידינו כרגע, נמצא שיש בעיה ליצור רשימות כאלו.
\n",
+ " עבור רשימות גדולות מאוד – לא יהיה למחשב די זיכרון ולבסוף הוא ייכשל בשמירת ערכים חדשים.
\n",
+ " ומה בנוגע לרשימות אין־סופיות? הן... ובכן... אין־סופיות, ולכן מלכתחילה לא נוכל ליצור אותן.\n",
+ "
\n",
+ " פתרון שמעניין לחשוב עליו הוא \"פונקציה עצלנית\".
\n",
+ " אם אנחנו בשום שלב לא יכולים להחזיק בזיכרון המחשב את כל האיברים (כי יש יותר מדי מהם, או כי זו סדרה אין־סופית),
\n",
+ " אולי נוכל לשלוח תחילה את הערך הראשון – ואת הערכים שבאים אחריו נשלח רק כשיבקשו אותם מאיתנו.\n",
+ "
\n",
+ " פונקציה עצלנית שכזו נקראת generator, ובכל פעם שנבקש ממנה, היא תחזיר לנו איבר יחיד מתוך סדרת ערכים.
\n",
+ " תחילה – היא תחזיר רק את הערך הראשון, בלי לחשב את שאר האיברים. אחר כך, באותו אופן, רק את השני, אחר כך רק את השלישי וכן הלאה.
\n",
+ " ההבדל העיקרי בין generator לבין פונקציה רגילה, הוא שב־generator נבחר להחזיר את הערכים אחד־אחד ולא תחת מבנה מאוגד.\n",
+ "
\n", + " נסכם: generator היא פונקציה שיוצרת עבורנו בכל פעם ערך אחד, מחזירה אותו, ומחכה עד שנבקש את האיבר הבא.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## שימוש" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### יצירת generator בסיסי" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " נתחיל בהגדרת generator מטופש למדי:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def silly_generator():\n", + " a = 1\n", + " yield a\n", + " b = a + 1\n", + " yield b\n", + " c = [1, 2, 3]\n", + " yield c" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " מעניין! זה נראה ממש כמו פונקציה. נקרא למבנה הזה שיצרנו \"פונקציית ה־generator\".
\n",
+ " אבל מהו ה־yield
המוזר הזה שנמצא שם?\n",
+ "
\n", + " לפני שנתהה על קנקנו, בואו ננסה לקרוא לפונקציה ונראה מה היא מחזירה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(silly_generator())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " אומנם למדנו שלא אומרים איכס על פונקציות, אבל מה קורה פה?\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בניגוד לפונקציות רגילות, קריאה ל־generator לא מחזירה ערך מייד.
\n",
+ " במקום ערך היא מחזירה מעין סמן, כמו בקובץ, שאפשר לדמיין כחץ שמצביע על השורה הראשונה של הפונקציה.
\n",
+ " נשמור את הסמן על משתנה:\n",
+ "
\n",
+ " בעקבות הקריאה ל־silly_generator
נוצר לנו סמן שמצביע כרגע על השורה a = 1
.
\n",
+ " המינוח המקצועי לסמן הזה הוא generator iterator.\n",
+ "
\n",
+ " אחרי שהרצנו את השורה our_generator = silly_generator()
, הסמן המדובר נשמר במשתנה בשם our_generator.
\n",
+ " זה זמן מצוין לבקש מה־generator להחזיר ערך.
\n",
+ " נעשה זאת בעזרת הפונקציה הפייתונית next
:\n",
+ "
\n",
+ " כדי להבין מה התרחש נצטרך להבין שני דברים חשובים שקשורים ל־generators:
\n",
+ "
next
היא כמו לחיצה על \"נגן\" (Play) – היא גורמת לסמן לרוץ עד שהוא מגיע לשורה של החזרת ערך.yield
דומה למילת המפתח return
– היא מפסיקה את ריצת הסמן, ומחזירה את הערך שמופיע אחריה.\n",
+ " אז היה לנו סמן שהצביע על השורה הראשונה. לחצנו Play, והוא הריץ את הקוד עד שהוא הגיע לנקודה שבה מחזירים ערך.
\n",
+ " ההבדל בין פונקציה לבין generator, הוא שכשאנחנו מחזירים ערך בעזרת yield
אנחנו \"מקפיאים\" את המצב שבו\n",
+ " יצאנו מהפונקציה.
\n",
+ " ממש כמו ללחוץ על \"Pause\".
\n",
+ " כשנקרא ל־next
בפעם הבאה – הפונקציה תמשיך לרוץ מאותו המקום שבו השארנו את הסמן, עם אותם ערכי משתנים.
\n",
+ " עכשיו הסמן מצביע על השורה b = a + 1
, ומחכה שמישהו יקרא שוב ל־next
כדי שהפונקציה תוכל להמשיך לרוץ:\n",
+ "
\n", + " נסכם מה קרה עד עכשיו:\n", + "
\n", + "\n", + "next
על ה־generator iterator, הרצנו את הסמן עד שה־generator החזיר ערך.next
על ה־generator iterator, וראינו שהוא ממשיך מהמקום שבו ה־generator הפסיק לרוץ פעם קודמת.\n",
+ " תוכלו לחזות מה יקרה אם נקרא שוב ל־next(our_generator)
?\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n", + " ננסה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(next(our_generator))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " יופי! הכל הלך כמצופה.
\n",
+ " אבל מה צופן לנו העתיד?
\n",
+ " בפעם הבאה שנבקש ערך מהפונקציה, הסמן שלנו ירוץ הלאה ולא ייתקל ב־yield
.
\n",
+ " במקרה כזה, נקבל שגיאת StopIteration, שמבשרת לנו ש־next
לא הצליח לחלץ מה־generator את הערך הבא.\n",
+ "
\n",
+ " מובן שאין סיבה להילחץ.
\n",
+ " במקרה הזה אפילו לא מדובר במשהו רע – פשוט כילינו את כל הערכים מה־generator iterator שלנו.
\n",
+ " פונקציית ה־generator עדיין קיימת!
\n",
+ " אפשר ליצור עוד generator iterator אם נרצה, ולקבל את כל הערכים שנמצאים בו באותה צורה:\n",
+ "
\n",
+ " אבל כשחושבים על זה, זה קצת מגוחך.
\n",
+ " בכל פעם שנרצה להשיג את הערך הבא נצטרך לרשום next
?
\n",
+ " חייבת להיות דרך טובה יותר!\n",
+ "
\n",
+ " אז למעשה, יש יותר מדרך טובה אחת להשיג את כל הערכים שיוצאים מ־generator מסוים.
\n",
+ " כהקדמה, נניח פה עובדה שלא תשאיר אתכם אדישים: ה־generator iterator הוא... iterable! הפתעת השנה, אני יודע!
\n",
+ " אמנם אי אפשר לפנות לאיברים שלו לפי מיקום, אך בהחלט אפשר לעבור עליהם בעזרת לולאת for
, לדוגמה:\n",
+ "
\n",
+ " מה מתרחש כאן?
\n",
+ " אנחנו מבקשים מלולאת ה־for
לעבור על ה־generator iterator שלנו.
\n",
+ " ה־for
עושה עבורנו את העבודה אוטומטית:\n",
+ "
next
.next
. כך עד שייגמרו האיברים ב־generator iterator.\n",
+ " שימו לב שהעובדות שלמדנו בנוגע לאותו \"סמן\" יבואו לידי ביטוי גם כאן.
\n",
+ " הרצה נוספת של הלולאה על אותו סמן לא תדפיס יותר איברים, כיוון שהסמן מצביע כעת על סוף פונקציית ה־generator:\n",
+ "
\n",
+ " למזלנו, לולאות for
יודעות לטפל בעצמן בשגיאת StopIteration
, ולכן שגיאה שכזו לא תקפוץ לנו במקרה הזה.\n",
+ "
\n", + " דרך אחרת, לדוגמה, היא לבקש להמיר את ה־generator iterator לסוג משתנה אחר שהוא גם iterable:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "our_generator = silly_generator()\n", + "items = list(our_generator)\n", + "print(items)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בקוד שלמעלה, השתמשנו בפונקציה list
שיודעת להמיר ערכים iterable־ים לרשימות.
\n",
+ " שימו לב שמה שלמדנו בנוגע ל\"סמן\" יבוא לידי ביטוי גם בהמרות:\n",
+ "
\n", + "נכתוב פונקציה רגילה שמקבלת מספר שלם, ומחזירה רשימה של כל המספרים השלמים מ־0 ועד אותו מספר (נשמע לכם מוכר?):\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def my_range(upper_limit):\n", + " numbers = []\n", + " current_number = 0\n", + " while current_number < upper_limit:\n", + " numbers.append(current_number)\n", + " current_number = current_number + 1\n", + " return numbers\n", + "\n", + "\n", + "for number in my_range(1000):\n", + " print(number)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בפונקציה הזו אנחנו יוצרים רשימת מספרים חדשה, המכילה את כל המספרים בין 0 לבין המספר שהועבר לפרמטר upper_limit.
\n",
+ " אך ישנה בעיה חמורה – הפעלת הפונקציה גורמת לניצול משאבים רבים!
\n",
+ " אם נכניס כארגומנט 1,000 – נצטרך להחזיק רשימה המכילה 1,000 איברים שונים, ואם נכניס מספר גדול מדי – עלול להיגמר לנו הזיכרון.\n",
+ "
\n",
+ " אבל איזו סיבה יש לנו להחזיק בזיכרון את רשימת כל המספרים?
\n",
+ " אם לא עולה צורך מובהק שכזה, ייתכן שעדיף להחזיק בזיכרון מספר אחד בלבד בכל פעם, ולהחזירו מייד בעזרת generator:\n",
+ "
\n",
+ " שימו לב כמה הגרסה הזו אלגנטית יותר!
\n",
+ " בכל פעם אנחנו פשוט שולחים את ערכו של מספר אחד (current_number) החוצה.
\n",
+ " כשמבקשים את הערך הבא מה־generator iterator, פונקציית ה־generator חוזרת לעבוד מהנקודה שבה היא עצרה:
\n",
+ " היא מעלה את ערכו של המספר הנוכחי, בודקת אם הוא נמוך מ־upper_limit, ושולחת גם אותו החוצה.
\n",
+ " בשיטה הזו, my_range(numbers)
לא מחזירה לנו רשימה של התוצאות – אלא generator iterator שמחזיר ערך אחד בכל פעם.
\n",
+ " כך אנחנו לעולם לא מחזיקים בזיכרון 1,000 מספרים בו־זמנית.\n",
+ "
\n",
+ " לפניכם פונקציה שמקבלת רשימה, ומחזירה עבור כל מספר ברשימה את ערכו בריבוע.
\n",
+ " זוהי גרסה מעט בזבזנית שמשתמשת בהרבה זיכרון. תוכלו להמיר אותה להיות generator?\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " לעיתים ניאלץ לבצע חישוב ארוך, שהשלמתו תימשך זמן רב מאוד.
\n",
+ " במקרה כזה, נוכל להשתמש ב־generator כדי לקבל חלק מהתוצאה בזמן אמת,
\n",
+ " בזמן שבפונקציה \"רגילה\" נצטרך להמתין עד סיום החישוב כולו.\n",
+ "
\n",
+ " שלשה פיתגורית, לדוגמה, היא שלישיית מספרים שלמים וחיוביים, $a$, $b$ ו־$c$, שעונים על הדרישה $a^2 + b^2 = c^2$.
\n",
+ " אם כך, כדי ששלושה מספרים שאנחנו בוחרים ייחשבו שלשה פיתגורית,
\n",
+ " הסכום של ריבוע המספר הראשון וריבוע המספר השני, אמור להיות שווה לערכו של המספר השלישי בריבוע.\n",
+ "
\n", + " אלו דוגמאות לשלשות פיתגוריות:\n", + "
\n", + "\n", + "\n", + " ננסה למצוא את כל השלשות הפיתגוריות מתחת ל־10,000 בעזרת קוד שרץ על כל השלשות האפשריות:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def find_pythagorean_triples(upper_bound=10_000):\n", + " pythagorean_triples = []\n", + " for c in range(3, upper_bound):\n", + " for b in range(2, c):\n", + " for a in range(1, b):\n", + " if a ** 2 + b **2 == c ** 2:\n", + " pythagorean_triples.append((a, b, c))\n", + " return pythagorean_triples\n", + "\n", + "\n", + "for triple in find_pythagorean_triples():\n", + " print(triple)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " הרצת התא הקודם תתקע את המחברת (חישוב התוצאה יימשך זמן רב).
\n",
+ " כדי להיות מסוגלים להריץ את התאים הבאים, לחצו 00 לאחר הרצת התא, ובחרו Restart.
\n",
+ " אל דאגה – האתחול יתבצע אך ורק עבור המחברת, ולא עבור מחשב.\n",
+ "
\n",
+ " יו, כמה זמן נמשכת הרצת הקוד הזה... 😴
\n",
+ " הלוואי שעד שהקוד הזה היה מסיים היינו מקבלים לפחות חלק מהתוצאות!
\n",
+ " נפנה ל־generator־ים לעזרה:\n",
+ "
\n",
+ " איך זה קרה? קיבלנו את התשובה בתוך שבריר שנייה!
\n",
+ " ובכן, זה לא מדויק – קיבלנו חלק מהתשובות. שימו לב שהקוד ממשיך להדפיס :)
\n",
+ "
\n",
+ " להזכירכם, ה־generator שולח את התוצאה החוצה מייד כשהוא מוצא שלשה אחת,
\n",
+ " וה־for מקבל מה־generator iterable כל שלשה ברגע שהיא נמצאה.
\n",
+ " ברגע שה־for מקבל שלשה, הוא מבצע את גוף הלולאה עבור אותה שלשה, ורק אז מבקש מ־generator את האיבר הבא.
\n",
+ " בגלל האופי של generators, הקוד בתא האחרון מדפיס לנו כל שלשה ברגע שהוא מצא אותה, ולא מחכה עד שיימצאו כל השלשות.\n",
+ "
\n", + " \"פירוק לגורמים של מספר שלם\" היא בעיה שחישוב פתרונה נמשך זמן רב במחשבים מודרניים.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " עליכם לכתוב פונקציה שמקבלת מספר חיובי שלם $n$, ומחזירה קבוצת מספרים שמכפלתם (תוצאת הכפל ביניהם) היא $n$.
\n",
+ " לדוגמה, המספר 1,386 בנוי מהמכפלה של קבוצת המספרים $2 \\cdot 3 \\cdot 3 \\cdot 7 \\cdot 11$.
\n",
+ " כל מספר בקבוצת המספרים הזו חייב להיות ראשוני.
\n",
+ " להזכירכם: מספר ראשוני הוא מספר שאין לו מחלקים חוץ מעצמו ומ־1.\n",
+ "
\n",
+ " הניחו שהמספר שהתקבל אינו ראשוני.
\n",
+ " מה היתרון של generator על פני פונקציה רגילה שעושה אותו דבר?\n",
+ "
\n",
+ " רמז: אם תנסו לחלק את המספר ב־2, ואז ב־3 (וכן הלאה), בסופו של דבר תגיעו למחלק ראשוני של המספר.
\n",
+ " רמז עבה: בכל פעם שמצאתם מחלק אחד למספר, חלקו את המספר במחלק, והתחילו את החיפוש מחדש. מתי עליכם לעצור?\n",
+ "
\n",
+ " עבור בעיות מסוימות, נרצה להיות מסוגלים להחזיר אין־סוף תוצאות.
\n",
+ " ניקח כדוגמה לסדרה אין־סופית את סדרת פיבונאצ'י, שבה כל איבר הוא סכום זוג האיברים הקודמים לו:
\n",
+ " $1, 1, 2, 3, 5, 8, \\ldots$\n",
+ "
\n",
+ " נממש פונקציה שמחזירה לנו את סדרת פיבונאצ'י.
\n",
+ " בפונקציה רגילה אין לנו אפשרות להחזיר מספר אין־סופי של איברים, ולכן נצטרך להחליט על מספר האיברים המרבי שנרצה להחזיר:\n",
+ "
\n",
+ " לעומת זאת, ל־generators לא חייב להיות סוף מוגדר.
\n",
+ " נשתמש ב־while True
שתמיד מתקיים, כדי שבסופו של דבר – תמיד נגיע ל־yield
: \n",
+ "
\n",
+ " generators אין־סופיים יכולים לגרום בקלות ללולאות אין־סופיות, גם בלולאות for
.
\n",
+ " שימו לב לצורת ההתעסקות העדינה בדוגמאות למעלה.
\n",
+ " הרצת לולאת for
ישירות על ה־generator iterator הייתה מכניסה אותנו ללולאה אין־סופית.\n",
+ "
\n", + " כתבו generator שמחזיר את כל המספרים השלמים הגדולים מ־0.\n", + "
\n", + "\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n", + " נגדיר generator פשוט שמחזיר את האיברים 1, 2 ו־3:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def simple_generator():\n", + " yield 1\n", + " yield 2\n", + " yield 3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " ניצור שני generator iterators (\"סמנים\") שונים שמצביעים לשורה הראשונה של ה־generator שמופיע למעלה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "first_gen = simple_generator()\n", + "second_gen = simple_generator()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בעניין זה, חשוב להבין שכל אחד מה־generator iterators הוא \"חץ\" נפרד שמצביע לשורה הראשונה ב־simple_generator.
\n",
+ " אם נבקש מכל אחד מהם להחזיר ערך, נקבל משניהם את 1, ואותו חץ דמיוני יעבור בשני ה־generator iterators להמתין בשורה השנייה:\n",
+ "
\n", + " נוכל לקדם את first_gen, לדוגמה, לסוף הפונקציה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(next(first_gen))\n", + "print(next(first_gen))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אבל second_gen הוא חץ נפרד, שעדיין מצביע לשורה השנייה של פונקציית ה־generator.
\n",
+ " אם נבקש ממנו את הערך הבא, הוא ימשיך את המסע מהערך 2:
\n",
+ "
\n",
+ " ממצב זה נוכל להסיק שאפשר ליצור יותר מ־generator iterator אחד עבור כל פונקציית generator.
\n",
+ " כל אחד מה־generator iterators יחזיק תמונת מצב עצמאית של המקום שבו עצר הסמן ושל ערכי המשתנים.
\n",
+ " ההתנהלות של כל generator iterator תקרה בנפרד, ולא תושפע בשום צורה מה־generator iterators האחרים.
\n",
+ "
\n",
+ " בשלב זה יש לנו הרבה מבנים שאפשר לרוץ עליהם, וכל המינוח סביב עניין האיטרביליות נעשה מעט מבלבל.
\n",
+ " ננסה לעשות סדר בדברים:\n",
+ "
next()
.iterable[0]
), כמו מחרוזות, רשימות ו־tuple־ים.yield
ומגדירה אילו ערכים יוחזרו מה־generator.\n",
+ " Generators הם פונקציות שמאפשרות לנו להחזיר סדרות ערכים באופן מדורג.
\n",
+ " כשנקרא לפונקציית generator, היא תחזיר לנו generator iterator שישמש מעין \"סמן\".
\n",
+ " הסמן ישמור \"מצב\" שמתאר את המקום שבו אנחנו שוהים בתוך הפונקציה, ואת הערכים שחושבו במהלך ריצתה עד כה.
\n",
+ " בכל שלב, נוכל לבקש את הערך הבא ב־generator בעזרת קריאה לפונקציה next
על ה־generator iterator.
\n",
+ " נוכל גם להשתמש במבנים שיזיזו עבורנו את הסמן, כמו for
או המרה לסוג נתונים אחר שגם הוא iterable.\n",
+ "
\n", + " ל־generators יתרונות רבים:\n", + "
\n", + "\n", + "\n", + " קראו בוויקיפדיה על דרך החישוב של ספרת הביקורת במספרי הזהות בישראל. \n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אפשר לחלק רול סושי של 6 יחידות לאדם אחד, ל־2 אנשים, ל־3 אנשים ול־6 אנשים.
\n",
+ " נתעלם ממצבים שבהם כל אדם מקבל רק חתיכת סושי אחת. זה נשמע לי עצוב.
\n",
+ " נגדיר \"מנה מושלמת לחלוקה\" כמנה שאם נסכום את כל הצורות לחלק אותה, נקבל את גודל המנה עצמה.\n",
+ "
\n", + " לדוגמה:\n", + "
\n", + "\n", + "\n", + " כתבו תוכנית שמדפיסה באופן אין־סופי את כל גודלי המנות שנחשבים מושלמים לחלוקה.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### לחששנית" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בקובץ resources/logo.jpg מופיע לוגו הקורס, ובתוכו מוכמנים מסרים סודיים אחדים.
\n",
+ " המסרים הם מחרוזות באורך 5 אותיות לפחות, כתובים באותיות אנגליות קטנות בלבד ומסתיימים בסימן קריאה.\n",
+ "
\n",
+ " פתחו את הלוגו לקריאה בתצורה בינארית, וחלצו ממנו את המסרים הסודיים.
\n",
+ " זכרו שהקובץ עלול להיות גדול מאוד, ועדיף שלא לקרוא את כולו במכה אחת.
\n",
+ " מצאו באינטרנט עזרה בנוגע לפתיחת קבצים בצורה בינארית ולקריאה מדורגת של הקובץ.
\n",
+ " הקפידו שלא להשתמש בטכניקות שלא למדנו (או להוסיף אותן רק בנוסף לפתרון שכזה).\n",
+ "
תרגילים
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## הקדמה" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " התרגול השבוע כרוך בהשקעת מאמץ ניכר, ומטרתו לחזור ולחזק את היסודות שנלמדו בשבועות קודמים.
\n",
+ " השבוע ניצב בפניכם אתגר חדש: חזקו את יכולות החיפוש שלכם באינטרנט ובתיעוד של פייתון, ונסו למצוא מודולים שיסייעו לכם בפתרון הבעיות.
\n",
+ " זוהי יכולת חשובה מאוד עבור מתכנתים, וזהו בהחלט חלק נכבד מהאתגר בתרגילים שמופיעים לפניכם במחברת הזו.\n",
+ "
\n", + " פתרו את התרגיל \"מלחמה וזהו\" משבוע 4, הפעם עם הידע החדש שרכשתם.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## כלים שלובים" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כתבו פונקציה בשם interleave
שמקבלת פרמטר iterable אחד או יותר, ומחזירה רשימה של האיברים שזורים זה בזה.\n",
+ "
\n",
+ "לדוגמה, עבור הקריאה interleave('abc', [1, 2, 3], ('!', '@', '#'))
יוחזר הערך: ['a', 1, '!', 'b', 2, '@', 'c', 3, '#'].\n",
+ "
\n", + " ממשו גרסה של generator ל־interleave.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## שובו של קשטן" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בחנות של אדון קשטן ישנו מילון המכיל את כל המוצרים בחנות, ואת המלאי של כל מוצר.
\n",
+ " רשמו פונקציה שמקבלת את המוצרים שהתקבלו במשלוח האחרון, ומעדכנת את המלאי של אדון קשטן בהתאם לכך.\n",
+ "
\n", + "לדוגמה:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "add_inventory({'cheese': 2, 'milk': 1}, cheese=3, chocolate=5)
add_inventory({'refrigerator': 7, 'goat': 1}, honey=2)
\n",
+ " הספרן שהיה בדרכו להביא לי את העותק הנחשק של \"הארי פוטר והשיטה הרציונלית\" מעד.
\n",
+ " הספרים התעופפו ופרקי הספרים התפזרו לכל עבר.
\n",
+ " בתיקייה resources מצורף קובץ מכווץ ובו כל פרקי הסיפור, אך למרבה הצער שם הקבצים אינו תואם לתוכן שלהם.
\n",
+ " שנו את שמו של כל קובץ כך ששמו החדש יהיה מספר תלתַ־ספרתי שמתאר את מספר הפרק, ואחריו את שם הפרק.
\n",
+ " לדוגמה: עבור הפרק הראשון, שם הקובץ צריך להיות 001 A Day of Very Low Probability.
\n",
+ " במהלך התרגיל ייתכן שתצטרכו לשלב עבודה עם כמה ספריות, ובהן כאלו שלא למדנו.\n",
+ "
\n",
+ " טיפ: גבו את הקבצים לפני שתתחילו לעבוד עליהם.
\n",
+ " רמז: אפשר לפתוח קובצי html ידנית, כקובצי טקסט.
\n",
+ " רמז 2: כיצד משנים שמות של קבצים באמצעות פייתון?\n",
+ "
\n", + " מוצגת להלן אחת מהגרסאות של פרדוקס יום ההולדת:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " מה הסיכוי שבכיתה שבה 23 תלמידים (חחחחח) יציינו שני תלמידים יום הולדת באותו תאריך?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "
\n", + "הנחות המוצא שלנו בפתירת הבעיה הן כדלהלן:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " הפתרון של ה\"פרדוקס\" מפתיע את רוב האנשים ששומעים עליו לראשונה.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כתבו תוכנה שמגרילה תאריכי יום הולדת עבור 10,000 כיתות, שבכל אחת מהן לומדים 23 תלמידים.
\n",
+ " מהו אחוז הכיתות שבהן ציינו 2 ימי הולדת באותו תאריך? השוו עם הפתרון המוצג בוויקיפדיה.
\n",
+ " דאגו שהפתרון שלכם יתחשב בכך שקיימות שנים מעוברות.
\n",
+ "
\n", + " השתמשו בתוכנה שכתבתם כדי לחשב את הסיכויים עבור כיתות שבהן תלמיד אחד, ועד כיתות ישראליות ממוצעות שבהן 366 תלמידים.\n", + "
" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/content/week05/images/deeper.svg b/content/week05/images/deeper.svg new file mode 100644 index 0000000..1b7344f --- /dev/null +++ b/content/week05/images/deeper.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/content/week05/images/deeper2.svg b/content/week05/images/deeper2.svg new file mode 100644 index 0000000..0c4a5e2 --- /dev/null +++ b/content/week05/images/deeper2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/content/week05/images/exercise.svg b/content/week05/images/exercise.svg new file mode 100644 index 0000000..21feb9f --- /dev/null +++ b/content/week05/images/exercise.svg @@ -0,0 +1,48 @@ + + +\n", + " קבוצה (או set) בפייתון היא אוסף של איברים שמתאפיינת בתכונות הבאות:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " לדוגמה:\n", + "
\n", + "\n", + "\n", + " עד מחברת זו, לו היינו מתבקשים להגדיר מבנה נתונים שיאחסן עבורנו שמות של אומנים איטלקים ידועים, היינו משתמשים ברשימה או ב־tuple־ים:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# List:\n", + "italian_artists = ['Donatelo', 'Giotto', 'Leonardo', 'Masaccio', 'Michelangelo', 'Raphael', 'Titian']\n", + "# Tuple:\n", + "italian_artists = ('Donatelo', 'Giotto', 'Leonardo', 'Masaccio', 'Michelangelo', 'Raphael', 'Titian')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " אך כיוון שמדובר באוסף איברים ללא חזרות וללא סדר מוגדר, אפשר לשמור אותם כ־set:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "italian_artists = {'Donatelo', 'Giotto', 'Leonardo', 'Masaccio', 'Michelangelo', 'Raphael', 'Titian'}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אפשר לראות שההגדרות דומות מאוד במראה שלהן.
\n",
+ " כדי להגדיר קבוצה, נפתח סוגריים מסולסלים (כמו במילון), ונציין את איבריה, כשהם מופרדים זה מזה בפסיקים.
\n",
+ " אם תרצו, תוכלו לדמיין קבוצה כמילון שבו יש רק מפתחות.\n",
+ "
\n",
+ " נוכל גם להגדיר קבוצה ריקה באמצעות הפונקציה set
.
\n",
+ " נגדיר, לדוגמה, את קבוצת כל החדי־קרן הוורודות הבלתי נראות:\n",
+ "
\n", + " תעלול נחמד נוסף הוא המרת iterable לקבוצה, באמצעות אותה הפונקציה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "numbers = range(10)\n", + "numbers = set(numbers)\n", + "print(numbers)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בהגדרה שבתחילת הפרק ציינו שקבוצה לא תכיל כמה איברים בעלי ערך זהה.
\n",
+ " למרות ההגבלה הזו, בהגדרת הקבוצה אפשר לספק ערכים שחוזרים על עצמם.
\n",
+ " הקבוצה \"תבלע\" את הכפילויות בערכים, ותשאיר רק איבר אחד מכל ערך:\n",
+ "
\n",
+ " הגדירו קבוצה שאיבריה הם האותיות הקטנות והגדולות באנגלית.
\n",
+ " ישנו פתרון פשוט שלא מצריך מכם לכתוב את כל הא\"ב.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n", + " נגדיר שוב את קבוצת האומנים האיטלקים:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "italian_artists = {'Donatelo', 'Giotto', 'Leonardo', 'Masaccio', 'Michelangelo', 'Raphael', 'Titian'}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אחד מיתרונותיהן רבי־הערך של קבוצות הוא היכולת לבצע ביניהן פעולות בקלות.
\n",
+ " נדגים את הרעיון בעזרת הגדרת קבוצה נוספת, הפעם של דמויות מחוברות הקומיקס של צבי הנינג'ה:\n",
+ "
\n",
+ " כעת נוכל, לדוגמה, לבחור את כל הדמויות מצבי הנינג'ה שנושאות שם של אומן איטלקי מפורסם.
\n",
+ " הקבוצה שנוצרת מהאיברים המשותפים לשתי הקבוצות נקראת קבוצת החיתוך.\n",
+ "
\n",
+ " ישנן פעולות רבות נוספות שאפשר לבצע בין קבוצות.
\n",
+ " דרך קלה להמחיש את היחסים ההדדיים בין הקבוצות הללו היא תרשים שנקרא \"דיאגרמת ון\":\n",
+ "
\n",
+ " נוכל גם לבקש, לדוגמה, את שמות האומנים האיטלקים שאינם דמויות מצבי הנינג'ה.
\n",
+ " נקבל בחזרה את השמות שמופיעים בתרשים בעיגול הטורקיז, אך לא בחיתוך בין שני העיגולים:\n",
+ "
\n",
+ " באותה צורה נוכל לבקש את שמות כל הדמויות מצבי הנינג'ה שאינם אומנים איטלקים.
\n",
+ " אלו השמות שמופיעים אך ורק בעיגול הורוד, ולא בחיתוך בין שני העיגולים:\n",
+ "
\n",
+ " אפשר לבקש גם את קבוצת האיחוד, שמורכבת מכל הדמויות שקיימות בשתי הקבוצות.
\n",
+ " אלו כל הדמויות שמופיעות בשרטוט:\n",
+ "
\n",
+ " או את כל הדמויות שלא משותפות לשתי הקבוצות.
\n",
+ " אלו כל הדמויות שמופיעות מחוץ לחיתוך של שני העיגולים:\n",
+ "
\n",
+ " צרו קבוצה שאיבריה הם 10 מספרי פיבונאצ'י הראשונים, וקבוצה נוספת שאיבריה הם המספרים הזוגיים בין 1 ל־15.
\n",
+ " שרטטו דיאגרמת ון של שתי הקבוצות, ובדקו שהשרטוט נאמן למציאות בעזרת הפעולות שלמדתם למעלה.\n",
+ "
\n",
+ " בתיקיית resources נמצאים הקבצים hamlet.txt ו־the-monkeys-paw.txt.
\n",
+ " כמה מילים משותפות לשני החיבורים? כמה שונות?
\n",
+ " כמה מהמילים מופיעות רק בכפת הקוף, וכמה רק בהמלט?\n",
+ "
\n",
+ " קבוצה היא mutable – אפשר לערוך אותה מבלי ליצור קבוצה חדשה, בדומה לרשימות או למילונים.
\n",
+ " תכונה זו מאפשרת לנו לצרף לקבוצה איברים חדשים או להסיר ממנה איברים קיימים בקלות רבה.\n",
+ "
\n",
+ " נוסיף לקבוצת שמות הדמויות בצבי הנינג'ה את אפריל או'ניל באמצעות הפעולה add
:\n",
+ "
\n",
+ " ונמחק מהקבוצה את שרדר הרשע באמצעות הפעולה remove
:\n",
+ "
\n", + " שימו לב שניסיון למחוק ערך שלא קיים בקבוצה יכעיס את פייתון:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ninja_turtles_characters.remove(\"Krang\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אלא אם כן נשתמש בפעולה discard
, שמאפשרת לנו למחוק איברים מקבוצות, בלי לחשוש משגיאה:\n",
+ "
\n",
+ " ולסיום – הדמויות הרעות תמיד חוזרות לסיבוב שני.
\n",
+ " נרחיב את הקבוצה שלנו ונוסיף אליה את הנבלים מ\"שבט הרגל\" (בחיי, ככה הם תרגמו את Foot Clan!) במכה אחת, באמצעות הפעולה update
:\n",
+ "
\n",
+ " צרו קבוצה של 100 המספרים הראשוניים הראשונים, וצרפו אליה את כל המספרים הזוגיים בין 1 ל־1,000.
\n",
+ " צרו קבוצה נוספת של המספרים בין 1 ל־1,000 שסכום ספרותיהם קטן מ־4.
\n",
+ " כמה מספרים משותפים לשתי הקבוצות שיצרתם?\n",
+ "
\n",
+ " אחת התכונות המועילות ביותר של קבוצות היא שאפשר לבצע בעזרתן בדיקות שייכות במהירות רבה.
\n",
+ " בדיוק כמו חיפוש מפתח במילון, בקבוצה אפשר למצוא ערך בתוך זמן קצר מאוד.\n",
+ "
\n", + " אפשר גם לבדוק אם כל האיברים של קבוצה אחת נמצאים בתוך קבוצה אחרת:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(italian_artists)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "only_turtles.issubset(italian_artists)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " ולהפך – אם קבוצה מסוימת מכילה את כל איברי הקבוצה השנייה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "italian_artists.issuperset(only_turtles)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בקובץ words.txt ישנה רשימת מילים ארוכה מאוד.
\n",
+ "
\n",
+ " כמה זמן בממוצע נמשך חיפוש המילה ברשימה? ובקבוצה?
\n",
+ " רמז: השתמשו במודול time.\n",
+ "
\n",
+ " לעיתים יהיה לנו נוח יותר להשתמש באופרטורים במקום בפעולות.
\n",
+ " לדוגמה, הפעולה של חיתוך בין שמות הדמויות בצבי הנינג'ה לשמות האומנים האיטלקים שנעשתה כך:\n",
+ "
\n", + " יכולה להיעשות גם כך:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "italian_artists & ninja_turtles_characters" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " השימוש באופרטורים בין קבוצות יכול לקצר את הכתיבה ולשוות לה מראה נקי יותר.
\n",
+ " ריכזנו עבורכם אופרטורים שבהם משתמשים תדיר כשעובדים עם קבוצות:\n",
+ "
שם פעולה | \n", + "שם הפעולה בפייתון | \n", + "אופרטור | \n", + "דוגמה | \n", + "
---|---|---|---|
איחוד | \n", + "union | \n", + "| | \n",
+ " {1, 3, 5} | {1, 2, 3} מחזיר {1, 2, 3, 5} | \n",
+ "
חיתוך | \n", + "intersection | \n", + "& | \n",
+ " {1, 3, 5} & {1, 2, 3} מחזיר {1, 3} | \n",
+ "
הפרש | \n", + "difference | \n", + "- | \n",
+ " \n",
+ " {1, 3, 5} - {1, 2, 3} מחזיר {5} \n", + " {1, 2, 3} - {1, 3, 5} מחזיר {2} \n",
+ " | \n",
+ "
הפרש סימטרי | \n", + "symmetric_difference | \n", + "^ | \n",
+ " {1, 2, 3} ^ {1, 3, 5} מחזיר {2, 5} | \n",
+ "
בדיקת שייכות/הכלה | \n", + "issubset/issuperset | \n", + "<= | \n",
+ " \n",
+ " {1, 2, 3} <= {1, 3, 5} מחזיר False \n", + " {1, 2, 3} <= {1, 2, 3, 4, 5} מחזיר True \n",
+ " | \n",
+ "
\n",
+ " שימו לב לכך שאופרטורים בין קבוצות לרוב יוצרים קבוצה חדשה.
\n",
+ " במקרה שאנחנו יכולים לשנות קבוצה קיימת ולנצל את היותה של קבוצה mutable, נעדיף לבחור בדרך הפעולה הזו כדי לייעל את התוכנית שלנו.
\n",
+ "
\n", + " כך, לדוגמה, הוספת איברים לקבוצה בצורה הזו:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "primes = {2, 3, 5, 7}\n", + "primes_to_add = {11, 13, 17, 19}\n", + "primes.update(primes_to_add)\n", + "print(primes)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " עדיפה על הצורה הזו:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "primes = {2, 3, 5, 7}\n", + "primes_to_add = {11, 13, 17, 19}\n", + "primes = primes | primes_to_add\n", + "print(primes)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## תרגיל לדוגמה" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " כתבו פונקציה שמקבלת נתיב ל־2 תיקיות, ומחזירה שמות של קבצים שמופיעים בשתי התיקיות.\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "\n", + "def get_filenames(path):\n", + " for file in os.scandir(path):\n", + " yield file.name\n", + "\n", + "\n", + "def common_filenames(path1, path2):\n", + " path1_files = set(get_filenames(path1))\n", + " path2_files = set(get_filenames(path2))\n", + " return path1_files & path2_files\n", + "\n", + "\n", + "common_filenames('images', 'resources/week5_images')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## תרגילים" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### חזרת" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כתבו פונקציה בשם uniquify שמקבלת רשימה של איברים, ומחזירה רשימה של אותם איברים וללא כפילויות.
\n",
+ " הניחו שאיברי כל הרשימה הם immutable.\n",
+ "
\n",
+ " כתבו פונקציה בשם count_specials שמקבלת מספר שלם חיובי $n$.
\n",
+ " הפונקציה תחזיר את מספר המספרים החיוביים הנמוכים מ־$n$, שמתחלקים ב־3 או ב־7 ללא שארית.
\n",
+ " לדוגמה, עבור $n=22$, המספרים הם: 3, 6, 7, 9, 12, 14, 15, 18 ו־21. במקרה כזה הפונקציה תחזיר 9.\n",
+ "
\n", + " ודאו שאתם משתמשים ב־set בפתרון התרגיל.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### שטוחלנדיה" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בפריסת מקלדת סטנדרטית ישנן 3 שורות של מקשי אותיות.
\n",
+ " האותיות שנמצאות בפינת המקלדת השמאלית־עליונה מרכיבות את הצירוף qwerty.
\n",
+ " מבין שמות כל המדינות בארצות הברית, ישנו רק שם מדינה אחד שאפשר לכתוב בעזרת שורה אחת בלבד במקלדת.
\n",
+ "
\n",
+ " קראו את שמות כל המדינות בארצות־הברית מהקובץ resources/states.txt.
\n",
+ " כתבו פונקציה בשם find_special_state.
\n",
+ " הפונקציה תחזיר את שם המדינה שאפשר להרכיב בעזרת האותיות המופיעות באותה השורה במקלדת.\n",
+ "
\n",
+ " לדוגמה, potter או hash הן מילים שנכתבו בעזרת שורה אחת במקלדת.
\n",
+ " turtle נכתבה בעזרת 2 שורות, ו־ninja נכתבה בעזרת 3 שורות.\n",
+ "
\n",
+ " בפסקאות הקרובות נבחן פונקציות מזווית ראייה מעט שונה מהרגיל.
\n",
+ " בואו נקפוץ ישירות למים!\n",
+ "
\n",
+ " תכונה מעניינת שמתקיימת בפייתון היא שפונקציה היא ערך, בדיוק כמו כל ערך אחר.
\n",
+ " נגדיר פונקציה שמעלה מספר בריבוע:\n",
+ "
\n", + " נוכל לבדוק מאיזה טיפוס הפונקציה (אנחנו לא קוראים לה עם סוגריים אחרי שמה – רק מציינים את שמה):\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "type(square)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " ואפילו לבצע השמה שלה למשתנה, כך ששם המשתנה החדש יצביע עליה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ribua = square\n", + "\n", + "print(square(5))\n", + "print(ribua(5))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " מה מתרחש בתא למעלה?
\n",
+ " כשהגדרנו את הפונקציה square, יצרנו לייזר עם התווית square שמצביע לפונקציה שמעלה מספר בריבוע.
\n",
+ " בהשמה שביצענו בשורה הראשונה בתא שלמעלה, הלייזר שעליו מודבקת התווית ribua כוון אל אותה הפונקציה שעליה מצביע הלייזר square.
\n",
+ " כעת square ו־ribua מצביעים לאותה פונקציה. אפשר לבדוק זאת כך:\n",
+ "
\n", + " בשלב הזה אצטרך לבקש מכם לחגור חגורות, כי זה לא הולך להיות טיול רגיל הפעם.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### פונקציות במבנים מורכבים" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אם פונקציה היא בסך הכול ערך, ואם אפשר להתייחס לשם שלה בכל מקום, אין סיבה שלא נוכל ליצור רשימה של פונקציות!
\n",
+ " ננסה לממש את הרעיון:\n",
+ "
\n",
+ " כעת יש לנו רשימה בעלת 4 איברים, שכל אחד מהם מצביע לפונקציה שונה.
\n",
+ " אם נרצה לבצע פעולת חיבור, נוכל לקרוא ישירות ל־add או (בשביל התרגול) לנסות לאחזר אותה מהרשימה שיצרנו:\n",
+ "
\n", + " אם נרצה, נוכל אפילו לעבור על רשימת הפונקציות בעזרת לולאה ולהפעיל את כולן, זו אחר זו:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for function in functions:\n", + " print(function(5, 2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בכל איטרציה של לולאת ה־for
, המשתנה function עבר להצביע על הפונקציה הבאה מתוך רשימת functions.
\n",
+ " בשורה הבאה קראנו לאותה הפונקציה ש־function מצביע עליה, והדפסנו את הערך שהיא החזירה. \n",
+ "
\n",
+ " כיוון שרשימה היא מבנה ששומר על סדר האיברים שבו, התוצאות מודפסות בסדר שבו הפונקציות שמורות ברשימה.
\n",
+ " התוצאה הראשונה שאנחנו רואים היא תוצאת פונקציית החיבור, השנייה היא תוצאת פונקציית החיסור וכן הלאה.\n",
+ "
\n",
+ " כתבו פונקציה בשם calc שמקבלת כפרמטר שני מספרים וסימן של פעולה חשבונית.
\n",
+ " הסימן יכול להיות אחד מאלה: +
, -
, *
או /
.
\n",
+ " מטרת הפונקציה היא להחזיר את תוצאת הביטוי החשבוני שהופעל על שני המספרים.
\n",
+ " בפתרונכם, השתמשו בהגדרת הפונקציות מלמעלה ובמילון.\n",
+ "
\n",
+ " נמשיך ללהטט בפונקציות.
\n",
+ "
\n",
+ " פונקציה נקראת \"פונקציה מסדר גבוה\" (higher order function) אם היא מקבלת כפרמטר פונקציה.
\n",
+ " ניקח לדוגמה את הפונקציה calculate:\n",
+ "
\n",
+ " בקריאה ל־calculate, נצטרך להעביר פונקציה ושני מספרים.
\n",
+ " נעביר לדוגמה את הפונקציה divide שהגדרנו קודם לכן:\n",
+ "
\n",
+ " מה שמתרחש במקרה הזה הוא שהעברנו את הפונקציה divide כארגומנט ראשון.
\n",
+ " הפרמטר function בפונקציה calculate מצביע כעת על פונקציית החילוק שהגדרנו למעלה.
\n",
+ " מכאן, שהפונקציה תחזיר את התוצאה של divide(5, 2)
– הרי היא 2.5.\n",
+ "
\n",
+ " כתבו generator בשם apply שמקבל כפרמטר ראשון פונקציה (func), וכפרמטר שני iterable (iterable).
\n",
+ " עבור כל איבר ב־iterable, ה־generator יניב את האיבר אחרי שהופעלה עליו הפונקציה func, דהיינו – func(item)
.
\n",
+ "
\n",
+ " ודאו שהרצת התא הבא מחזירה True
עבור הקוד שלכם:\n",
+ "
\n", + " וואו. זה היה די משוגע.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אז למעשה, פונקציות בפייתון הן ערך לכל דבר, כמו מחרוזות ומספרים!
\n",
+ " אפשר לאחסן אותן במשתנים, לשלוח אותן כארגומנטים ולכלול אותם בתוך מבני נתונים מורכבים יותר.
\n",
+ " אנשי התיאוריה של מדעי המחשב נתנו להתנהגות כזו שם: \"אזרח ממדרגה ראשונה\" (first class citizen).
\n",
+ " אם כך, אפשר להגיד על פונקציות בפייתון שהן אזרחיות ממדרגה ראשונה.\n",
+ "
\n",
+ " החדשות הטובות הן שכבר עשינו היכרות קלה עם המונח פונקציות מסדר גבוה.
\n",
+ " עכשיו, כשאנחנו יודעים שמדובר בפונקציות שמקבלות פונקציה כפרמטר, נתחיל ללכלך קצת את הידיים.
\n",
+ " נציג כמה פונקציות פייתוניות מעניינות שכאלו:\n",
+ "
\n",
+ " הפונקציה map מקבלת פונקציה כפרמטר הראשון, ו־iterable כפרמטר השני.
\n",
+ " map מפעילה את הפונקציה מהפרמטר הראשון על כל אחד מהאיברים שהועברו ב־iterable.
\n",
+ " היא מחזירה iterator שמורכב מהערכים שחזרו מהפעלת הפונקציה.
\n",
+ "
\n",
+ " במילים אחרות, map יוצרת iterable חדש.
\n",
+ " ה־iterable כולל את הערך שהוחזר מהפונקציה עבור כל איבר ב־iterable
שהועבר.\n",
+ "
\n", + " לדוגמה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "squared_items = map(square, [1, 6, -1, 8, 0, 3, -3, 9, -8, 8, -7])\n", + "print(tuple(squared_items))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " הפונקציה קיבלה כארגומנט ראשון את הפונקציה square שהגדרנו למעלה, שמטרתה העלאת מספר בריבוע.
\n",
+ " כארגומנט שני היא קיבלה את רשימת כל המספרים שאנחנו רוצים שהפונקציה תרוץ עליהם.
\n",
+ " כשהעברנו ל־map את הארגומנטים הללו, map החזירה לנו ב־iterator (מבנה שאפשר לעבור עליו איבר־איבר) את התוצאה:
\n",
+ " הריבוע, קרי החזקה השנייה, של כל אחד מהאיברים ברשימה שהועברה כארגומנט השני.\n",
+ "
\n",
+ " למעשה, אפשר להגיד ש־map
שקולה לפונקציה הבאה:\n",
+ "
\n", + " הנה דוגמה נוספת לשימוש ב־map:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "numbers = [(2, 4), (1, 4, 2), (1, 3, 5, 6, 2), (3, )]\n", + "sums = map(sum, numbers)\n", + "print(tuple(sums))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " במקרה הזה, בכל מעבר, קיבלה הפונקציה sum איבר אחד מתוך הרשימה – tuple.
\n",
+ " היא סכמה את האיברים של כל tuple שקיבלה, וכך החזירה לנו את הסכומים של כל ה־tuple־ים – זה אחרי זה.\n",
+ "
\n", + " ודוגמה אחרונה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def add_one(number):\n", + " return number + 1\n", + "\n", + "\n", + "incremented = map(add_one, (1, 2, 3))\n", + "print(tuple(incremented))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בדוגמה הזו יצרנו פונקציה משל עצמנו, ואותה העברנו ל־map.
\n",
+ " מטרת דוגמה זו היא להדגיש שאין שוני בין העברת פונקציה שקיימת בפייתון לבין פונקציה שאנחנו יצרנו.\n",
+ "
\n",
+ " כתבו פונקציה שמקבלת רשימת מחרוזות של שתי מילים: שם פרטי ושם משפחה.
\n",
+ " הפונקציה תשתמש ב־map כדי להחזיר מכולן רק את השם הפרטי.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " הפונקציה filter מקבלת פונקציה כפרמטר ראשון, ו־iterable כפרמטר שני.
\n",
+ " filter מפעילה על כל אחד מאיברי ה־iterable את הפונקציה, ומחזירה את האיבר אך ורק אם הערך שחזר מהפונקציה שקול ל־True
.
\n",
+ " אם ערך ההחזרה שקול ל־False
– הערך \"יבלע\" ב־filter ולא יחזור ממנה.\n",
+ "
\n",
+ " במילים אחרות, filter יוצרת iterable חדש ומחזירה אותו.
\n",
+ " ה־iterable כולל רק את האיברים שעבורם הפונקציה שהועברה החזירה ערך השקול ל־True
.\n",
+ "
\n",
+ " נבנה, לדוגמה, פונקציה שמחזירה אם אדם הוא בגיר.
\n",
+ " הפונקציה תקבל כפרמטר גיל, ותחזיר True
כאשר הגיל שהועבר לה הוא לפחות 18, ו־False
אחרת.\n",
+ "
\n", + " נגדיר רשימת גילים, ונבקש מ־filter לסנן אותם לפי הפונקציה שהגדרנו:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ages = [0, 1, 4, 10, 20, 35, 56, 84, 120]\n", + "mature_ages = filter(is_mature, ages)\n", + "print(tuple(mature_ages))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " כפי שלמדנו, filter מחזירה לנו רק גילים השווים ל־18 או גדולים ממנו.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " נחדד שהפונקציה שאנחנו מעבירים ל־filter לא חייבת להחזיר בהכרח True
או False
.
\n",
+ " הערך 0, לדוגמה, שקול ל־False
, ולכן filter תסנן כל ערך שעבורו הפונקציה תחזיר 0: \n",
+ "
\n",
+ " בתא האחרון העברנו ל־filter את sum כפונקציה שאותה אנחנו רוצים להפעיל, ואת to_sum כאיברים שעליהם אנחנו רוצים לפעול.
\n",
+ " ה־tuple־ים שסכום איבריהם היה 0 סוננו, וקיבלנו חזרה iterator שהאיברים בו הם אך ורק אלו שסכומם שונה מ־0.\n",
+ "
\n",
+ " כטריק אחרון, נלמד ש־filter יכולה לקבל גם None
בתור הפרמטר הראשון, במקום פונקציה.
\n",
+ " זה יגרום ל־filter לא להפעיל פונקציה על האיברים שהועברו, כלומר לסנן אותם כמו שהם.
\n",
+ " איברים השקולים ל־True
יוחזרו, ואיברים השקולים ל־False
לא יוחזרו:\n",
+ "
\n",
+ " כתבו פונקציה שמקבלת רשימת מחרוזות, ומחזירה רק את המחרוזות הפלינדרומיות שבה.
\n",
+ " מחרוזת נחשבת פלינדרום אם קריאתה מימין לשמאל ומשמאל לימין יוצרת אותו ביטוי.
\n",
+ " השתמשו ב־filter.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " תעלול נוסף שנוסיף לארגז הכלים שלנו הוא פונקציות אנונימיות (anonymous functions).
\n",
+ " אל תיבהלו מהשם המאיים – בסך הכול פירושו הוא \"פונקציות שאין להן שם\".
\n",
+ "
\n",
+ " לפני שאתם מרימים גבה ושואלים את עצמכם למה הן שימושיות, בואו נבחן כמה דוגמאות.
\n",
+ " ניזכר בהגדרת פונקציית החיבור שיצרנו לא מזמן:\n",
+ "
\n", + " ונגדיר את אותה הפונקציה בדיוק בצורה אנונימית:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "add = lambda num1, num2: num1 + num2\n", + "\n", + "print(add(5, 2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " לפני שנסביר איפה החלק של ה\"פונקציה בלי שם\" נתמקד בצד ימין של ההשמה.
\n",
+ " כיצד מנוסחת הגדרת פונקציה אנונימית?\n",
+ "
lambda
.lambda
\n",
+ " במה שונה ההגדרה של פונקציה זו מההגדרה של פונקציה רגילה?
\n",
+ " היא לא באמת שונה.
\n",
+ " המטרה היא לאפשר תחביר שיקל על חיינו כשאנחנו רוצים לכתוב פונקציה קצרצרה שאורכה שורה אחת.\n",
+ "
\n", + " נראה, לדוגמה, שימוש ב־filter כדי לסנן את כל האיברים שאינם חיוביים:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def is_positive(number):\n", + " return number > 0\n", + "\n", + "\n", + "numbers = [-2, -1, 0, 1, 2]\n", + "positive_numbers = filter(is_positive, numbers)\n", + "print(tuple(positive_numbers))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " במקום להגדיר פונקציה חדשה שנקראת is_positive, נוכל להשתמש בפונקציה אנונימית:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "numbers = [-2, -1, 0, 1, 2]\n", + "positive_numbers = filter(lambda n: n > 0, numbers)\n", + "print(tuple(positive_numbers))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " איך זה עובד?
\n",
+ " במקום להעביר ל־filter פונקציה שיצרנו מבעוד מועד, השתמשנו ב־lambda
כדי ליצור פונקציה ממש באותה השורה.
\n",
+ " הפונקציה שהגדרנו מקבלת מספר (n), ומחזירה True
אם הוא חיובי, או False
אחרת.
\n",
+ " שימו לב שבצורה זו באמת לא היינו צריכים לתת שם לפונקציה שהגדרנו.\n",
+ "
\n",
+ " השימוש בפונקציות אנונימיות לא מוגבל ל־map ול־filter, כמובן.
\n",
+ " מקובל להשתמש ב־lambda
גם עבור פונקציות כמו sorted, שמקבלות פונקציה בתור ארגומנט.\n",
+ "
\n",
+ " הפונקציה sorted
מאפשרת לנו לסדר ערכים, ואפילו להגדיר עבורה לפי מה לסדר אותם.
\n",
+ " לרענון בנוגע לשימוש בפונקציה גשו למחברת בנושא פונקציות מובנות בשבוע 4.\n",
+ "
\n", + " נסדר, למשל, את הדמויות ברשימה הבאה, לפי תאריך הולדתן:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "closet = [\n", + " {'name': 'Peter', 'year_of_birth': 1927, 'gender': 'Male'},\n", + " {'name': 'Edmund', 'year_of_birth': 1930, 'gender': 'Male'},\n", + " {'name': 'Lucy', 'year_of_birth': 1932, 'gender': 'Female'},\n", + " {'name': 'Susan', 'year_of_birth': 1928, 'gender': 'Female'},\n", + " {'name': 'Jadis', 'year_of_birth': 0, 'gender': 'Female'},\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " נרצה שסידור הרשימה יתבצע לפי המפתח year_of_birth.
\n",
+ " כלומר, בהינתן מילון שמייצג דמות בשם d, יש להשיג את d['year_of_birth']
, ולפיו לבצע את סידור הרשימה.
\n",
+ " ניגש למלאכה:\n",
+ "
\n",
+ " פונקציות אנונימיות הן יכולת חביבה שאמורה לסייע לכם לכתוב קוד נאה וקריא.
\n",
+ " כלל אצבע טוב לחיים הוא להימנע משימוש בהן כאשר הן מסרבלות את הקוד.\n",
+ "
\n", + " סדרו את הדמויות ב־closet לפי האות האחרונה בשמם. \n", + "
\n", + "lambda
\n",
+ " כתבו פונקציה בשם my_filter שמתנהגת בדיוק כמו הפונקציה filter.
\n",
+ " בפתירת התרגיל, המנעו משימוש ב־filter או במודולים.\n",
+ "
\n",
+ "כתבו פונקציה בשם get_positive_numbers שמקבלת מהמשתמש קלט בעזרת input.
\n",
+ "המשתמש יתבקש להזין סדרה של מספרים המופרדים בפסיק זה מזה.
\n",
+ "הפונקציה תחזיר את כל המספרים החיוביים שהמשתמש הזין, כרשימה של מספרים מסוג int
.
\n",
+ "אפשר להניח שהקלט מהמשתמש תקין.\n",
+ "
\n",
+ " כתבו פונקציה בשם timer שמקבלת כפרמטר פונקציה (נקרא לה f) ופרמטרים נוספים.
\n",
+ " הפונקציה timer תמדוד כמה זמן רצה פונקציה f כשמועברים אליה אותם פרמטרים.
\n",
+ "
\n", + " לדוגמה:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "timer(print, \"Hello\")
, תחזיר הפונקציה את משך זמן הביצוע של print(\"Hello\")
.timer(zip, [1, 2, 3], [4, 5, 6])
, תחזיר הפונקציה את משך זמן הביצוע של zip([1, 2, 3], [4, 5, 6])
.timer(\"Hi {name}\".format, name=\"Bug\")
, תחזיר הפונקציה את משך זמן הביצוע של \"Hi {name}\".format(name=\"Bug\")
\n",
+ " מפתחי פייתון אוהבים מאוד קוד קצר ופשוט שמנוסח היטב.
\n",
+ " יוצרי השפה מתמקדים פעמים רבות בלאפשר למפתחים בה לכתוב קוד בהיר ותמציתי במהירות.
\n",
+ " במחברת זו נלמד איך לעבור על iterable וליצור ממנו מבני נתונים מעניינים בקלות ובמהירות.\n",
+ "
\n",
+ " נתחיל במשימה פשוטה יחסית:
\n",
+ " בהינתן רשימת שמות, אני מעוניין להפוך את כל השמות ברשימה ליווניים.
\n",
+ " כידוע, אפשר להפוך כל שם ליווני על ידי הוספת ההברה os בסופו. לדוגמה, השם Yam ביוונית הוא Yamos.\n",
+ "
\n", + " למה אנחנו מחכים? ניצור את הרשימה החדשה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "new_names = []" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " נעבור על הרשימה הישנה בעזרת לולאת for
, נשרשר לכל איבר \"os\" ונצרף את התוצאה לרשימה החדשה:\n",
+ "
\n", + " כשהלולאה תסיים לרוץ, תהיה בידינו רשימה חדשה של שמות יוונים:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(new_names)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " אם נסתכל על הלולאה שיצרנו, נוכל לזהות בה ארבעה מרכיבים עיקריים.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "שם המרכיב | \n", + "תיאור המרכיב | \n", + "דוגמה | \n", + "
---|---|---|
ה־iterable הישן | \n", + "אוסף הנתונים המקורי שעליו אנחנו רצים. | \n", + "names | \n", + "
הערך הישן | \n", + "משתנה הלולאה. הלייזר שמצביע בכל פעם על ערך יחיד מתוך ה־iterable הישן. | \n", + "name | \n", + "
הערך החדש | \n", + "הערך שנרצה להכניס ל־iterable שאנחנו יוצרים, בדרך כלל מושפע מהערך הישן. | \n", + "name + 'os' | \n",
+ "
ה־iterable החדש | \n", + "ה־iterable שאנחנו רוצים ליצור, הערך שיתקבל בסוף הריצה. | \n", + "new_names | \n", + "
\n",
+ " השתמשו ב־map כדי ליצור מ־names רשימת שמות יווניים באותה הצורה.
\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " צורת ה־map בפתרון שלכם הייתה אמורה להשתמש בדיוק באותם חלקי הלולאה.
\n",
+ " אם עדיין לא ניסיתם לפתור בעצמכם, זה הזמן לכך.
\n",
+ " התשובה שלכם אמורה להיראות בערך כך:\n",
+ "
\n",
+ " list comprehension היא טכניקה שמטרתה לפשט את מלאכת הרכבת הרשימה, כך שתהיה קצרה, מהירה וקריאה.
\n",
+ " ניגש לעניינים! אבל ראו הוזהרתם – במבט ראשון list comprehension עשוי להיראות מעט מאיים וקשה להבנה.
\n",
+ " הנה זה בא:\n",
+ "
\n",
+ " הדבר הראשון שמבלבל כשנפגשים לראשונה עם list comprehension הוא סדר הקריאה המשונה:
\n",
+ "
for
– נוכל לראות את הביטוי for name in names
שאנחנו כבר מכירים.for
, נכתוב את ערכו של האיבר שאנחנו רוצים לצרף לרשימה החדשה בכל איטרציה של הלולאה.\n",
+ " נביט בהשוואת החלקים של ה־list comprehension לחלקים של לולאת ה־for
:\n",
+ "
for
ובעזרת list comprehension\n",
+ " list comprehension מאפשרת לשנות את הערך שנוסף לרשימה בקלות.
\n",
+ " מסיבה זו, מתכנתים רבים יעדיפו את הטכניקה הזו על פני שימוש ב־map, שבה נצטרך להשתמש ב־lambda
ברוב המקרים.\n",
+ "
\n",
+ " נתונה הרשימה numbers = [1, 2, 3, 4, 5]
.
\n",
+ " השתמשו ב־list comprehension כדי ליצור בעזרתה את הרשימה [1, 4, 9, 16, 25]
.
\n",
+ " האם אפשר להשתמש בפונקציה range במקום ב־numbers?\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " list comprehension הוא מבנה גמיש מאוד!
\n",
+ " נוכל לכתוב בערך שאנחנו מצרפים לרשימה כל ביטוי שיתחשק לנו, ואפילו לקרוא לפונקציות.
\n",
+ " נראה כמה דוגמאות:\n",
+ "
\n",
+ " השתמשו ב־list comprehension כדי ליצור את הרשימה הבאה:
\n",
+ " [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]
. \n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " נציג תבנית נפוצה נוספת הנוגעת לעבודה עם רשימות.
\n",
+ " לעיתים קרובות, נרצה להוסיף איבר לרשימה רק אם מתקיים לגביו תנאי מסוים.
\n",
+ " לדוגמה, ניקח מרשימת השמות הבאה רק את האנשים ששמם ארוך מתריסר תווים:\n",
+ "
\n",
+ " השתמשו ב־filter כדי ליצור מ־names רשימת שמות ארוכים באותה הצורה.
\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n", + " נפרק את הקוד הקצר שיצרנו למעלה למרכיביו:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "שם המרכיב | \n", + "תיאור המרכיב | \n", + "דוגמה | \n", + "
---|---|---|
איפוס | \n", + "אתחול הרשימה לערך ריק. | \n", + "long_names = [] | \n",
+ "
הלולאה | \n", + "החלק שעובר על כל האיברים ב־iterable הקיים ויוצר משתנה שאליו אפשר להתייחס. | \n", + "for name in names: | \n",
+ "
הבדיקה | \n", + "התניה שבודקת אם הערך עונה על תנאי מסוים. | \n", + "if len(name) > 12: | \n",
+ "
הוספה | \n", + "צירוף האיבר לרשימה החדשה, אם הוא עונה על התנאי שנקבע בבדיקה. | \n", + "long_names.append(name) | \n",
+ "
\n", + " ונלמד איך מממשים את אותו הרעיון בדיוק בעזרת list comprehension:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "names = ['Margaret Thatcher', 'Karl Marx', \"Ze'ev Jabotinsky\", 'Bertrand Russell', 'Fidel Castro']\n", + "long_names = [name for name in names if len(name) > 12]\n", + "print(long_names)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " נראה שוב השוואה בין list comprehension ללולאת for
רגילה, הפעם עם תנאי:\n",
+ "
for
ובעזרת list comprehension\n",
+ " גם כאן יש לנו סדר קריאה משונה מעט, אך הרעיון הכללי של ה־list comprehension נשמר:
\n",
+ "
for
– נוכל לראות את הביטוי for name in names
שאנחנו כבר מכירים.for
, נכתוב את ערכו של האיבר שאנחנו רוצים לצרף לרשימה בכל איטרציה של הלולאה.\n",
+ " אפשר לשלב את השיטות כדי לבנות בקלילות רשימות מורכבות.
\n",
+ " נמצא את שמות כל הקבצים שהסיומת שלהם היא \".html\":\n",
+ "
\n",
+ " בחנות של סנדל'ה הארון קצת מבולגן.
\n",
+ " כשלקוח נכנס ומבקש מסנדל'ה למדוד מידה מסוימת, היא צריכה לפשפש בין אלפי המוצרים בארון, ולפעמים המידות שהיא מוצאת שם מוזרות מאוד.
\n",
+ " ההנחיות שסנדל'ה נתנה לנו לצורך סידור הארון שלה די פשוטות:
\n",
+ " התעלמו מכל מידה שיש בה תו שאינו ספרה או נקודה, והוציאו שורש רק מהמידות המספריות.
\n",
+ " התעלמו גם ממספרים עם יותר מנקודה אחת.\n",
+ "
\n",
+ " לדוגמה, עבור הארון ['100', '25.0', '12a', 'mEoW', '0']
, החזירו [10.0, 5.0, 0.0].
\n",
+ " עבור הארון ['Area51', '303', '2038', 'f00b4r', '314.1']
, החזירו [17.4, 45.14, 17.72].
\n",
+ " (מחקנו קצת ספרות אחרי הנקודה בשביל הנראות).\n",
+ "
\n",
+ " כתבו פונקציה בשם organize_closet שמקבלת רשימת ארון ומסדרת אותו.
\n",
+ " תוכלו לבדוק את עצמכם באמצעות הפונקציה generate_closet שתיצור עבורכם ארון אסלי מהחנות של סנדל'ה.\n",
+ "
\n",
+ " בפייתון, נהוג לכנות משתנה שלא יהיה בו שימוש בעתיד כך: _
.
\n",
+ " דוגמה טובה אפשר לראות בלולאה שב־generate_closet
.\n",
+ "
\n",
+ " מלבד list comprehension, קיימים גם set comprehension ו־dictionary comprehension שפועלים בצורה דומה.
\n",
+ " הרעיון בבסיסו נשאר זהה – שימוש בערכי iterable כלשהו לצורך יצירת מבנה נתונים חדש בצורה קריאה ומהירה.
\n",
+ " נראה דוגמה ל־dictionary comprehension שבו המפתח הוא מספר, והערך הוא אותו המספר בריבוע:\n",
+ "
\n",
+ " בדוגמה למעלה חישבנו את הריבוע של כל אחד מעשרת המספרים החיוביים הראשונים.
\n",
+ " משתנה הלולאה i עבר על כל אחד מהמספרים בטווח שבין 1 ל־11 (לא כולל), ויצר עבור כל אחד מהם את המפתח i, ואת הערך i ** 2
.
\n",
+ "
\n",
+ " ראו כיצד בעזרת התחביר העוצמתי הזה בפייתון, אנחנו יכולים ליצור מילונים מורכבים בקלות רבה.
\n",
+ " כל שעלינו לעשות הוא להשתמש בסוגריים מסולסלים במקום במרובעים,
\n",
+ " ולציין מייד אחרי פתיחת הסוגריים את הצמד שנרצה להוסיף בכל איטרציה – מפתח וערך, כשביניהם נקודתיים.\n",
+ "
\n", + " בצורה דומה אפשר ליצור set comprehension:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sentence = \"99 percent of all statistics only tell 49 percent of the story.\"\n", + "words = {word for word in sentence.lower().split() if word.isalpha()}\n", + "print(words)\n", + "print(type(words))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " התחביר של set comprehension כמעט זהה לתחביר של list comprehension.
\n",
+ " ההבדל היחיד ביניהם הוא שב־set comprehension אנחנו משתמשים בסוגריים מסולסלים.
\n",
+ " ההבדל בינו לבין dictionary comprehension הוא שאנחנו משמיטים את הנקודתיים והערך, ומשאירים רק את המפתח.\n",
+ "
\n",
+ " מצאו כמה מהמספרים הנמוכים מ־1,000 מתחלקים ב־3 וב־7 ללא שארית.
\n",
+ " השתמשו ב־set comprehension.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " בשבוע שעבר למדנו על הכוח הטמון ב־generators.
\n",
+ " בזכות שמירת ערך אחד בלבד בכל פעם, generators מאפשרים לנו לכתוב תוכניות יעילות מבחינת צריכת הזיכרון.\n",
+ "
\n", + " נכתוב generator פשוט שמניב עבורנו את אורכי השורות בטקסט מסוים:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_line_lengths(text):\n", + " for line in text.splitlines():\n", + " if line.strip(): # אם השורה אינה ריקה\n", + " yield len(line)\n", + "\n", + "\n", + "# לדוגמה\n", + "with open('resources/states.txt') as states_file:\n", + " states = states_file.read()\n", + "print(list(get_line_lengths(states)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " חדי העין כבר זיהו את התבנית המוכרת – יש פה for
, מייד אחריו if
ומייד אחריו אנחנו יוצרים איבר חדש.
\n",
+ " אם כך, generator expression הוא בסך הכול שם מפונפן למה שאנחנו היינו קוראים לו generator comprehension.
\n",
+ " נמיר את הפונקציה get_line_lengths ל־generator comprehension:\n",
+ "
\n", + " נעמוד על ההבדלים בין הגישות:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כאמור, הרעיון דומה מאוד ל־list comprehension.
\n",
+ " האיבר שנחזיר בכל פעם מה־generator בעזרת yield
יהפוך ב־generator expression להיות האיבר שנמצא לפני המילה for
.\n",
+ "
\n",
+ " שימו לב שה־generator expression שקול לערך המוחזר לנו בקריאה לפונקציית ה־generator.
\n",
+ " זו נקודה שחשוב לשים עליה דגש: generator expression מחזיר generator iterator, ולא פונקציית generator.\n",
+ "
\n", + " נסתכל על דוגמה נוספת ל־generator expression שמחזיר את ריבועי כל המספרים מ־1 ועד 11 (לא כולל):\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "squares = (number ** 2 for number in range(1, 11))\n", + "print(list(squares))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " בדיוק כמו ב־generator iterator רגיל, אחרי שנשתמש באיבר לא נוכל לקבל אותו שוב:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(list(squares))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " והפעלת next על generator iterator שכבר הניב את כל הערכים תקפיץ StopIterator: \n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "next(squares)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " ולטריק האחרון בנושא זה –
\n",
+ " טוב לדעת שכשמעבירים לפונקציה generator expression כפרמטר יחיד, לא צריך לעטוף אותו בסוגריים נוספים.
\n",
+ " לדוגמה:\n",
+ "
\n",
+ " בדוגמה שלמעלה ה־generator comprehension יצר את כל ריבועי המספרים מ־1 ועד 11, לא כולל.
\n",
+ " הפונקציה sum השתמשה בכל ריבועי המספרים שה־generator הניב, וסכמה אותם.\n",
+ "
\n",
+ " לפעמים נרצה לכתוב כמה לולאות מקוננות זו בתוך זו.
\n",
+ " לדוגמה, ליצירת כל האפשרויות שיכולות להתקבל בהטלת 2 קוביות:\n",
+ "
\n", + " נוכל להפוך גם את המבנה הזה ל־list comprehension:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dice_options = [(die1, die2) for die1 in range(1, 7) for die2 in range(1, 7)]\n", + "print(dice_options)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כדי להבין איך זה עובד, חשוב לזכור איך קוראים list comprehension:
\n",
+ " פשוט התחילו לקרוא מה־for
הראשון, וחזרו לאיבר שאנחנו מוסיפים לרשימה בכל פעם רק בסוף.\n",
+ "
\n", + " אם במשחק מוזר כלשהו נצטרך לזרוק 3 קוביות, לדוגמה, ונרצה לראות אילו אופציות יכולות להתקבל, נוכל לכתוב זאת כך:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dice_options = [\n", + " (die1, die2, die3)\n", + " for die1 in range(1, 7)\n", + " for die2 in range(1, 7)\n", + " for die3 in range(1, 7)\n", + "]\n", + "print(dice_options)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " שבירת השורה בתא שלמעלה נעשתה מטעמי סגנון.
\n",
+ " באופן טכני, מותר לרשום את ה־list comprehension הזה בשורה אחת.\n",
+ "
\n",
+ " צרו פונקציית generator ו־generator expression מהדוגמה האחרונה.
\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " החסרון בדוגמה של קוביות הוא שאנחנו מקבלים בתוצאות גם את (1, 1, 6)
וגם את (6, 1, 1)
.
\n",
+ " האם תוכלו לפתור בעיה זו בקלות?\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " הטכניקות שלמדנו במחברת זו מפקידות בידינו כוח רב, אך כמו שאומר הדוד בן, \"עם כוח גדול באה אחריות גדולה\".
\n",
+ " עלינו לזכור תמיד שהמטרה של הטכניקות הללו בסופו של דבר היא להפוך את הקוד לקריא יותר.\n",
+ "
\n",
+ " לעיתים קרובות מתכנתים לא מנוסים ישתמשו בטכניקות שנלמדו במחברת זו כדי לבנות מבנים מורכבים מאוד.
\n",
+ " התוצאה תהיה קוד שקשה לתחזק ולקרוא, ולעיתים קרובות הקוד יוחלף לבסוף בלולאות רגילות.
\n",
+ " כלל האצבע הוא שבשורה לא יהיו יותר מ־99 תווים, ושהקוד יהיה פשוט ונוח לקריאה בידי מתכנת חיצוני.\n",
+ "
\n",
+ " קהילת פייתון דשה בנושאי קריאות קוד לעיתים קרובות, תוך כדי התייחסויות תכופות ל־PEP8.
\n",
+ " נסביר בקצרה – PEP8 הוא מסמך שמתקנן את הקווים הכלליים של סגנון הכתיבה הרצוי בפייתון.
\n",
+ " לדוגמה, מאגרי קוד העוקבים אחרי המסמך בצורה מחמירה לא מתירים כתיבת שורות קוד שבהן יותר מ־79 תווים.
\n",
+ " כתיבה מסוגננת היטב היא נושא רחב יריעה שנעמיק בו בהמשך הקורס.\n",
+ "
\n", + " במחברת זו למדנו 4 טכניקות שימושיות שעוזרות לנו ליצור בצורה קריאה ומהירה מבני נתונים:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " למדנו מעט איך להשתמש בהם ומתי, ועל ההקבלות שלהם ללולאות רגילות ולפונקציות כמו map ו־filter.
\n",
+ " למדנו גם איך אפשר להשתמש בכל אחת מהן במצב שבו יש לנו כמה לולאות מקוננות.\n",
+ "
\n", + "מתכנתי פייתון עושים שימוש רב בטכניקות האלו, וחשוב לשלוט בהן היטב כדי לדעת לקרוא קוד וכדי להצליח לממש רעיונות במהירות.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## תרגילים" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### הֲיִי שלום" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כתבו פונקציה בשם words_length שמקבלת משפט ומחזירה את אורכי המילים שבו, לפי סדרן במשפט.
\n",
+ " לצורך התרגיל, הניחו שסימני הפיסוק הם חלק מאורכי המילים.
\n",
+ "
\n",
+ " לדוגמה:
\n",
+ " עבור המשפט: Toto, I've a feeling we're not in Kansas anymore
\n",
+ " החזירו את הרשימה: [5, 4, 1, 7, 5, 3, 2, 6, 7]\n",
+ "
\n",
+ " כתבו פונקציה בשם get_letters שמחזירה את רשימת כל התווים בין a ל־z ובין A ל־Z.
\n",
+ " השתמשו ב־list comprehension, ב־ord וב־chr.
\n",
+ " הקפידו שלא לכלול את המספרים 65, 90, 97 או 122 בקוד שלכם.\n",
+ "
\n",
+ " כתבו פונקציה בשם count_words שמקבלת כפרמטר טקסט, ומחזירה מילון של אורכי המילים שבו.
\n",
+ " השתמשו ב־comprehension לבחירתכם (או ב־generator expression) כדי לנקות את הטקסט מסימנים שאינם אותיות.
\n",
+ " לאחר מכן, השתמשו ב־dictionary comprehension כדי לגלות את אורכה של כל מילה במשפט.
\n",
+ "
\n", + " לדוגמה, עבור הטקסט הבא, בדקו שחוזר לכם המילון המופיע מייד אחריו.\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import string\n", + "\n", + "text = \"\"\"\n", + "You see, wire telegraph is a kind of a very, very long cat.\n", + "You pull his tail in New York and his head is meowing in Los Angeles.\n", + "Do you understand this?\n", + "And radio operates exactly the same way: you send signals here, they receive them there.\n", + "The only difference is that there is no cat.\n", + "\"\"\"\n", + "\n", + "expected_result = {'you': 3, 'see': 3, 'wire': 4, 'telegraph': 9, 'is': 2, 'a': 1, 'kind': 4, 'of': 2, 'very': 4, 'long': 4, 'cat': 3, 'pull': 4, 'his': 3, 'tail': 4, 'in': 2, 'new': 3, 'york': 4, 'and': 3, 'head': 4, 'meowing': 7, 'los': 3, 'angeles': 7, 'do': 2, 'understand': 10, 'this': 4, 'radio': 5, 'operates': 8, 'exactly': 7, 'the': 3, 'same': 4, 'way': 3, 'send': 4, 'signals': 7, 'here': 4, 'they': 4, 'receive': 7, 'them': 4, 'there': 5, 'only': 4, 'difference': 10, 'that': 4, 'no': 2}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ואלה שמות" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כתבו פונקציה בשם full_names, שתקבל כפרמטרים רשימת שמות פרטיים ורשימת שמות משפחה, ותרכיב מהם שמות מלאים.
\n",
+ " לכל שם פרטי תצמיד הפונקציה את כל שמות המשפחה שהתקבלו.
\n",
+ " ודאו שהשמות חוזרים כאשר האות הראשונה בשם הפרטי ובשם המשפחה היא אות גדולה.\n",
+ "
\n",
+ " על הפונקציה לקבל גם פרמטר אופציונלי בשם min_length.
\n",
+ " אם הפרמטר הועבר, שמות מלאים שכמות התווים שבהם קטנה מהאורך שהוגדר – לא יוחזרו מהפונקציה.\n",
+ "
\n", + " לדוגמה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "first_names = ['avi', 'moshe', 'yaakov']\n", + "last_names = ['cohen', 'levi', 'mizrahi']\n", + "\n", + "# התנאים הבאים צריכים להתקיים\n", + "full_names(first_names, last_names, 10) == ['Avi Mizrahi', 'Moshe Cohen', 'Moshe Levi', 'Moshe Mizrahi', 'Yaakov Cohen', 'Yaakov Levi', 'Yaakov Mizrahi']\n", + "full_names(first_names, last_names) == ['Avi Cohen', 'Avi Levi', 'Avi Mizrahi', 'Moshe Cohen', 'Moshe Levi', 'Moshe Mizrahi', 'Yaakov Cohen', 'Yaakov Levi', 'Yaakov Mizrahi']" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/content/week06/4_Modules_Part_2.ipynb b/content/week06/4_Modules_Part_2.ipynb new file mode 100644 index 0000000..04aedcb --- /dev/null +++ b/content/week06/4_Modules_Part_2.ipynb @@ -0,0 +1,615 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בשבוע שעבר עשינו את צעדינו הראשונים בעולם המודולים.
\n",
+ " למדנו את הרעיון העומד מאחוריהם, כיצד לייבא אותם, איפה למצוא עליהם מידע וכיצד להשתמש בהם.
\n",
+ " השתמשנו במודולים שמגיעים עם פייתון, כמו math, random או datetime, ופתרנו בעזרתם בעיות מורכבות.\n",
+ "
\n",
+ " ישנם יתרונות רבים לשימוש במודולים המובנים בפייתון:
\n",
+ " הם מקלים עלינו בפתרון בעיות מורכבות, מכסים מקרי קצה רבים ומתוחזקים היטב.
\n",
+ " מעבר לכך, השימוש בהם נפוץ מאוד בקהילת מפתחי הפייתון, ולכן התמיכה בהם ברשת רחבה.
\n",
+ " רשימת המודולים שמגיעים עם פייתון נמצאת באתר התיעוד הרשמי של פייתון, וכל מודול ברשימה מתועד לעילא ולעילא.
\n",
+ "
\n",
+ " אך ישנם גם חסרונות למודולים המובנים בפייתון:
\n",
+ " הם לא מתיימרים לפתור בעיות איזוטריות, כמו קריאת חדשות או פתירת קוביות הונגריות.
\n",
+ " יתרה מזאת, המודולים מתעדכנים לעיתים רחוקות, חלקם מפגרים אחר קצב החידושים בעולם,
\n",
+ " ועבור חלקם הקהילה מפתחת אלטרנטיבה טובה יותר.\n",
+ "
\n",
+ " בפרק זה נלמד על מודולים שנוצרו על ידי צד שלישי – מתכנתים כמוכם.
\n",
+ " לאורך המחברת הזו נבין איך מוצאים אותם, איך מתקינים אותם ואיך משתמשים בהם.\n",
+ "
\n",
+ " בדרך כלל המסע שלנו יתחיל כאשר ניתקל בבעיה שגורמת לנו לשבור את הראש –
\n",
+ " מאחר שהיא מורכבת מאוד או שיש לה מקרי קצה רבים מאוד.
\n",
+ " זו גם יכולה להיות בעיה שכנראה כבר פתרו בעבר ושייקח לנו זמן לממש בעצמנו.\n",
+ "
\n",
+ " נניח שאנחנו בונים פרויקט קטן משלנו – מילון אינטרנטי שמציג למשתמש את פירוש המילה שהוא מקליד.
\n",
+ " אנחנו רוצים שכשמחפשים מילה במילון שלנו – אחת התוצאות תהיה תקציר מוויקיפדיה.
\n",
+ " כרגע אין לנו די ידע כדי לממש שליפת מידע מוויקיפדיה, אבל אין סיבה שזה ירתיע אותנו!\n",
+ "
\n",
+ " המקום הראשון שנפנה אליו הוא מנוע החיפוש המועדף עליכם.
\n",
+ " נחפש מודול שמתעסק בוויקיפדיה: python module wikipedia
, וננסה לקבל מושג על מודולים שעשויים לעזור לנו:\n",
+ "
python module wikipedia
\n",
+ " הקישור הראשון שהתקבל בתוצאות החיפוש מוביל אותנו לאתר שנקרא PyPI, או בשמו המלא – Python Package Index.
\n",
+ " זהו האתר הרשמי של פייתון לאחסון חבילות (שאנחנו מכירים כ\"מודולים\") – מאגר גדול שבו חבילות מכל סוג וצורה.
\n",
+ " בבסיסו, PyPI הוא מקום ריכוזי שאליו משתמשים יכולים להעלות חבילות שיצרו, וממנו ציבור המתכנתים יכול להוריד חבילות ולהשתמש בהן.
\n",
+ " נכון לכתיבת מילים אלו, ל־PyPI הועלו יותר מ־235,000 חבילות.
\n",
+ "
\n",
+ " ישנו הבדל בין משמעות המונח \"חבילה\" (Package) לבין משמעות המונח \"מודול\" (Module).
\n",
+ " נסביר את ההבדל בין המילים באחד השבועות הבאים. עד אז – נתייחס אליהן כאל מילים נרדפות.\n",
+ "
\n", + " נכנס לקישור שהופיע בתוצאות החיפוש, ונביט על דף המודול wikipedia באתר PyPI:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " במרכז האתר נוכל לראות פירוט קצר על החבילה, ולעיתים יופיעו הוראות התקנה ודוגמאות קוד המדריכות כיצד לעבוד איתה.
\n",
+ " מדובר במידע שימושי שכדאי לעיין בו –
\n",
+ " פעמים רבות נמצא בו קישורים רלוונטיים, או דוגמאות שנותנות מושג על נוחות השימוש בחבילה לצורכנו.\n",
+ "
\n",
+ " בצד ימין למעלה נוכל למצוא את תאריך העדכון האחרון של החבילה – במקרה שלנו, לפני 6 שנים, ב־2014.
\n",
+ " מדובר בתאריך ישן וברור שהחבילה אינה מתוחזקת.
\n",
+ " נעדיף שלא לבחור חבילות כאלו לצורכי פרויקטים גדולים שאנחנו מתכוונים לתחזק לאורך זמן.
\n",
+ " לרוב נעדיף חבילות עם סביבת תמיכה כמה שיותר מבוססת, וכאלו שעבורן משוחררים תיקונים ועדכונים שיוצאים תדיר.\n",
+ "
\n",
+ " בצד שמאל נוכל לראות נתונים שמעידים, בין היתר, על הפופולריות של הפרויקט.
\n",
+ " מדד הכוכבים הוא למעשה כמות ה\"לייקים\" שהפרויקט קיבל בפלטפורמה לניהול קוד בשם GitHub.
\n",
+ " לרוב נעדיף לבחור בספריות שצברו כמה מאות כוכבים. לזו יש כמעט 2,000, מספר לא רע בכלל.\n",
+ "
\n",
+ " המודול הזה נראה טוב דיו לצורכנו, והחלטנו להשתמש בו.
\n",
+ " אם ננסה לייבא אותו, נקבל שגיאה:\n",
+ "
\n",
+ " זה מובן. פייתון מן הסתם לא יכולה להגיע עם כל ה־235,000 מודולים שפרסמה הקהילה.
\n",
+ " אם כך, נצטרך לבחור אילו מודולים אנחנו רוצים להתקין, ולהתקין אותם לפני שנוכל להשתמש בהם.\n",
+ "
\n",
+ " תהליך ההתקנה הוא לרוב פשוט ולא דורש מאמץ רב.
\n",
+ " פייתון מגיעה עם כלי שנקרא pip, שמשמש להתקנה של חבילות חיצוניות ולניהולן.
\n",
+ " כדי להשתמש בו, פתחו את שורת הפקודה במחשב שלכם.\n",
+ "
\n",
+ " כדי לפתוח את שורת הפקודה במערכת ההפעלה חלונות, לחצו על ⊞ WinKey+R במקלדת.
\n",
+ " הלחצן ⊞ WinKey מופיע בין כפתור ה־ALT לכפתור ה־CTRL בצד השמאלי התחתון של המקלדת.
\n",
+ " בחלון הקטן שייפתח לכם, הקישו cmd
ולחצו ⏎ Enter.
\n",
+ " בשלב זה אמור להופיע לכם על המסך חלון שחור. זוהי שורת הפקודה.\n",
+ "
\n",
+ " כדי לפתוח את שורת הפקודה במערכת Mac, לחצו על cmd ⌘+Space במקלדת.
\n",
+ " בחלון שייפתח לכם, הקישו Terminal
. התוכנה Terminal אמורה להופיע לכם כעת תחת המילים Top Hit.
\n",
+ " לחצו לחיצה כפולה על Terminal.
\n",
+ " בשלב זה אמור להופיע לכם על המסך חלון לבן. זוהי שורת הפקודה.\n",
+ "
\n",
+ " עכשיו, כששורת הפקודה פתוחה, נוכל להתקין את החבילה wikipedia.
\n",
+ " נכתוב בשורת הפקודה: pip install wikipedia, ונראה את pip מתקין עבורנו את החבילה.
\n",
+ " פקודת ההתקנה מופיעה בראש הדף של החבילה באתר PyPI, ולרוב גם בתיעוד החבילה עצמו.\n",
+ "
\n",
+ " אפשר להריץ פקודה בשורת הפקודה מהמחברת. פתחו תא קוד חדש, והדביקו את הפקודה כשלפניה סימן קריאה.
\n",
+ " לדוגמה, כדי להתקין את החבילה, השתמשו ב־!pip install wikipedia
\n",
+ "
\n",
+ " זה הזמן להתחיל לעבוד עם החבילה.
\n",
+ " בשלב הזה כדאי לפתוח את תיעוד החבילה ולנסות להריץ משם דברים כדי לקבל תחושה כללית בנוגע לטיבה.\n",
+ "
\n",
+ " חיפוש זריז בגוגל של python wikipedia module documentation
יחזיר לנו את התוצאה הרביעית מהחיפוש הקודם שלנו בגוגל.
\n",
+ " ניכנס ונריץ את שורות הקוד לדוגמה המופיעות בתיעוד:
\n",
+ "
\n",
+ " יופי! כבר עכשיו אפשר להגיד שהצלחנו במשימה שלנו לאחזר את פסקת הסיכום מוויקיפדיה לפי ערך מילוני.
\n",
+ " לאן ממשיכים מכאן?\n",
+ "
\n",
+ " נשוטט קצת בתיעוד של החבילה כדי להבין מה עוד היא יכולה לעשות.
\n",
+ " נראה, לדוגמה, שאפשר לחפש ערכים גם בעברית!\n",
+ "
\n",
+ " מצאו אילו נקודות עניין יש ברדיוס של עד 600 מטר מהנקודה שהקואורדינטות שלה הן: קו רוחב 29.979167 וקו אורך 31.134167.
\n",
+ " השתמשו בחבילה wikipedia ובתיעוד שלה.\n",
+ "
\n",
+ " אם אתם אמיצים במיוחד ומרגישים ברי־מזל, תוכלו לכתוב את שם החבילה, נקודה, ואז ללחוץ על Tab ↹ במקלדת.
\n",
+ " התוצאה תהיה רשימת הפעולות הקיימות בחבילה, מסודרת לפי סדר מילוני.\n",
+ "
\n",
+ " קל מאוד ללכת לאיבוד בצורה הזו.
\n",
+ " פעמים רבות תראו לצד הפעולות גם משתנים פנימיים שבהם החבילה עושה שימוש,
\n",
+ " והסידור האלפבתי לא מקל על מציאת פונקציות שבהן אנחנו רוצים להשתמש.
\n",
+ " כשמתאפשר, נעדיף להשתמש בתיעוד שכתב המפתח.\n",
+ "
\n",
+ " pip מאפשרת לנו לנהל את כל חבילות הפייתון שמותקנות על המחשב שלנו.
\n",
+ " נסקור כמה פקודות שכדאי להכיר:
\n",
+ "
pip install
pip uninstall
pip show
pip search
pip list
\n",
+ " במחברת זו למדנו כיצד מוצאים, מתקינים ומשתמשים במודול שכתבו מפתחי פייתון אחרים.
\n",
+ " היכולת למצוא קטעי קוד שכתבו מתכנתים אחרים ולהשתמש בהם, היא אחד הכלים החשובים ביותר עבור כל מתכנת.\n",
+ "
\n",
+ " ככל שתשתמשו יותר בחבילות חיצוניות, כך יתפתחו יכולותיכם הקשורות במציאה של מודולים חדשים ובתפעולם.
\n",
+ " הקפידו לזכור שלרוב קיים פתרון לכל בעיה שנתקלתם בה, והרבו להשתמש בחבילות חיצוניות כדי לפתור בעיות.\n",
+ "
\n",
+ "מצאו במחשבכם חבילה אחת ששמה כשם של חיה, חבילה אחת ששמה כשם של מספר וחבילה אחת ששמה כשם של יצור מתולוגי.
\n",
+ "בחרו באחת מהן והסבירו במשפט אחד מה היא עושה.\n",
+ "
\n", + " שימו לב: אפשר להתעלם מהערות הבודק האוטומטי.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### זהות בדויה" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כתבו פונקציה בשם create_people שמקבלת מספר ($n$) ומחזירה $n$ פרטי אנשים מזויפים.
\n",
+ " \"איש\" מוגדר כצירוף הפרטים של שם מלא, כתובת, כתובת דואר אלקטרוני ומספר טלפון.
\n",
+ " בחרו בעצמכם את מבני הנתונים שבהם כדאי לעשות שימוש בתרגיל זה.\n",
+ "
\n",
+ " הצילו! טרוריסט משוגע מנסה לבצע פיגוע, ויכולות הפייתון שלכם הן היחידות שיכולות לעזור!
\n",
+ " מאמצי מודיעין נרחבים הביאו אותנו למסקנה שהטרוריסט מצפין את מסריו בתמונות כך:\n",
+ "
\n",
+ " בכל עמודה בתמונה ישנו פיקסל אחד שצבוע בשחור.
\n",
+ " הפיקסל צבוע במספר השורה שתואם את הערך המספרי של התו (ראו תיעוד על הפונקציות ord, chr).
\n",
+ " אם ממירים את המיקום שבו נמצאים הפיקסלים השחורים לפי סדר, משמאל לימין, מקבלים את המסר המוכמן.\n",
+ "
\n", + " לדוגמה, אם בתמונה בגודל (2, 255) הפיקסל השמאלי נמצא בשורה מספר 72 והפיקסל הימני נמצא בשורה מספר 105, המסר המוכמן הוא \"Hi\".\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כתבו פונקציה שמקבלת נתיב לקובץ המוצפן ומחזירה את המסר המוצפן בו.
\n",
+ " השתמשו בקובץ resources/code.png כדי לפענח את המסר של הטרוריסט!\n",
+ "
\n",
+ " כתבו פונקציה בשם group_by שמקבלת פונקציה כפרמטר ראשון, ו־iterable כפרמטר שני.
\n",
+ " הפונקציה תחזיר מילון, שבו:\n",
+ "
\n",
+ "לדוגמה, עבור הקריאה group_by(len, [\"hi\", \"bye\", \"yo\", \"try\"])
יוחזר הערך: {2: [\"hi\", \"yo\"], 3: [\"bye\", \"try\"]}.\n",
+ "
\n",
+ " כתבו פונקציה בשם zip_with שמקבלת פונקציה כפרמטר ראשון, ושני iterable־ים או יותר בפרמטרים שאחריו.
\n",
+ " הפונקציה תחזיר רשימה, שבה האיבר במקום ה־N־י הוא הערך שחזר מהעברת כל הערכים במקום ה־N־י של כל ה־iterables לפונקציה.\n",
+ "
\n", + "לדוגמה:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "zipwith(add, [1, 2, 3], [4, 5, 6])
יוחזר הערך: [5, 7, 9].zipwith(max, (5, 4), (2, 5), (6, -6))
יוחזר הערך: [6, 5].\n", + " אפשר להניח שה־iterables המועברים לפונקציה זהים באורכם.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### זכרתם?\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כתבו פונקציה שמקבלת מסר להצפנה, ויוצרת ממנו תמונה מוצפנת.
\n",
+ " השתמשו בשיטת ההצפנה שהוצגה במחברת הקודמת. \n",
+ "
\n", + " למדו את החוקים של המשחק סט, מהערך בוויקיפדיה או מ־YouTube.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " כדי לחשב את סעיף 3, הריצו את הבדיקה על 10,000 מקרים שבהם פתחתם 12 קלפים מהחפיסה המעורבבת.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 100 מעלות" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כתבו קוד שמוצא את 100 השירים הפופולריים ביותר לפי מדד Hot 100 של Billboard.
\n",
+ " השיגו את המילים של השירים שמצאתם, ושרטטו גרף שמראה כמה פעמים מופיעה כל מילה מ־100 המילים הנפוצות ביותר בכל השירים.\n",
+ "
\n", + " בונוס: בצעו ניתוח מעניין אחר, כמו מיהם האומנים שמשתמשים בהכי הרבה מילים בשירים שלהם!\n", + "
" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/content/week06/images/deeper.svg b/content/week06/images/deeper.svg new file mode 100644 index 0000000..1b7344f --- /dev/null +++ b/content/week06/images/deeper.svg @@ -0,0 +1 @@ +\n",
+ " בוקר חדש, השמש הפציעה והחלטתם שצברתם מספיק ידע בקורס כדי לפתוח רשת חברתית משלכם, בשם צ'יקצ'וק.
\n",
+ " אתם משליכים את מחברות הפייתון מהחלון ומתחילים לתכנת במרץ את המערכת שתעזור לכם לנהל את המשתמשים.\n",
+ "
\n", + " לכל משתמש יש את התכונות הבאות:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בחרו סוג משתנה שיאפשר לכם לאחסן בנוחות את הנתונים הללו.
\n",
+ " צרו שני משתמשים לדוגמה, והשתמשו בסוג המשתנה שבחרתם.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " לפני שנציג את פתרון השאלה, נעמיק מעט ברעיון הכללי שעומד מאחורי הדוגמה הזו.
\n",
+ " כל משתמש שניצור הוא מעין אסופת תכונות – במקרה שלנו התכונות הן שם פרטי, שם משפחה, כינוי וגיל.
\n",
+ " לכל תכונה יהיה ערך המתאים לה, ויחד הערכים הללו יצרו משתמש אחד.\n",
+ "
\n", + " נמצא עוד דוגמאות לאסופות תכונות שכאלו:\n", + "
\n", + "\n", + "\n", + " חשבו על עוד 3 דוגמאות לעצמים שאפשר לתאר כערכים עם אסופת תכונות.\n", + "
\n", + "\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n", + " ניצור שני משתמשים לדוגמה לפי תכונותיהם שהוצגו לעיל:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "user1 = {\n", + " 'first_name': 'Christine',\n", + " 'last_name': 'Daaé',\n", + " 'nickname': 'Little Lotte',\n", + " 'age': 20,\n", + "}\n", + "user2 = {\n", + " 'first_name': 'Elphaba',\n", + " 'last_name': 'Thropp',\n", + " 'nickname': 'Elphie',\n", + " 'age': 19,\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " תוכלו ליצור בעצמכם פונקציה שיוצרת משתמש חדש?
\n",
+ " זה לא מסובך מדי:\n",
+ "
\n",
+ " נוכל גם לממש פונקציות שיעזרו לנו לבצע פעולות על כל אחד מהמשתמשים.
\n",
+ " לדוגמה: הפונקציה describe_as_a_string תקבל משתמש ותחזיר לנו מחרוזת שמתארת אותו,
\n",
+ " והפונקציה celeberate_birthday תקבל משתמש ותגדיל את גילו ב־1:\n",
+ "
\n",
+ " הצלחנו לערוך את ערכו של user['age']
מבלי להחזיר ערך, כיוון שמילונים הם mutable.
\n",
+ " אם זה נראה לכם מוזר, חזרו למחברת על mutability ו־immutability.\n",
+ "
\n",
+ " בשלב הזה בתוכניתנו קיימות קבוצת פונקציות שמטרתן היא ניהול של משתמשים ושל תכונותיהם.
\n",
+ "
\n",
+ " נוכל להוסיף למשתמש תכונות נוספות, כמו דוא\"ל ומשקל, לדוגמה,
\n",
+ " או להוסיף לו פעולות שיהיה אפשר לבצע עליו, כמו הפעולה eat_bourekas, שמוסיפה לתכונת המשקל של המשתמש חצי קילו.
\n",
+ "
\n",
+ " אף על פי שהרעיון נחמד, ככל שנרבה להוסיף פעולות ותכונות, תגבר תחושת האי־סדר שאופפת את הקוד הזה.
\n",
+ " קל לראות שהקוד שכתבנו מפוזר על פני פונקציות רבות בצורה לא מאורגנת.
\n",
+ " במילים אחרות – אין אף מבנה בקוד שתחתיו מאוגדות כל הפונקציות והתכונות ששייכות לטיפול במשתמש.\n",
+ "
\n",
+ " הבעיה תצוף כשנרצה להוסיף לתוכנה שלנו עוד מבנים שכאלו.
\n",
+ " לדוגמה, כשנרצה להוסיף לצ'יקצ'וק יכולת לניהול סרטונים – שתכונותיהם אורך סרטון ומספר לייקים, והפעולה עליהם היא היכולת לעשות Like לסרטון.
\n",
+ " הקוד לניהול המשתמש והקוד לניהול הסרטונים עלולים להתערבב, יווצרו תלויות ביניהם וחוויית ההתמצאות בקוד תהפוך ללא נעימה בעליל.
\n",
+ "
\n",
+ " החוסר באיגוד התכונות והפונקציות אף מקשה על הקורא להבין לאן שייכות כל אחת מהתכונות והפונקציות, ומה תפקידן בקוד.
\n",
+ " מי שמסתכל על הקוד שלנו לא יכול להבין מייד ש־describe_as_a_string מיועדת לפעול רק על מבנים שנוצרו מ־create_user.
\n",
+ " הוא עלול לנסות להכניס מבנים אחרים ולהקריס את התוכנית, או גרוע מכך – להיתקל בבאגים בעתיד, בעקבות שימוש לא נכון בפונקציה.\n",
+ "
\n",
+ " במהלך המחברת ראינו דוגמאות למבנים שהגדרנו כאוספים של תכונות ושל פעולות.
\n",
+ " משתמש באפליקציית צ'יקצ'וק, לדוגמה, מורכב מהתכונות שם פרטי, שם משפחה, כינוי וגיל, ומהפעולות \"חגוג יום הולדת\" ו\"תאר כמחרוזת\".
\n",
+ " נורה עשויה להיות מורכבת מהתכונות צבע ומצב (דולקת או לא), ומהפעולות \"הדלק נורה\" ו\"כבה נורה\".
\n",
+ "
\n",
+ " מחלקה היא דרך לתאר לפייתון אוסף כזה של תכונות ושל פעולות, ולאגד אותן תחת מבנה אחד.
\n",
+ " אחרי שתיארנו בעזרת מחלקה אילו תכונות ופעולות מאפיינות עצם מסוים, נוכל להשתמש בה כדי לייצר כמה עצמים כאלו שנרצה.
\n",
+ "
\n",
+ " נדמיין מחלקה כמו שבלונה – תבנית שמתארת אילו תכונות ופעולות מאפיינות סוג עצם מסוים.
\n",
+ " מחלקה שעוסקת במשתמשים, לדוגמה, תתאר עבור פייתון מאילו תכונות ופעולות מורכב כל משתמש.
\n",
+ "
\n",
+ " בעזרת אותה מחלקת משתמשים (או שבלונת משתמשים, אם תרצו), נוכל ליצור משתמשים רבים.
\n",
+ " כל משתמש שניצור באמצעות השבלונה ייקרא \"מופע\" (או Instance) – יחידה אחת, עצמאית, שמכילה את התכונות והפעולות שתיארנו.
\n",
+ " אנחנו נשתמש במחלקה שוב ושוב כדי ליצור כמה משתמשים שנרצה, בדיוק כמו שנשתמש בשבלונה.\n",
+ "
\n",
+ " יש עוד הרבה מה להגיד והרבה מה להגדיר, אבל נשמע שמתחתי אתכם מספיק.
\n",
+ " בואו ניגש לקוד!\n",
+ "
\n",
+ " ראשית, ניצור את המחלקה הפשוטה ביותר שאנחנו יכולים לבנות, ונקרא לה User.
\n",
+ " בהמשך המחברת נרחיב את המחלקה, והיא תהיה זו שמטפלת בכל הקשור במשתמשים של צ'יקצ'וק:\n",
+ "
\n",
+ " ניסינו ליצור את המבנה הכי קצר שאפשר, אבל class
חייב להכיל קוד.
\n",
+ " כדי לעקוף את המגבלה הזו, השתמשנו במילת המפתח pass
, שאומרת לפייתון \"אל תעשי כלום\".\n",
+ "
\n",
+ " בקוד שלמעלה השתמשנו במילת המפתח class
כדי להצהיר על מחלקה חדשה.
\n",
+ " מייד לאחר מכן ציינו את שם המחלקה שאנחנו רוצים ליצור – User במקרה שלנו.
\n",
+ " שם המחלקה נתון לחלוטין לבחירתנו, והמילה User לא אומרת לפייתון שום דבר מיוחד. באותה המידה יכולנו לבחור כל שם אחר.\n",
+ "
\n",
+ " הדבר שחשוב לזכור הוא שהמחלקה היא לא המשתמש עצמו, אלא רק השבלונה שלפיה פייתון תבנה את המשתמש.
\n",
+ " אמנם כרגע המחלקה User ריקה ולא מתארת כלום, אבל פייתון עדיין תדע ליצור משתמש חדש אם נבקש ממנה לעשות זאת.
\n",
+ " נבקש מהמחלקה ליצור עבורנו משתמש חדש. נקרא לה בשמה ונוסיף סוגריים, בדומה לקריאה לפונקציה:\n",
+ "
\n",
+ " כעת יצרנו משתמש, ואנחנו יכולים לשנות את התכונות שלו.
\n",
+ " מבחינה מילולית, נהוג להגיד שיצרנו מופע (Instance) או עצם (אובייקט, Object) מסוג User, ששמו user1.
\n",
+ " השתמשנו לשם כך במחלקה בשם User.\n",
+ "
\n",
+ " נשנה את תכונות המשתמש.
\n",
+ " כדי להתייחס לתכונה של מופע כלשהו בפייתון, נכתוב את שם המשתנה שמצביע למופע, נקודה, ואז שם התכונה.
\n",
+ " אם נרצה לשנות את התכונה – נבצע אליה השמה:\n",
+ "
\n", + " נוכל לאחזר את התכונות הללו בקלות, באותה הצורה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(user1.age)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " ואם נבדוק מה הסוג של המשתנה user1, מצפה לנו הפתעה נחמדה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "type(user1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " איזה יופי! המחלקה גרמה לכך ש־User הוא ממש סוג משתנה בפייתון עכשיו.
\n",
+ " קחו לעצמכם רגע להתפעל – יצרנו סוג משתנה חדש בפייתון!
\n",
+ " אם כך, המשתנה user1 מצביע על מופע של משתמש, שסוגו User.\n",
+ "
\n", + " ננסה ליצור מופע נוסף, הפעם של משתמש אחר:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "user2 = User()\n", + "user2.first_name = \"Harry\"\n", + "user2.last_name = \"Potter\"\n", + "user2.age = 39\n", + "user2.nickname = \"BoyWhoLived1980\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " ונשים לב ששני המופעים מתקיימים זה לצד זה, ולא דורסים את הערכים זה של זה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"{user1.first_name} {user1.last_name} is {user1.age} years old.\")\n", + "print(f\"{user2.first_name} {user2.last_name} is {user2.age} years old.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " המצב הזה מתקיים כיוון שכל קריאה למחלקה User יוצרת מופע חדש של משתמש.
\n",
+ " כל אחד מהמופעים הוא ישות נפרדת שמתקיימת בזכות עצמה.\n",
+ "
\n",
+ " צרו מחלקה בשם Point שמייצגת נקודה.
\n",
+ " צרו 2 מופעים של נקודות: אחת בעלת x שערכו 3 ו־y שערכו 1, והשנייה בעלת x שערכו 4 ו־y שערכו 1.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " שמות מחלקה ייכתבו באות גדולה בתחילתם, כדי להבדילם מפונקציות וממשתנים רגילים.
\n",
+ " אם שם המחלקה מורכב מכמה מילים, האות הראשונה בכל מילה תהא אות גדולה. בשם לא יופיעו קווים תחתונים.
\n",
+ " לדוגמה, מחלקת PopSong.\n",
+ "
\n",
+ " יצירת מחלקה ריקה זה נחמד, אבל זה לא מרגיש שעשינו צעד מספיק משמעותי כדי לשפר את איכות הקוד מתחילת המחברת.
\n",
+ " לדוגמה, אם אנחנו רוצים להדפיס את הפרטים של משתמש מסוים, עדיין נצטרך לכתוב פונקציה כזו:\n",
+ "
\n",
+ " הפונקציה עדיין מסתובבת לה חופשייה ולא מאוגדת תחת אף מבנה – וזה בדיוק המצב שניסינו למנוע.
\n",
+ " למזלנו הפתרון לבעיית איגוד הקוד הוא פשוט. נוכל להדביק את קוד הפונקציה תחת המחלקה User
:\n",
+ "
\n",
+ " בתא שלמעלה הגדרנו את הפונקציה describe_as_a_string בתוך המחלקה User.
\n",
+ " פונקציה שמוגדרת בתוך מחלקה נקראת פעולה (Method), שם שניתן לה כדי לבדל אותה מילולית מפונקציה רגילה.\n",
+ "
\n",
+ " למעשה, בתא שלמעלה הוספנו את הפעולה describe_as_a_string לשבלונה של המשתמש.
\n",
+ " מעכשיו, כל מופע חדש של משתמש יוכל לקרוא לפעולה describe_as_a_string בצורה הבאה:\n",
+ "
\n",
+ " חדי העין שמו ודאי לב למשהו מעט משונה בקריאה לפעולה describe_as_a_string.
\n",
+ " הפעולה מצפה לקבל פרמטר (קראנו לו user), אבל כשקראנו לה בתא האחרון לא העברנו לה אף ארגומנט!
\n",
+ "
\n",
+ " זהו קסם ידוע ונחמד של מחלקות: כשמופע קורא לפעולה כלשהי – אותו מופע עצמו מועבר אוטומטית כארגומנט הראשון לפעולה.
\n",
+ " לדוגמה, בקריאה user3.describe_as_a_string()
, המופע user3 הועבר לתוך הפרמטר user של describe_as_a_string.
\n",
+ "
\n",
+ " המוסכמה היא לקרוא תמיד לפרמטר הקסום הזה, זה שהולך לקבל את המופע, בשם self.
\n",
+ " נשנה את ההגדרה שלנו בהתאם למוסכמה:\n",
+ "
\n", + " טעות נפוצה היא לשכוח לשים self כפרמטר הראשון בפעולות שנגדיר.\n", + "
\n", + "\n",
+ " צרו פעולה בשם describe_as_a_string עבור מחלקת Point שיצרתם.
\n",
+ " הפעולה תחזיר מחרוזת בצורת (x, y).\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " הפיסה החסרה בפאזל היא יצירת המופע.
\n",
+ " אם נרצה ליצור משתמש חדש, עדיין נצטרך להציב בו תכונות אחת־אחת – וזה לא כזה כיף.
\n",
+ " נשדרג את עצמנו ונכתוב פונקציה שקוראת ל־User ויוצרת מופע עם כל התכונות שלו:
\n",
+ "
\n",
+ " אבל הגדרה שכזו, כמו שכבר אמרנו, סותרת את כל הרעיון של מחלקות.
\n",
+ " הרי המטרה של מחלקות היא קיבוץ כל מה שקשור בניהול התכונות והפעולות תחת המחלקה.\n",
+ "
\n", + " נעתיק את create_user לתוך מחלקת User, בשינויים קלים: \n", + "
\n", + "user = User()
ו־return user
.\n", + " עכשיו נוכל ליצור משתמש חדש, בצורה החביבה והמקוצרת הבאה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "user4 = User()\n", + "user4.create_user('Daenerys', 'Targaryen', 'Mhysa', 23)\n", + "user4.describe_as_a_string()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### תרגיל ביניים: מחלקת נקודות" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " מינרווה מקגונגל יצאה לבילוי לילי בסמטת דיאגון,
\n",
+ " ואחרי לילה עמוס בשתיית שיכר בקלחת הרותחת, היא מעט מתקשה לחזור להוגוורטס.\n",
+ "
\n",
+ " הוסיפו את הפעולות create_point ו־distance למחלקת הנקודה שיצרתם.
\n",
+ " הפעולה create_point תקבל כפרמטרים x ו־y, ותיצוק תוכן למופע שיצרתם.
\n",
+ " הפעולה distance תחזיר את המרחק של מקגונגל מהוגוורטס, הממוקם בנקודה (0, 0).\n",
+ "
\n",
+ " נוסחת המרחק היא חיבור בין הערכים המוחלטים של נקודות ה־x וה־y.
\n",
+ " לדוגמה:\n",
+ "
x = 5, y = 3הוא 8.
x = 0, y = 3הוא 3.
x = -3, y = 3הוא 6.
x = -5, y = 0הוא 5.
x = 0, y = 0הוא 0.
\n", + " ודאו שהתוכנית שלכם מחזירה Success! עבור הקוד הבא:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "current_location = Point()\n", + "current_location.create_point(5, 3)\n", + "if current_location.distance() == 8:\n", + " print(\"Success!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### פעולות קסם" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כדי להקל אפילו עוד יותר על המלאכה, בפייתון יש פעולות קסם (Magic Methods).
\n",
+ " אלו פעולות עם שם מיוחד, שאם נגדיר אותן במחלקה, הן ישנו את ההתנהגות שלה או של המופעים הנוצרים בעזרתה.\n",
+ "
__str__
\n",
+ " נתחיל, לדוגמה, מהיכרות קצרה עם פעולת הקסם __str__
(עם קו תחתון כפול, מימין ומשמאל לשם הפעולה).
\n",
+ " אם ננסה סתם ככה להמיר למחרוזת את user4 שיצרנו קודם לכן, נקבל בהלה והיסטריה:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "user4 = User()\n",
+ "user4.create_user('Daenerys', 'Targaryen', 'Mhysa', 23)\n",
+ "str(user4)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ " פייתון אמנם אומרת דברים נכונים, כמו שמדובר באובייקט (מופע) מהמחלקה User ואת הכתובת שלו בזיכרון, אבל זה לא באמת מועיל.
\n",
+ "
\n",
+ " כיוון שפונקציית ההדפסה print, מאחורי הקלעים, מבקשת את צורת המחרוזת של הארגומנט שמועבר אליה,
\n",
+ " גם קריאה ל־print ישירות על user4 תיצור את אותה תוצאה לא ססגונית:\n",
+ "
\n",
+ " המחלקה שלנו, כמובן, כבר ערוכה להתמודד עם המצב.
\n",
+ " בזכות הפעולה describe_as_a_string שהגדרנו קודם לכן נוכל להדפיס את פרטי המשתמש בקלות יחסית:\n",
+ "
\n",
+ " אבל יש דרך קלה עוד יותר!
\n",
+ " ניחשתם נכון – פעולת הקסם __str__
.
\n",
+ " נחליף את השם של הפעולה describe_as_a_string, ל־__str__
:\n",
+ "
\n",
+ " ראו איזה קסם! עכשיו המרה של כל מופע מסוג User למחרוזת היא פעולה ממש פשוטה!
\n",
+ "
\n",
+ " בתא שלמעלה, הגדרנו את פעולת הקסם __str__
.
\n",
+ " הפעולה מקבלת כפרמטר את self, המופע שביקשנו להמיר למחרוזת,
\n",
+ " ומחזירה לנו מחרוזת שאנחנו הגדרנו כמחרוזת שמתארת את המופע.\n",
+ "
\n",
+ " הגדרת פעולת הקסם __str__
עבור מחלקה מסוימת מאפשרת לנו להמיר מופעים למחרוזות בצורה טבעית.\n",
+ "
__init__
\n",
+ " פעולת קסם חשובה אף יותר, ואולי המפורסמת ביותר, נקראת __init__
.
\n",
+ " היא מאפשרת לנו להגדיר מה יקרה ברגע שניצור מופע חדש:\n",
+ "
\n",
+ " בדוגמת הקוד שלמעלה הגדרנו את פעולת הקסם __init__
, שתרוץ מייד כשנוצר מופע חדש.
\n",
+ " החלטנו שברגע שייווצר מופע של משתמש, תודפס ההודעה New user has been created!.\n",
+ "
\n",
+ " הכיף הגדול ב־__init__
הוא היכולת שלה לקבל פרמטרים.
\n",
+ " נוכל להעביר אליה את הארגומנטים בקריאה לשם המחלקה, בעת יצירת המופע 🤯\n",
+ "
\n",
+ " בתא שלמעלה הגדרנו שפעולת הקסם __init__
תקבל כפרמטר הודעה להדפסה.
\n",
+ " ההודעה תישמר בתכונה creation_message השייכת למופע, ותודפס מייד לאחר מכן.
\n",
+ " את ההודעה העברנו כארגומנט בעת הקריאה לשם המחלקה, User, שיוצרת את המופע.\n",
+ "
\n",
+ " ואם כבר יש לנו משהו שרץ כשאנחנו יוצרים את המופע... והוא יודע לקבל פרמטרים...
\n",
+ " אתם חושבים על מה שאני חושב?
\n",
+ " בואו נשנה את השם של create_user ל־__init__
!
\n",
+ " בצורה הזו נוכל לצקת את התכונות למופע מייד עם יצירתו, ולוותר על קריאה נפרדת לפעולה שמטרתה למלא את הערכים:\n",
+ "
\n",
+ " איגדנו את יצירת תכונות המופע תחת פעולה אחת, שרצה כשהוא נוצר.
\n",
+ " הרעיון הנפלא הזה נפוץ מאוד בשפות תכנות שתומכות במחלקות, ומוכרת בשם פעולת אתחול (Initialization Method).
\n",
+ " זו גם הסיבה לשם הפעולה – המילה init נגזרת מהמילה initialization, אתחול. \n",
+ "
\n",
+ " שפצו את מחלקת הנקודה שיצרתם, כך שתכיל __init__
ו־__str__
.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " צ'יקצ'וק שמה את ידה על פרטי המשתמשים של הרשת החברתית המתחרה, סניילצ'אט.
\n",
+ " רשימת המשתמשים נראית כך:\n",
+ "
\n",
+ " נניח, לכאורה בלבד, שאנחנו רוצים להעתיק את אותה רשימת משתמשים ולצרף אותה לרשת החברתית שלנו.
\n",
+ " קחו דקה וחשבו איך הייתם עושים את זה.\n",
+ "
\n",
+ " זכרו שקריאה למחלקה User היא ככל קריאה לפונקציה אחרת,
\n",
+ " ושהמופע שחוזר ממנה הוא ערך בדיוק כמו כל ערך אחר.\n",
+ "
\n", + " נוכל ליצור רשימת מופעים של משתמשים. לדוגמה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "our_users = []\n", + "for user_details in snailchat_users:\n", + " new_user = User(*user_details) # Unpacking – התא הראשון עובר לפרמטר התואם, וכך גם השני, השלישי והרביעי\n", + " our_users.append(new_user)\n", + " print(new_user)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בקוד שלמעלה יצרנו רשימה ריקה, שאותה נמלא במשתמשים שנגנוב שנשאיל מסניילצ'אט.
\n",
+ " נעביר את הפרטים של כל אחד מהמשתמשים המופיעים ב־snailchat_users, ל־__init__
של User,
\n",
+ " ונצרף את המופע החדש שנוצר לתוך הרשימה החדשה שיצרנו.\n",
+ "
\n", + " עכשיו הרשימה our_users היא רשימה לכל דבר, שכוללת את כל המשתמשים החדשים שהצטרפו לרשת החברתית שלנו:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(our_users[0])\n", + "print(our_users[1])\n", + "print(our_users[2])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " צרו את רשימת כל הנקודות שה־x וה־y שלהן הוא מספר שלם בין 0 ל־6.
\n",
+ " לדוגמה, רשימת כל הנקודות שה־x וה־y שלהן הוא בין 0 ל־2 היא:
\n",
+ " [(0, 0), (0, 1), (1, 0), (1, 1), (0, 2), (1, 2), (2, 0), (2, 1), (2, 2)]\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " נסקור כמה דוגמאות כדי לוודא שבאמת הבנו כיצד מתנהגות מחלקות.
\n",
+ " נגדיר את מחלקת User שאנחנו מכירים, ונצרף לה את הפעולה celebrate_birthday, שכזכור, מגדילה את גיל המשתמש ב־1:\n",
+ "
\n",
+ " ניסיון ליצור מופע של משתמש ולחגוג לו יום הולדת יגרום לשגיאה.
\n",
+ " תוכלו לנחש מה תהיה השגיאה עוד לפני שתריצו?\n",
+ "
\n",
+ " ניסינו לשנות את המשתנה age – אך הוא אינו מוגדר.
\n",
+ " כדי לשנות את הגיל של המשתמש שיצרנו, נהיה חייבים להתייחס ל־self.age
.
\n",
+ " אם לא נציין במפורש שאנחנו רוצים לשנות את התכונה age ששייכת ל־self, פייתון לא תדע לאיזה מופע אנחנו מתכוונים.
\n",
+ " נתקן:\n",
+ "
\n",
+ " באותה המידה, תכונות שהוגדרו כחלק ממופע לא מוגדרות מחוצה לו.
\n",
+ " אפשר להשתמש, לדוגמה, בשם המשתנה age מבלי לחשוש לפגוע בתפקוד המחלקה או בתפקוד המופעים:\n",
+ "
\n", + " כדי לשנות את גילו של המשתמש, נצטרך להתייחס אל התכונה שלו בצורת הכתיבה שלמדנו:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "user6.age = 10\n", + "print(user6)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### תכונה או פעולה שלא קיימות" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " שגיאה שמתרחשת לא מעט היא פנייה לתכונה או לפעולה שלא קיימות עבור המופע.
\n",
+ " לדוגמה:\n",
+ "
\n",
+ " יצרנו רשימת קוביות וביצענו השמה כך ש־dice_bag תצביע עליה.
\n",
+ " כעת נדפיס את התכונה is_valid של כל אחת מהקוביות:\n",
+ "
\n",
+ " הבעיה היא שהקוביה הראשונה שיצרנו קיבלה את המספר 0.
\n",
+ " במקרה כזה, התנאי בפעולת האתחול (__init__
) לא יתקיים, והתכונה is_valid לא תוגדר.
\n",
+ " כשהלולאה תגיע לקובייה 0 ותנסה לגשת לתכונה is_valid, נגלה שהיא לא קיימת עבור הקובייה 0, ונקבל AttributeError.\n",
+ "
\n", + " נתקן:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class Dice:\n", + " def __init__(self, number):\n", + " self.is_valid = (1 <= number <= 6) # לא חייבים סוגריים\n", + "\n", + "\n", + "dice_bag = [Dice(roll_result) for roll_result in range(7)]\n", + "for dice in dice_bag:\n", + " print(dice.is_valid)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## סיכום" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " במחברת זו רכשנו כלים לעבודה עם מחלקות ועצמים, ולייצוג אוספים של תכונות ופעולות.
\n",
+ " כלים אלו יעזרו לנו לארגן טוב יותר את התוכנית שלנו ולייצג ישויות מהעולם האמיתי בצורה אינטואיטיבית יותר.\n",
+ "
\n",
+ " נהוג לכנות את עולם המחלקות בשם \"תכנות מונחה עצמים\" (Object Oriented Programming, או OOP).
\n",
+ " זו פרדיגמת תכנות הדוגלת ביצירת מחלקות לצורך חלוקת קוד טובה יותר,
\n",
+ " ובתיאור עצמים מהעולם האמיתי בצורה טובה יותר, כאוספים של תכונות ופעולות.\n",
+ "
\n",
+ " תכנות מונחה עצמים הוא פיתוח מאוחר יותר של פרדיגמת תכנות אחרת שאתם כבר מכירים, הנקראת \"תכנות פרוצדורלי\".
\n",
+ " פרדיגמה זו דוגלת בחלוקת הקוד לתתי־תוכניות קטנות (מה שאתם מכירים כפונקציות), כדי ליצור קוד שמחולק טוב יותר וקל יותר לתחזוק.\n",
+ "
\n", + " פייתון תומכת הן בתכנות פרוצדורלי והן בתכנות מונחה עצמים.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## מונחים" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__init__
ו־__str__
.\n",
+ " \n",
+ " כתבו מחלקה המייצגת נתיב תקין במערכת ההפעלה חלונות.
\n",
+ " הנתיב מחולק לחלקים באמצעות התו / או התו \\.
\n",
+ " החלק הראשון בנתיב הוא תמיד אות הכונן ואחריה נקודתיים.
\n",
+ " החלקים שנמצאים אחרי החלק הראשון, ככל שיש כאלו, הם תיקיות וקבצים.
\n",
+ " דוגמאות לנתיבים תקינים:\n",
+ "
\n", + " המחלקה תכלול את הפעולות הבאות:\n", + "
\n", + "\n",
+ " מחלקת המוצר בצ'יקצ'וק החליטה להוסיף פיצ'ר שמאפשר למשתמשים ליצור סקרים, וכרגיל כל העבודה נופלת עליכם.
\n",
+ " כתבו מחלקה בשם Poll שמייצגת סקר.
\n",
+ " פעולת האתחול של המחלקה תקבל כפרמטר את שאלת הסקר, וכפרמטר נוסף iterable עם כל אפשרויות ההצבעה לסקר.
\n",
+ " כל אפשרות הצבעה בסקר מיוצגת על ידי מחרוזת.
\n",
+ " המחלקה תכיל את הפעולות הבאות: \n",
+ "
\n",
+ " במקרה של תיקו, החזירו מ־get_winner את אחת האפשרויות המובילות.
\n",
+ " החזירו מהפעולות vote, add_option ו־remove_option את הערך True אם הפעולה עבדה כמצופה.
\n",
+ " במקרה של הצבעה לאפשרות שאינה קיימת, מחיקת אפשרות שאינה קיימת או הוספת אפשרות שכבר קיימת, החזירו False.
\n",
+ "
\n", + "ודאו שהקוד הבא מדפיס רק True עבור התוכנית שכתבתם:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def cast_multiple_votes(poll, votes):\n", + " for vote in votes:\n", + " poll.vote(vote)\n", + "\n", + "\n", + "bridge_question = Poll('What is your favourite colour?', ['Blue', 'Yellow'])\n", + "cast_multiple_votes(bridge_question, ['Blue', 'Blue', 'Yellow'])\n", + "print(bridge_question.get_winner() == 'Blue')\n", + "cast_multiple_votes(bridge_question, ['Yellow', 'Yellow'])\n", + "print(bridge_question.get_winner() == 'Yellow')\n", + "print(bridge_question.get_votes() == [('Yellow', 3), ('Blue', 2)])\n", + "bridge_question.remove_option('Yellow')\n", + "print(bridge_question.get_winner() == 'Blue')\n", + "print(bridge_question.get_votes() == [('Blue', 2)])\n", + "bridge_question.add_option('Yellow')\n", + "print(bridge_question.get_votes() == [('Blue', 2), ('Yellow', 0)])\n", + "print(not bridge_question.add_option('Blue'))\n", + "print(bridge_question.get_votes() == [('Blue', 2), ('Yellow', 0)])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### משחקי הרעב" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " קטניס אוורדין הלכה לאיבוד באיזו זירה מעצבנת, ועכשיו היא מחפשת את הסניף הקרוב של אבו־חסן למנה משולשת ראויה.
\n",
+ "
\n",
+ " צורת הזירה היא משולש שקודקודיו (0, 0), (2, 2) ו־(4, 0).
\n",
+ " קטניס מתחילה מאחד הקודקודים ומחליטה על הצעד הבא שלה כך:
\n",
+ " \n",
+ "
\n",
+ " כתבו פעולה בשם plot_walks, שמקבלת כפרמטר את מספר הצעדים של קטניס.
\n",
+ " הפעולה תצייר מפת נקודות בגודל 4 על 4, שכל נקודה בה מציינת מקום שקטניס סימנה במפה שלה.
\n",
+ "
\n",
+ " השתמשו במנועי חיפוש כדי לקרוא על פעולות קסם שיכולות לעזור לכם, ועל מודולים לשרטוט גרפים.
\n",
+ " שימו לב שנקודות יכולות להיות ממוקמות על x ו־y עשרוניים.\n",
+ "
\n",
+ " אחרי שחיפשתם זמן רב מודולים ואפילו השתמשתם באחד או שניים מהם, הבנתם ודאי את החשיבות הרבה של תיעוד טוב.
\n",
+ " בין אם תכתבו קוד כחלק מפרויקט צוותי או שתשחררו מודול בקוד פתוח, איכות התיעוד תקבע אם יהיו לכם לקוחות מרוצים או אם תצטרכו לעמוד מול המון זועם. \n",
+ "
\n",
+ " כמפתחים וכמשתמשים, התיעוד עוזר לנו להבין מהי תכליתם של מודולים, טיפוסים, מחלקות, פעולות ופונקציות.
\n",
+ " הוא עוזר לנו להתמצא בקוד ולהבין במהירות אם מה שאנחנו רואים מתאים לנו ומה תהיה צורת השימוש המיטבית בו,
\n",
+ " ובכלל, בשפה כמו פייתון, שמקדשת את ערך קְרִיאוּת הקוד, יש ערך גדול מאוד לתיעוד ראוי. \n",
+ "
\n", + " במחברת הקרובה נלמד על תיעוד ראוי בפייתון, על מוסכמות תיעוד ועל גישות שונות לתיעוד.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### הערות ותיעוד" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " לפני שנדון לעומק בתיעוד, נדבר מעט על ההבדל שבין הערות בקוד לבין תיעוד בקוד.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " הערות הן שורות מלל המיועדות למפתחים.
\n",
+ " הן מכילות מידע אינפורמטיבי על החלטות שקיבלתם בנוגע לקוד. מידע זה נועד לעזור לעמיתיכם לתחזק את הקוד.
\n",
+ " הן יופיעו קרוב ככל האפשר שאפשר לקוד שאליו הן מתייחסות, לעיתים אפילו ממש בשורה של הקוד עצמו.
\n",
+ " הערות אמורות להסביר למה דבר מסוים כתוב כמו שהוא כתוב, ולעולם לא מה כתוב בקוד.\n",
+ "
\n",
+ " לעומתן, תיעוד הוא מלל המיועד לאנשים שמשתמשים בקוד שלכם.
\n",
+ " הוא יוצמד למודולים, למחלקות, לפעולות ולפונקציות, ויספר בקצרה ובענייניות מה עושה הפונקציה ואיך להשתמש בה.
\n",
+ "
\n",
+ " אם, לדוגמה, פיתחתם מודול ושיחררתם אותו לאינטרנט,
\n",
+ " המשתמשים בו יצפו לתיעוד שברור ממנו איך משתמשים בקוד, ומה קורה בכל אחד ממקרי הקצה.\n",
+ "
\n",
+ " כפי שאתם ודאי זוכרים מהמחברות שקראתם עד כה, הערה תתחיל בתו #
ורווח שיבוא אחריו.
\n",
+ " אחרי הסולמית והרווח יבוא המלל שמסביר את ההחלטות שהתקבלו לצורך כתיבת הקוד.\n",
+ "
\n", + " נראה לדוגמה קוד קצר לפירוק מספרים ראשוניים עם הערות שהוטמעו בו:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import math\n", + "\n", + "\n", + "def factorize_prime(number):\n", + " while number % 2 == 0: \n", + " yield 2\n", + " number = number // 2\n", + " \n", + " # `number` must be odd at this point (we've just factored 2 out).\n", + " # Skip even numbers. Square root is good upper limit, check\n", + " # https://math.stackexchange.com/a/1039525 for more info.\n", + " divisor = 3\n", + " max_divisor = math.ceil(number ** 0.5)\n", + " while number != 1 and divisor <= max_divisor: \n", + " if number % divisor == 0:\n", + " yield divisor\n", + " number = number // divisor\n", + " else:\n", + " divisor += 2\n", + " \n", + " # If `number` is a prime, just print `number`.\n", + " # 1 is not a prime, 2 already taken care of.\n", + " if number > 2:\n", + " yield number\n", + "\n", + "\n", + "print(list(factorize_prime(5)))\n", + "print(list(factorize_prime(100)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בקוד שלמעלה מופיעים שני מקרים של \"Block Comment\".
\n",
+ " מדובר בשורה אחת או יותר של הערה שבאה לפני פיסת קוד, ומטרתה לבאר דברים בקוד.
\n",
+ " ה־Block ייכתב באותה רמת הזחה של הקוד שאליו הוא מתייחס, וכל שורה בו תתחיל בתו #
שלאחריו יבוא רווח.\n",
+ "
\n", + " נשים לב לנקודות סגנון חשובות, שניכרות היטב בדוגמה האחרונה:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " הערה יכולה להיות ממוקמת גם בסוף שורת הקוד:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Hello World\") # This is a comment" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " במקרה שבו ההערה נמצאת באותה שורה יחד עם הקוד (כמו בתא האחרון), נהוג לשים לפחות שני רווחים לפני הסימן #
.
\n",
+ " זוהי צורה מקובלת פחות לכתיבת הערות, כיוון שהיא מאריכה את שורת הקוד, שוברת את רצף הקריאה ולרוב מיותרת.\n",
+ "
\n",
+ " מתכנתים מתחילים נוטים להסביר מה הקוד עושה, ולשם כך הם משתמשים לעיתים קרובות ב־Inline Comments.
\n",
+ " הימנעו מלהסביר מה הקוד שלכם עושה.\n",
+ "
\n", + " דוגמה להערה לא טובה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "snake_y = snake_y % 10 # Take the remainder from 10" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " ולעומתה, הערה המתקבלת על הדעת:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "snake_y = snake_y % 10 # Wrap from the bottom if the snake hits the top" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### הוויכוח על הערות" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " בעולם התוכנה המקצועי ניטש ויכוח רב שנים על מתי נכון להוסיף הערות לקוד.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " דעה פופולרית אחת דוגלת בריבוי הערות בקוד.
\n",
+ " אלו המצדדים בדעה זו מוסיפים לקוד שלהם הערות לצרכים מגוונים:
\n",
+ "
# FIXME
לציון קטע קוד שצריך לתקן.# TODO
ואחריו מלל שמסביר משהו שעדיין צריך לבצע ועוד לא נפתר.# HACK
לציון מעקף שנועד לפתור בעיה, פעמים רבות בדרך בעייתית.\n",
+ " דעה פופולרית אחרת דוגלת בצמצום ההערות בקוד למינימום ההכרחי.
\n",
+ " אלו המצדדים בדעה זו משתדלים להמעיט ככל האפשר בהוספת הערות לקוד.
\n",
+ "
\n",
+ " טענותיהם של הנמנים עם אסכולת צמצום ההערות מגוונות יחסית:
\n",
+ "
\n",
+ " האמת, כרגיל, נמצאת איפשהו באמצע, ולא מתפקידה של מחברת זו להכריע בוויכוח הזה.
\n",
+ " כן נזכיר כמה כללים בסיסיים שפחות או יותר מקובלים על כולם:\n",
+ "
\n", + " ניזכר בפונקציה help, שמטרתה להציג לנו תיעוד בנוגע לערך מסוים בתוכנית:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "quote = \"So many books, so little time.\"\n", + "help(quote.upper)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " והיא תעבוד היטב, לשמחתנו, גם עבור סוג המשתנה str עצמו, אם כי בצורה טיפה פחות חיננית (פנו ערב כדי לקרוא את זה):\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "quote = \"So many books, so little time.\"\n", + "help(str)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " אם ננסה להגדיר פונקציה משלנו, העברתה כארגומנט לפונקציה help תחזיר לנו מידע לא מועיל בעליל: \n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def add(a, b):\n", + " return a + b\n", + "\n", + "\n", + "help(add)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אז מה עלינו לעשות כדי להוסיף תיעוד?
\n",
+ " מתברר שזה לא כזה מסובך. בסך הכול צריך להוסיף משהו שנקרא \"מחרוזת תיעוד\".\n",
+ "
\n", + " נוסיף לפונקציה שלנו מחרוזת תיעוד של שורה אחת (One-line Docstring) בצורה הבאה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def add(a, b):\n", + " \"\"\"Return the result of a + b.\"\"\"\n", + " return a + b" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בדוגמה האחרונה הוספנו לשורה הראשונה של גוף הפונקציה מחרוזת תיעוד (Docstring), שמתחילה ומסתיימת ב־3 מירכאות כפולות.
\n",
+ " אפשר להשתמש בסוג מירכאות אחר, אך 3 מירכאות זו המוסכמה ואנחנו נדבוק בה.
\n",
+ " בין המירכאות תיארנו בקצרה מה הפונקציה עושה.\n",
+ "
\n", + " כעת הפונקציה help תשתף איתנו פעולה, ונוכל לקבל את התיעוד על הפונקציה שכתבנו: \n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "help(add)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " נקודות חשובות בהקשר זה:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " ניקח לדוגמה פונקציה שמקבלת נתיב ומחזירה את חלקיו:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_parts(path):\n", + " current_part = \"\"\n", + " for char in self.fullpath:\n", + " if char in r\"\\/\":\n", + " yield current_part\n", + " current_part = \"\"\n", + " else:\n", + " current_part = current_part + char\n", + " if current_part != \"\":\n", + " yield current_part" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " בתור התחלה, נוסיף לפונקציה מחרוזת תיעוד של שורה אחת שמתארת בקצרה מה תכליתה.\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_parts(path):\n", + " \"\"\"Split the path, return each part separately.\"\"\"\n", + " current_part = \"\"\n", + " for char in self.fullpath:\n", + " if char in r\"\\/\":\n", + " yield current_part\n", + " current_part = \"\"\n", + " else:\n", + " current_part = current_part + char\n", + " if current_part != \"\":\n", + " yield current_part" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כדי להפוך את התיעוד למרובה שורות, נוסיף שורה ריקה אחרי השורה שמתארת בקצרה מה עושה הפונקציה.
\n",
+ " אחרי השורה הריקה נוסיף תיאור כללי יותר שמסביר מה אפשר לצפות שהפונקציה תחזיר, ומה הפרמטרים שהיא מצפה לקבל.
\n",
+ " המירכאות הסוגרות יזכו בשורה משלהן:\n",
+ "
\n",
+ " סוג כזה של תיעוד נקרא \"מחרוזת תיעוד מרובת שורות\" (Multi-line Docstring).
\n",
+ " נכתוב אותו כדי לעזור למי שישתמש בפונקציה להבין מה מטרתה, אילו פרמטרים היא מצפה לקבל ואילו ערכים יוחזרו ממנה.\n",
+ "
\n", + " היכן נשתמש בתיעוד מרובה שורות?\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__init__
.\n",
+ " כאשר אנחנו מוסיפים לישות מסוימת מחרוזת תיעוד, נוספת לה תכונת קסם בשם __doc__
שמכילה את התיעוד שלה.
\n",
+ " התוכן של התכונה הזו הוא זה שמודפס כאשר אנחנו מפעילים את הפונקציה help.
\n",
+ " נבחן לדוגמה את תוכן התכונה __doc__
של quote.upper שסקרנו בתחילת המחברת.
\n",
+ " אפשר לראות שהיא זהה לחלוטין למחרוזת שקיבלנו כשהפעלנו עליה help:\n",
+ "
\n",
+ " ומה קורה כשניצור פונקציה משלנו?
\n",
+ " ננסה ליצור לדוגמה את ידידתנו הוותיקה, הפונקציה add:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def add(a, b):\n",
+ " return a + b"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ " כיוון שלא הוספנו לפונקציה תיעוד, התכונה __doc__
תוגדר כ־None
:\n",
+ "
\n", + " נוסיף תיעוד ונראה את השינוי:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def add(a, b):\n", + " \"\"\"Return the result of a + b.\"\"\"\n", + " return a + b\n", + "\n", + "print(add.__doc__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " מדובר בידע כללי מגניב, אבל כאנשים מתורבתים לעולם לא נבצע השמה ישירה ל־__doc__
ולא ניגש אליה ישירות.\n",
+ "
\n",
+ " עם הזמן ראתה קהילת המפתחים בפייתון כי טוב, וחשבה על דרך נעימה ונוחה יותר לקרוא תיעוד ולכתוב אותו.
\n",
+ " כיוון שתיעוד הוא חלק חשוב בקוד, התפתחו במרוצת השנים טכנולוגיות ותקנים שמטרתם לסייע למפתחי פייתון לתעד טוב יותר את הקוד שלהם.\n",
+ "
\n",
+ " תחילה, קהילת פייתון חיפשה דרך לכתוב משהו שהוא יותר מ\"סתם מלל\".
\n",
+ " לעיתים קרובות נרצה להדגיש דברים בתיעוד, לתת קישור למקור חיצוני, להוסיף כותרת או לציין פריטים ברשימה.
\n",
+ " שפת הסימון המוכרת HTML שמשומשת תדיר ליצירת דפי אינטרנט הייתה כבר קיימת, אבל בפייתון חיפשו שפה נקייה יותר שנוח לעין לסרוק.\n",
+ "
\n",
+ " לצורך כך פותחה בשנת 2002 שפת הסימון reStructuredText (או בקיצור – reST), שמאפשרת להפוך מלל שאנחנו כותבים לטקסט מסוגנן.
\n",
+ " מטרתה העיקרית של reST הייתה לאפשר הכנסת טקסט מסוגנן לתיעוד טכני בפייתון, ופייתון אכן אימצה אותה רשמית לצורך זה.
\n",
+ " היא מאפשרת לכתוב לא רק תיעוד בקוד, אלא גם תיעוד מילולי כללי על מטרותיו של הפרויקט ועל דרך השימוש בו (כמו ספר תיעוד קטן).\n",
+ "
\n", + " קצרה היריעה מלכלול פה הדרכה על שימוש ב־reStructuredText, אבל אנחנו ממליצים בחום לקרוא את המדריך המקוצר שנמצא כאן.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " מלל שנכתב ב־reStructuredText ייראה כך עבור מי שכתב אותו:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "אפשר לראות בטקסט *הזה* טעימה קטנה מהיכולות של **reStructuredText**.\n", + "חלק מהאפשרויות הפחות מתוחכמות שלו כוללות:\n", + "\n", + "* הדגשה, מלל מוטה וקו תחתון.\n", + "* רשימות.\n", + "* סימון של קוד, כמו `print(\"Hello World\")`.\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + " וייראה כך בתוצאה הסופית:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ "אפשר לראות בטקסט הזה טעימה קטנה מהיכולות של reStructuredText.
\n",
+ "חלק מהאפשרויות הפחות מתוחכמות שלו כוללות:\n",
+ "
print(\"Hello World\")
.\n",
+ " בשנת 2008 פותח בקהילת הפייתון כלי בשם Sphinx.
\n",
+ " מטרתו לסרוק את התיעוד של פרויקט הקוד שלכם, וליצור ממנו מסמך תיעוד שנעים לקרוא ב־PDF או ב־HTML.
\n",
+ " Sphinx, כמובן, תומך במסמכים שנכתבו ב־reStructuredText, והוא הפך במהרה לפופולרי מאוד.
\n",
+ " אתר התיעוד הנוכחי של פייתון נוצר באמצעותו.\n",
+ "
\n",
+ " בהמשך הקורס נכתוב פרויקטים, ובהם נוכל להשתמש ב־Sphinx ליצירת מסמכים שיעזרו למשתמשים בפרויקט להתמצא בו.
\n",
+ " משתמשים בולטים ב־Sphinx כוללים, בין השאר, את:\n",
+ "
\n",
+ " בשנת 2010 פותח אתר בשם Read the Docs, שמטרתו לרכז תיעוד לפרויקטים שנכתבו בפייתון.
\n",
+ " האתר מאפשר להעלות לרשת בקלות תיעודים שנוצרו בעזרת Sphinx ולהנגיש אותם לקהל הרחב.\n",
+ "
\n", + " משתמשים בולטים ב־Read the Docs כוללים, בין השאר, את:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " במשך שנים חיפשו מפתחי פייתון דרך אחידה יותר לכתוב מחרוזות תיעוד.
\n",
+ " בכלל, מתכנתים אוהבים שלדברים יש צורה מוסכמת ומוגדרת מראש.
\n",
+ "
\n",
+ " בעקבות הצורך הזה, התחילו להתפתח סגנונות שמגדירים בצורה הדוקה יותר כיצד אמור להיראות התוכן של מחרוזת תיעוד.
\n",
+ " למה זה טוב, אתם שואלים? כי כשלדברים יש תקן אחיד אפשר לעשות הרבה דברים מגניבים:\n",
+ "
\n",
+ " מובן שכמפתחים לא באמת הצלחנו לוותר על הוויכוחים האינטרנטיים, ולכן הנקודה האחרונה לא תקפה.
\n",
+ " עם הזמן התגבשו שלושה סגנונות פופולריים ל\"איך אמורה להראות מחוזת תיעוד\".\n",
+ "
\n",
+ " נבחן את ההבדלים בין הסגנונות, ונשאיר לכם לבחור באיזה סגנון תעדיפו להשתמש.
\n",
+ " לפניכם קוד נחמד וקצרצר שאנחנו הולכים לתעד בשארית המחברת, בכל אחד מהסגנונות הללו.
\n",
+ " בקוד נגדיר מחלקה של סניף דואר, שתאפשר למשתמשים בה לשלוח הודעות זה לזה.
\n",
+ " ללא תיעוד כלל, המחלקה תיראה כך:\n",
+ "
\n", + " וכך תיראה דוגמה לפונקציה מתועדת שמדגימה כיצד המחלקה עובדת:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def show_example():\n", + " \"\"\"Show example of using the PostOffice class.\"\"\"\n", + " users = ('Newman', 'Mr. Peanutbutter')\n", + " post_office = PostOffice(users)\n", + " message_id = post_office.send_message(\n", + " sender='Mr. Peanutbutter',\n", + " recipient='Newman',\n", + " message_body='Hello, Newman.',\n", + " )\n", + " print(f\"Successfuly sent message number {message_id}.\")\n", + " print(post_office.boxes['Newman'])\n", + "\n", + "\n", + "show_example()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " מבולבלים? איזה מזל שאנחנו הולכים לתעד את המחלקה הזו.
\n",
+ " קדימה, לעבודה.\n",
+ "
\n",
+ " גוגל מתחזקים זה שנים רבות מסמך מוסכמות ארוך משלהם שמפרט את סגנון הכתיבה הפנימי הרצוי בפייתון בחברה.
\n",
+ " במסמך, בין היתר, מתארים גוגל כיצד הם מאמינים שצריכות להיראות מחרוזות תיעוד.\n",
+ "
\n",
+ " תוכלו לראות דוגמה לאופן שבו אמורות להיראות מחרוזות התיעוד של Google כאן.
\n",
+ " נראה דוגמה לתיעוד המחלקה PostOffice ופעולותיה בשיטה של גוגל, ומייד אחר כך ננתח מה ראינו.\n",
+ "
\n", + " לפי מסמך הסגנון של גוגל, לפעולה צריכים להיות 3 חלקי תיעוד:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " יש לתעד גם מחלקות כמובן:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__init__
של המחלקה.\n",
+ " NumPy היא המודול המוביל בפייתון בכל הקשור לכלים מתמטיים.
\n",
+ " שיטת התיעוד שלו די דומה לזו של גוגל, ומתועדת כאן.
\n",
+ " היא מעט קריאה יותר לעין אנושית, אך משתמשת ביותר שטח לאורך הדף:\n",
+ "
\n",
+ " מעבר להיותו כלי ליצירת מסמכי תיעוד, ב־Sphinx קיימת גם הגדרה לצורה שבה לדעתם מחרוזות תיעוד אמורות להיראות.
\n",
+ " בלי לחץ – Sphinx ידע להמיר את התיעוד שלכם למסמך גם אם תשתמשו ב־Google Docstrings או ב־NumPy Docstrings.
\n",
+ " סגנון זה תופס את השטח המזערי ביותר לאורך הדף, אך הוא מעט קשה יותר לקריאה.
\n",
+ " מחרוזות התיעוד ש־Sphinx מגדירים נראות כך:\n",
+ "
\n",
+ " למדנו על ההבדל בין הערות לבין תיעוד, על הגישות השונות אליהם ומתי להשתמש בכל אחד מהם.
\n",
+ " סקרנו קצת את התפתחות רעיון התיעוד לאורך השנים בפייתון, ואת הכלים המשמשים את הקהילה בתהליך יצירת התיעוד.
\n",
+ " למדנו גם על סגנונות תיעוד פופולריים, שיאפשרו לכם לכתוב תיעוד קריא בקוד שלכם, ובעתיד גם ליצור מסמכי תיעוד בעצמכם.\n",
+ "
\n", + " ממשו שתי פעולות נוספות למחלקת PostOffice:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " חלק ממטרת התרגיל היא תרגול היכולת שלכם להיכנס לקוד קיים.
\n",
+ " נסו לשנות את הקוד הקיים במידה, והסבירו את השינויים שלכם אם לדעתכם עולה צורך כזה.
\n",
+ " ודאו שהקוד שלכם מתועד היטב.\n",
+ "
\n",
+ " ממשו מחלקה בשם Player שיודעת לקבל שם שחקן, וליצור שחקן חדש.
\n",
+ " לכל שחקן יש לפחות את התכונות הבאות:\n",
+ "
\n",
+ " אם בעקבות פעולת התקפה שחקן מסוים הגיע ל־0 חיים או פחות מכך, הוא נחשב למת ברובו.
\n",
+ " דאגו שהשחקן יעבור החייאה וצרפו את מי שהתקיף אותו לרשימת ה־nemeses של השחקן.\n",
+ "
\n", + " תעדו את התוכנית שלכם היטב.\n", + "
" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/content/week07/3_Classes_Part_2.ipynb b/content/week07/3_Classes_Part_2.ipynb new file mode 100644 index 0000000..0f12784 --- /dev/null +++ b/content/week07/3_Classes_Part_2.ipynb @@ -0,0 +1,2217 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בשיעור הקודם בנושא מחלקות למדנו שמחלקה היא תבנית (\"שבלונה\") המאגדת תכונות ופעולות של ישות כלשהי.
\n",
+ " למדנו גם מה הוא מופע – התוצר של השימוש באותה שבלונה, באותה מחלקה, כדי ליצור פריט לפי השבלונה הזו.\n",
+ "
\n", + " לדוגמה, מחלקת \"קבוצת ווטסאפ\" יכולה להיות מוגדרת כך:\n", + "
\n", + "\n", + "\n",
+ " קריאה למחלקת \"קבוצת ווטסאפ\" תיצור מופע חדש של קבוצת ווטסאפ, שבו יש את התכונות והפעולות שהוגדרו במחלקה.
\n",
+ " קריאה למחלקה 5 פעמים תיצור 5 קבוצות ווטסאפ שונות, שלכל אחת מהן התכונות והפעולות שהוגדרו במחלקה.\n",
+ "
\n",
+ " במסעדת \"זלוברי\" ליד כל רכיב בתפריט רשומים ערכיו התזונתיים ל־100 גרם.
\n",
+ " מנהלת המסעדה קיפף אוסין שכרה את שירותינו וביקשה מאיתנו לכתוב מחלקה המייצגת רכיב במנה.\n",
+ "
\n", + " לכל רכיב יש:\n", + "
\n", + "\n", + "\n", + " להזכירכם, דרך להערכה מהירה של מספר הקלוריות שיש ברכיב, היא זו:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " נסו לממש בעצמכם את מחלקת Ingredient לפי התכונות והפעולות שתוארו למעלה.\n", + "
\n", + "\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n", + " מימוש של המחלקה ייראה כך:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class Ingredient:\n", + " def __init__(self, name, carbs, fats, proteins):\n", + " self.name = name\n", + " self.carbs = carbs\n", + " self.fats = fats\n", + " self.proteins = proteins\n", + " \n", + " def calculate_calories(self):\n", + " return (\n", + " self.carbs * 4\n", + " + self.fats * 9\n", + " + self.proteins * 4\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " ושימוש בה ייראה כך:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mango = Ingredient('Mango', carbs=15, fats=0.4, proteins=0.8)\n", + "mango.calculate_calories()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אוסין מגדירה רכיב כ\"בריא\" אם מספר הקלוריות שבו קטן מ־100 עבור 100 גרם.
\n",
+ " נממש את הפעולה is_healthy
שמחזירה True
או False
:\n",
+ "
\n",
+ " שימוש במספרים קבועים בקוד שלנו נחשב לרוב ללא מנומס, ותמיד נעדיף לתת להם שם.
\n",
+ " במקרה הזה, יש סיכוי שמסעדת \"זלוברי\" תשנה את הגדרתה עבור רכיבים דיאטטיים בעתיד, ועדיף שהמספר \"100\" לא יפוזר לאורך הקוד.
\n",
+ "
\n",
+ " פתרון אפשרי הוא להגדיר משתנה בראש הקוד, שייצג את מספר הקלוריות המרבי למאכל דיאטטי.
\n",
+ " שימו לב שנהוג לכתוב את שמם של משתנים קבועים, אלו שערכיהם ישארו קבועים לכל אורך התוכנית, באותיות גדולות:\n",
+ "
\n",
+ " הפתרון הזה עובד, אך מפספס עיקרון חשוב שהצגנו במחברת הקודמת שעסקה במחלקות.
\n",
+ " מחלקה אמורה לאגד את כל המידע הקשור אליה, וזה הדין גם בנוגע למחלקת \"רכיב\", שאמורה לכלול את כל המידע הרלוונטי אליה.\n",
+ "
\n", + " אפשרות נוספת היא להגדיר את הערך כתכונה של כל מופע חדש שניצור:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class Ingredient:\n", + " def __init__(self, name, carbs, fats, proteins):\n", + " self.name = name\n", + " self.carbs = carbs\n", + " self.fats = fats\n", + " self.proteins = proteins\n", + " self.HEALTHY_CALORIES_UPPER_BOUND = 100\n", + "\n", + " def calculate_calories(self):\n", + " return (\n", + " self.carbs * 4\n", + " + self.fats * 9\n", + " + self.proteins * 4\n", + " )\n", + "\n", + " def is_healthy(self):\n", + " return self.calculate_calories() < self.HEALTHY_CALORIES_UPPER_BOUND" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " מדובר בבחירה מעט בזבזנית, כיוון שאם יהיו לנו מיליון רכיבים נצטרך ליצור במיליון מופעים את HEALTHY_CALORIES_UPPER_BOUND.
\n",
+ " יתרה מכך, אם נחליט לשנות את HEALTHY_CALORIES_UPPER_BOUND נצטרך לפנות לכל מיליון המופעים הקיימים ולשנות בהם את הערך.\n",
+ "
\n", + " אפשרות טובה יותר, שבה טרם נתקלנו, היא להגדיר את המשתנה ברמת המחלקה עצמה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class Ingredient:\n", + " HEALTHY_CALORIES_UPPER_BOUND = 100\n", + "\n", + " def __init__(self, name, carbs, fats, proteins):\n", + " self.name = name\n", + " self.carbs = carbs\n", + " self.fats = fats\n", + " self.proteins = proteins\n", + "\n", + " def calculate_calories(self):\n", + " return (\n", + " self.carbs * 4\n", + " + self.fats * 9\n", + " + self.proteins * 4\n", + " )\n", + "\n", + " def is_healthy(self):\n", + " return self.calculate_calories() < self.HEALTHY_CALORIES_UPPER_BOUND" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " מה קרה בקוד שלמעלה?
\n",
+ " הגדרנו את HEALTHY_CALORIES_UPPER_BOUND באותה רמה היררכית של הגדרת הפעולות במחלקה.
\n",
+ " הגדרה של משתנה ברמת המחלקה גורמת לו להיות משותף לכל המופעים שייווצרו מאותה מחלקה.
\n",
+ " בשלב זה כל מופע יכול לגשת לערך HEALTHY_CALORIES_UPPER_BOUND כאילו הוא חלק מתכונות המופע.\n",
+ "
\n",
+ " בתא האחרון יצרנו שני מופעים חדשים של רכיבים – הראשון של בננה והשני של מלון.
\n",
+ " הצלחנו לגשת למשתנה המחלקה HEALTHY_CALORIES_UPPER_BOUND שמשותף לשניהם דרך המופע שלהם.\n",
+ "
\n",
+ " קריאה ל־banana.is_healthy()
תעביר את המופע banana לפרמטר self של הפעולה is_healthy.
\n",
+ " הביטוי self.HEALTHY_CALORIES_UPPER_BOUND
יאפשר לנו להשיג את משתנה המחלקה, כיוון שאל self הועבר המופע עצמו:\n",
+ "
\n", + " הטריק הזה נקרא \"משתני מחלקה\" והוא מועיל ונפוץ, בעיקר עבור הגדרת ערכים קבועים המשותפים לכל המופעים שייווצרו מהמחלקה.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### תרגיל ביניים: חללר" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בעקבות הצלחתכם בחברת צ'יקצ'וק, פנה אליכם היזם המוכר אלון מסכה וביקש מכם להצטרף לחברתו \"חללר\".
\n",
+ " אלון מעוניין לבנות גשושית שיכולה לנוע במהירות האור, והוא מעוניין בעזרתכם בבניית סימולציה של גשושית שכזו.\n",
+ "
\n",
+ " תכונות הגשושית (SpaceProbe) יהיו זמן השיגור שלה ומהירות הטיסה הממוצעת של הגשושית בקמ\"ש.
\n",
+ " לכל מופע של גשושית תהיה גם הפעולה get_distance שתחשב מה המרחק שהחללית עברה מנקודת השיגור שלה.
\n",
+ " אפשר לחשב זאת על ידי מציאת מספר השעות שעברו מרגע השיגור, והכפלתו במהירות הממוצעת של החללית.\n",
+ "
\n",
+ " כדי שהסימולציה תדמה מצב מציאותי, ביקש אלון להגביל את מהירות הגשושית.
\n",
+ " אם המהירות הממוצעת שהוגדרה לגשושית קטנה מ־0 – הגדירו אותה כ־0. אם היא גדולה ממהירות האור – הגדירו אותה כמהירות האור.\n",
+ "
\n",
+ " כדי להבין טוב יותר איך פייתון מתנהגת, חשוב להבין שהמחלקה Ingredient גם היא ערך לכל דבר.
\n",
+ " בדיוק כמו פונקציות (או כל ערך אחר), אפשר לבצע השמה שלה למשתנה:\n",
+ "
\n",
+ " בדוגמה האחרונה ביצענו השמה, וגרמנו למשתנה SomethingINeedForMyRecipe להצביע על המחלקה Ingredient.
\n",
+ " שימו לב שלא הפעלנו את ה־__init__
של Ingredient, אלא השתמשנו בה כערך – ציינו את שמה ללא הסוגריים.
\n",
+ " בשלב הזה SomethingINeedForMyRecipe ו־Ingredient הצביעו לאותו מקום, ולכן אפשר היה ליצור רכיבים בעזרת SomethingINeedForMyRecipe.\n",
+ "
\n", + " הדפסה של המשתנה SomethingINeedForMyRecipe תגלה לנו שמדובר במחלקה המקורית, Ingredient: \n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(SomethingINeedForMyRecipe)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " וכך גם בדיקת הסוג של המופע honey, שנוצר מהקריאה ל־SomethingINeedForMyRecipe:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "type(honey)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אפשר לקחת את האמירה שמחלקות הן כמו כל ערך אחר צעד אחד קדימה.
\n",
+ " ניצור שתי מחלקות, ונכניס את המחלקות עצמן לתוך רשימה:\n",
+ "
\n", + " מה התרחש בקוד שלמעלה?\n", + "
\n", + "\n", + "for
.\n",
+ " בשיעור על פונקציות בשבוע שעבר למדנו שפונקציות הן אזרחיות ממדרגה ראשונה – כלומר, הן ערכים לכל דבר.
\n",
+ " מהדוגמה האחרונה אפשר לראות שגם מחלקות בפייתון, בדומה לפונקציות, הן אזרחיות ממדרגה ראשונה.
\n",
+ " המשמעות היא שהן ערך לכל דבר, כמו מספרים ומחרוזות: אפשר להכניס אותן לרשימה, להעביר אותן כפרמטר ולאחסן אותן במשתנים.\n",
+ "
\n",
+ " נחדד פעם נוספת את ההבדלים בין מופע למחלקה.
\n",
+ " נביט במחלקה Ingredient שמייצגת רכיב במסעדה.
\n",
+ " בעזרת קריאה למחלקה (השבלונה, התבנית) Ingredient נוכל ליצור מופעים חדשים של רכיבים.\n",
+ "
\n", + " כך ניצור מחלקה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class Ingredient:\n", + " HEALTHY_CALORIES_UPPER_BOUND = 100\n", + "\n", + " def __init__(self, name, carbs, fats, proteins):\n", + " self.name = name\n", + " self.carbs = carbs\n", + " self.fats = fats\n", + " self.proteins = proteins\n", + "\n", + " def calculate_calories(self):\n", + " return (\n", + " self.carbs * 4\n", + " + self.fats * 9\n", + " + self.proteins * 4\n", + " )\n", + "\n", + " def is_healthy(self):\n", + " return self.calculate_calories() < self.HEALTHY_CALORIES_UPPER_BOUND" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " וכך ניצור מופע:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cinnamon = Ingredient('Cinnamon', carbs=81, fats=1.2, proteins=4)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כפי שראינו לפני כן, מחלקה היא ערך לכל דבר.
\n",
+ " מעבר לכך, ראינו שאפשר להגדיר משתנים השייכים למחלקה.\n",
+ "
\n",
+ " אפשר לגשת לאותם משתנים שלא דרך מופע מסוים, בעזרת שימוש בשם המחלקה.
\n",
+ " נוכל, לדוגמה, להשיג את הערך שנמצא ב־HEALTHY_CALORIES_UPPER_BOUND כך:\n",
+ "
\n",
+ " בשורה למעלה ציינו את שם המחלקה עצמה, כתבנו נקודה, ומייד אחריה התייחסנו לאחד המשתנים המוגדרים בה.
\n",
+ " הגדרת קבועים במחלקה והתייחסות אליהם מבחוץ הוא רעיון די נפוץ שנהוג להשתמש בו לא מעט בתכנות.\n",
+ "
\n", + " אם נרצה, נוכל גם לבצע השמה למשתנה המחלקה מבחוץ, ולשנות אותו עבור כל המופעים שמתייחסים אליו:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"healthy if calories < 100: {cinnamon.is_healthy()}\")\n", + "Ingredient.HEALTHY_CALORIES_UPPER_BOUND = 400\n", + "print(f\"healthy if calories < 400: {cinnamon.is_healthy()}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בניגוד לגישה למשתנה המחלקה בצורה שהוצגה למעלה, לא נוכל לגשת באותו אופן לתכונה Ingredient.name
.
\n",
+ " הרי אין בכך היגיון – התכונה name היא תכונה שמוגדרת עבור כל מופע, ולא עבור המחלקה כולה:\n",
+ "
\n", + " ומה יקרה אם ננסה לפנות דרך המחלקה עצמה לפעולה שהוגדרה ברמת המחלקה?\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Ingredient.is_healthy()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כמובן!
\n",
+ " הפעולה is_healthy מצפה ל־self, מופע כלשהו של המחלקה, שבדרך כלל מועבר לה כשאנחנו קוראים לה בעזרת המופע:\n",
+ "
\n",
+ " בתא למעלה, כפי שלמדנו בפרק הקודם על מחלקות, cinnamon נשלח לפעולה is_healthy בתור הארגומנט self.
\n",
+ "
\n",
+ " כדי לחקות את אותה ההתנהגות עבור הקריאה Ingredient.is_healthy()
שמצפה לקבל self כפרמטר,
\n",
+ " נעביר את cinnamon כארגומנט לפעולה, וזה יגרום לה להיכנס לפרמטר self:\n",
+ "
\n",
+ " שימוש בפעולות בצורה הזו כרגע הוא יותר בבחינת ידע כללי שאמור לעזור לכם להבין איך המחלקה מתנהגת מאחורי הקלעים.
\n",
+ " בהמשך הקורס נבין טוב יותר מהם השימושיים האפשריים בפנייה לפעולות דרך המחלקה עצמה ולא דרך מופע.\n",
+ "
\n",
+ " לאור ההצלחה המסחררת של מחלקת \"רכיב\", ביקשה מאיתנו מנהלת המסעדה קיפף אוסין לתכנת מחלקה בשם Dish שתייצג מנה במסעדה.
\n",
+ " לכל מנה יש שם (name), סימון אם היא צמחונית (is_vegetarian) ורשימת רכיבים (ingredients).
\n",
+ " הפעולה get_total_calories תחזיר את סכום הקלוריות של רכיבי המנה.\n",
+ "
\n",
+ " ממשו בעצמכם את מחלקת Dish.
\n",
+ " לעת עתה, התעלמו מהכמות של כל רכיב במנה.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " חלק מהכיף האמיתי בתכנות מחלקות מסתתר בכתיבת האינטרקציה ביניהן.
\n",
+ " לכן התרגיל למעלה הוא מעניין – הוא משלב את המחלקה הקודמת שבנינו, Ingredient, עם המחלקה Dish.\n",
+ "
\n",
+ " כדי לייצג את הרכיבים בכל מנה נשתמש במופעי המחלקה Ingredient.
\n",
+ " נוכל לשמור רשימה של רכיבים כתכונה של מחלקת \"מנה\".
\n",
+ " במילים אחרות: לכל מנה יש רשימת רכיבים.\n",
+ "
\n", + " נממש:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class Dish:\n", + " \"\"\"Create a new dish for the restaurant using our ingredients.\n", + " \n", + " Args:\n", + " name (str): The name of the dish.\n", + " is_vegetarian (bool): `True` if the dish is vegetarian.\n", + " ingredients (list of Ingredient): All the required ingredients\n", + " for the dish.\n", + "\n", + " Attributes:\n", + " name (str): The name of the dish.\n", + " is_vegetarian (bool): `True` if the dish is vegetarian.\n", + " ingredients (list of Ingredient): All the ingredients that are\n", + " required to prepare the dish.\n", + " \"\"\"\n", + " def __init__(self, name, is_vegetarian, ingredients):\n", + " self.name = name\n", + " self.is_vegetarian = is_vegetarian\n", + " self.ingredients = ingredients\n", + " \n", + " def get_total_calories(self):\n", + " \"\"\"Calculate calories based on the list of the ingredients.\"\"\"\n", + " calories = 0\n", + " for ingredient in self.ingredients:\n", + " calories = calories + ingredient.calculate_calories()\n", + " return calories\n", + " \n", + " def __str__(self):\n", + " calories = self.get_total_calories()\n", + " return f\"{self.name} has {calories:.7} calories in it.\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בקוד שלמעלה הגדרנו פעולת __init__
שתקבל את שם המנה, משתנה בוליאני שקובע אם היא צמחונית או לא ורשימת רכיבים.
\n",
+ " הפעולה get_total_calories תעבור על הרכיבים ותסכום את מספרי הקלוריות של כל אחד מהם.\n",
+ "
\n",
+ " נבדוק שהמחלקה עובדת.
\n",
+ " ניצור רשימת רכיבים ונעביר אותה ל־Dish כדי לייצר מופע של מנה:\n",
+ "
\n",
+ " העיקרון של שימוש במופעי מחלקה אחת בתוך מחלקה אחרת נקרא הכלה (Containment).
\n",
+ " זוהי טכניקה שימושית ונפוצה מאוד בתכנות מחלקות, ותשמש אותנו רבות ביום־יום כמתכנתים.\n",
+ "
\n",
+ " מנהל הדואר הידוע והאהוב קיפיק הצב אהב את מחלקת PostOffice שבניתם.
\n",
+ " מרוב התלהבות, הוא מעוניין שגם ההודעות עצמן ייוצגו בעזרת מחלקה.\n",
+ "
\n",
+ " צרו את מחלקת Message והחליטו אילו תכונות כדאי שיהיו לה.
\n",
+ " ממשו פעולת __str__
שתציג את ההודעה בצורה נאה.
\n",
+ " הרצת הפונקציה len על מופע של הודעה יחזיר את אורך ההודעה (ללא הכותרת).\n",
+ "
\n",
+ " ודאו שהתאמתם את פעולות מחלקת PostOffice כך שיפעלו גם על Message.
\n",
+ " אם עולה הצורך, קראו באינטרנט על פעולות קסם.\n",
+ "
\n",
+ " מחלקת Dish שכתבנו שינתה את עולם המסעדנות כולו, ואין מתכנת מסעדן שלא משתמש בה.
\n",
+ " הגברת קיפף אוסין משתמשת בה במסעדת הדגל שלה \"מסון קייפי\" ומעוניינת להשתמש בה במסעדה החדשה שלה, \"7 טעמים\".\n",
+ "
\n", + " במסעדת 7 טעמים אין אף מנה שמורכבת מיותר מ־7 רכיבים, והגברת אוסין מבקשת מאיתנו לעזור לה לאכוף זאת במחלקה.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " נסו לחשוב בעצמכם איך אתם הייתם פותרים את הבעיה.
\n",
+ " כיצד תשפרו את מחלקת Dish כך שתגביל את המשתמשים בה לעד 7 רכיבים?\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " ישנם הרבה פתרונות יצירתיים ומעניינים שיכולים לשמש אותנו במקרה שסופקו יותר מ־7 רכיבים.
\n",
+ " אפשר, לדוגמה, לקחת רק את 7 הרכיבים הראשונים שאנחנו מקבלים, או להשאיר את רשימת הרכיבים ריקה.\n",
+ "
\n",
+ " אחת הבחירות האפשריות היא לא לקבל מלכתחילה בפעולת האתחול __init__
את רשימת הרכיבים.
\n",
+ " במקרה כזה נצטרך לספק למשתמש דרך נוחה להוסיף רכיבים לתכונה ingredients ולהוריד רכיבים ממנה.
\n",
+ " נאתחל את התכונה לרשימה ריקה, ונוסיף ל־Dish את הפעולות add_ingredient ו־remove_ingredient.
\n",
+ " ברגע שהמשתמש במחלקה יקרא ל־add_ingredient כשיש במנה כבר 7 רכיבים, נכשיל את הוספת הרכיב העודף:\n",
+ "
\n",
+ " נבדוק שהמחלקה עובדת.
\n",
+ " לנוחיותכם, פירטנו מעל כל חלק מה יעשה הקוד.\n",
+ "
\n", + " אם ננסה להוציא את הסוכר, נגלה שהוא אכן לא ברשימת הרכיבים:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "great_cocktail.remove_ingredient(sugar)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " מזל טוב! הכל עובד וקיפף מרוצה, בינתיים.
\n",
+ " הכנתם לכולם Black Magic Julep כדי לחגוג את המאורע, לגמתם בשקיקה שני שלוקים ו...\n",
+ "
\n",
+ " \"יש בעיה חמורה במחלקה!\", מזדעק הסו־שף מפינת החדר. נראה שהוא טרם לגם מהמשקה שלו. הסו־שף מוסיף:
\n",
+ " \"משתמש שלא מכיר את המחלקה, יכול בטעות לערוך ישירות את התכונה ingredient השייכת למופע של המאכל שיצרנו.
\n",
+ " בכך הוא יעקוף את המגבלה שהצבנו בפעולה add_ingredient:\n",
+ "
\n",
+ " זה אולי נשמע מקרה קצה זניח, אבל אחת ההנחות שלנו כמתכנתים היא שמותר לשנות מאפייני מחלקה ללא חשש.
\n",
+ " במקרה כזה, הוא יוכל בהיסח הדעת להוסיף בשגגה סוכר כמרכיב שמיני לקוקטייל!\"\n",
+ "
\n",
+ " היסטריה המונית פורצת בחדר. עלי נענע ומטריות קוקטיילים מתעופפים לכל עבר.
\n",
+ " בתוך שניות ספורות אתם משתלטים על המצב, ואחרי דקה אתם כבר שקועים עמוק בקריאת מאמר שמדבר על תכנות מחלקות.\n",
+ "
\n",
+ " מתברר שאם מוסיפים את התו _
לפני שם התכונה, היא הופכת להיות תכונה \"מוגנת\" (protected).
\n",
+ " מצב כזה מסמן למתכנתים אחרים שהתכונה מיועדת לשימוש פנימי של המחלקה, ושאסור לגשת אליה מבחוץ.
\n",
+ " \"אין לפנות לתכונה כזו בקוד שכתוב מחוץ למחלקה\", נכתב במאמר. \"לא למטרות קריאה ולא למטרות כתיבה\".
\n",
+ "
\n",
+ " שיניתם את המחלקה. אף על פי שהשימוש בה נראה בדיוק אותו דבר, התכונה ingredients קיבלה קו תחתון לפני שמה.
\n",
+ " המחלקה המתוקנת שיצרתם נראית עכשיו כך:\n",
+ "
\n",
+ " לפעמים ניתקל במקרה שבו נרצה למנוע גישה ישירה לתכונות מסוימות של מחלקה מצד גורמים לא מורשים.
\n",
+ " פתיחת שם התכונה בתו _
וסימונה כמוגנת, או במילים אחרות מתאימה לשימוש רק בתוך המחלקה, יכולות לשרת אותנו להשגת המטרה הזו.
\n",
+ " זו מוסכמה חזקה מאוד בקרב מתכנתי פייתון: עשו מה שאפשר כדי לא לגשת לתכונות ששמן מתחיל בקו תחתון מתוך קוד שנמצא מחוץ למחלקה.\n",
+ "
\n",
+ " אם המשתמש במחלקה ירצה להשיג את ערכו של מופע או לשנות אותו, הוא יוכל לקרוא לפעולה שזו מטרתה.
\n",
+ " במקרה שלנו, הפעולות הללו הן add_ingredient או remove_ingredient.
\n",
+ " נוכל (וכדאי) שנממש פעולה לצפייה בתכונה, כמו get_ingredients.\n",
+ "
\n",
+ " הגישה של החבאת נתונים מאחורי פעולות היא רעיון פופולרי מאוד בתכנות מונחה עצמים.
\n",
+ " לפי רעיון שנקרא \"כימוס\" (Encapsulation), על מחלקה לאגד תכונות ופעולות, ולהסתיר מידע עודף מאלו שמשתמשים במחלקה.
\n",
+ " הסתרת המידע משרתת שתי מטרות:\n",
+ "
\n",
+ " ברוב שפות התכנות שאינן פייתון, שפת התכנות ממש מונעת גישה לתכונות מוגנות מתוך קוד שנמצא מחוץ למחלקה.
\n",
+ " בפייתון נהוג להגיד שכולנו מבוגרים בעלי שיקול דעת
, ולכן פייתון לא מונעת גישה לתכונות מוגנות.
\n",
+ "
\n",
+ " למרות זאת, בהקשר הזה יש לפייתון טריק מלוכלך בשרוול.
\n",
+ " אפשר להגדיר תכונות כפרטיות (private) בעזרת הקידומת __
(פעמיים קו תחתון).
\n",
+ " במקרה הזה, פייתון כן תתערב, וכן תנסה למנוע גישה לתכונה.
\n",
+ " נדגים בעזרת מחלקה פשוטה של משתמש:\n",
+ "
\n", + " ניצור דמות שנקראת פרנקלין:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "character = User(\"Franklin\", 200, \"Lacing shoes\")\n", + "print(character)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " וננסה לשנות לה את התכונות:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "character.name = \"Cookie Monster\" # עם זה אין שום בעיה\n", + "character._age = 54 # זה לא מנומס ומסוכן\n", + "character.__hobbies = \"Cookies\" # זה כבר ממש לא סבבה" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " אבל כשננסה להדפיס את Cookie Monster, נגלה שכל תכונות הדמות השתנו חוץ מהעובדה שהיא עדיין אוהבת לשרוך נעליים!\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(character)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " הסיבה לכך היא שכשפייתון רואה את התחילית __
היא מבינה שממש חשוב לכם שאף גורם חיצוני למחלקה לא ייגש לתכונה.
\n",
+ " אף על פי שניסיון פשוט לגשת לתכונה __hobbies מחוץ למחלקה לא יישא פרי, אם נחטט טוב נגלה שעדיין ישנה דרך לעשות זאת.
\n",
+ " מתברר שפייתון רק משנה את שם התכונה למשהו מעט מסובך יותר כדי \"להחביא\" אותה טוב יותר:\n",
+ "
\n",
+ " תכונה שמתחילה בתחילית __
בפייתון נקראת, כאמור, \"תכונה פרטית\" (private).
\n",
+ " גם בה מותר להשתמש רק בתוך המחלקה, רק שהפעם פייתון עושה טריק מלוכלך כדי למנוע מכם להשתמש בתכונה בכל זאת.
\n",
+ " פייתון משנה את שם התכונה לקו תחתון, שם המחלקה, שני קווים תחתונים ואז שם התכונה.
\n",
+ "
\n",
+ " השם המקצועי לשינוי שם משתנה כדי לפתור בעיית ארכיטקטורה הוא name mangling, והקיום שלו בפייתון הוא נושא טעון בקרב קהילת המפתחים בשפה.
\n",
+ " עקב כך, הקידומת __
היא אחת מהיכולות היותר שנויות במחלוקת בשפה ועדיף לא להשתמש בה, אלא אם כן ממש חייבים.
\n",
+ " כל עוד המקרה הוא לא קיצוני מאוד, העדיפו להגן על משתנה בעזרת קידומת של קו תחתון אחד.\n",
+ "
\n",
+ " אפשר להגדיר גם פעולות כמוגנות או כפרטיות אם נוסיף להן את התחילית _
או __
, בהתאמה.
\n",
+ " כיוון ש־can_add_ingredient מיועדת לשימוש פנימי, שנו את הפעולה כך שתוגדר כמוגנת.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " ישנם מקרים מיוחדים שטרם למדנו, שבהם אפשר לגשת לתכונות ולפעולות מוגנות ממחלקות אחרות.
\n",
+ " לעומתן, תכונות או פעולות שמוגדרות כפרטיות נגישות רק מתוך המחלקה שיצרה אותן.
\n",
+ " ההפרדה בין שני הרעיונות נפוצה בעיקר בשפות אחרות, ובפייתון נהוג להשתמש בתחילית _
(משתנה מוגן) בשני המקרים.
\n",
+ " מהסיבה הזו אנשים רבים יקראו לתכונה או לפעולה עם קידומת _
בפייתון \"תכונה פרטית\" או \"פעולה פרטית\".\n",
+ "
\n",
+ " כמו שראינו השבוע, פייתון מתירה כברירת מחדל גישה נוחה לתכונות של מופעים בעזרת קוד שנכתב מחוץ למחלקה.
\n",
+ " בניגוד לפייתון, בשפות אחרות לא נהוג לאפשר גישה ישירה לתכונות המחלקה.
\n",
+ " מתוך הרגל, מתכנתים שמגיעים משפות אחרות משתמשים לעיתים תכופות בהחבאת נתונים בצורה לא מידתית:\n",
+ "
\n",
+ " הרעיון בקוד שהוצג למעלה נקרא \"פעולות גישה ושינוי\" (accessor and mutator methods), או getters and setters.
\n",
+ " אלו פעולות שמטרתן עריכת תכונות מסוימות או אחזור של הערך הנוכחי שלהן, תוך כדי ניסיון למנוע מהמשתמש במחלקה לגשת ישירות לערך התכונה.
\n",
+ " המטרה של מתכנת שנוהג כך היא לדאוג שהוא תמיד יוכל לשלוט על ערכי התכונות, לוודא את תקינותם ולמנוע מופע שמכיל נתונים שאינם תקינים.\n",
+ "
\n",
+ " שימוש שכזה בפעולות גישה ושינוי הוא נדיר יחסית בפייתון.
\n",
+ " בזכות היכולת לגשת לתכונות של מופע ישירות מחוץ למחלקה, כתיבת מחלקות הופכת להיות מהירה ונעימה לשימוש.
\n",
+ " כתיבת getter ו־setter לכל תכונה עשויה לגרום למחלקות בפייתון להפוך למסורבלות וארוכות, והשימוש בהן נעשה לא נוח:\n",
+ "
\n", + " עקב הסרבול שבעניין, בפייתון לרוב נעדיף להשתמש בפעולות גישה ושינוי רק כשיש צורך ממשי בהגדרת תכונות כמוגנות או כפרטיות. \n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## מונחים" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בהמשך להצלחה המסחררת של אפליקציית צ'יקצ'וק, החלטנו להשיק את יישומון המסרים המיידיים \"מנשמעעעעעעע XPPP\".
\n",
+ " תחילה, ניצור מחלקה שתייצג משתמש.\n",
+ "
\n", + " לכל משתמש יש:\n", + "
\n", + "\n", + "\n", + " לכל הודעה יש:\n", + "
\n", + "\n", + "\n", + " למה כדאי לשים לב בפתרון?\n", + "
\n", + "\n", + "\n",
+ " במחלקת Player שבניתם, שנו את הפעולה attack.
\n",
+ " אם הפעולה לא מקבלת פרמטרים ואין לשחקן אויבים, היא תחזיר False במקום לזרוק IndexError.
\n",
+ " אם התקיפה הצליחה, הפעולה תחזיר True.\n",
+ "
\n",
+ " ממשו מחלקת זירה בשם Arena.
\n",
+ " פעולת האתחול של Arena מקבלת את מספר השחקנים המרבי שאפשר להכניס לזירה.\n",
+ "
\n", + " המחלקה תכיל לכל הפחות את התכונות הבאות:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "None
אם עדיין אין אחד כזה.\n", + " במחלקה ימומשו גם הפעולות הבאות:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " ודאו שכל זמן שיש שחקנים בזירה, next_player מצביעה למופע של שחקן.
\n",
+ " אם אין שחקנים בזירה, בעת אתחול המחלקה למשל, שנו את ערכה של next_player ל־None
.
\n",
+ "
\n", + " כאשר שחקן א גורם לשחקן ב להיות מת ברובו, שחקן א יקבל מספר נקודות ניסיון לפי הנוסחה הבאה:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "$$ EXP_{gain} = \\frac{L_{rival} \\cdot (2 \\cdot L_{rival} + 10)^{2.5}}{5 \\cdot (L_{rival} + L_{player} + 10) ^{2.5}} $$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כאשר $L_{rival}$ היא הרמה של המת ברובו (שחקן ב), ו־$L_{player}$ היא הרמה של המנצח (שחקן א).
\n",
+ " אם השחקן מת לחלוטין ולא רק מת ברובו, השחקן שניצח אותו מקבל פי 2 נקודות ניסיון.\n",
+ "
\n", + " מספר נקודות הניסיון שיש להשיג בסך הכול כדי להגיע לרמה $L$ הוא:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "$$ \\frac{4 \\cdot ({L-1})^{2.5}}{5} $$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### מתחילים לשחק" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " ממשו סימולציית קרב:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " הרגישו נוח להוסיף רכיבים לכל אחת מהמחלקות שלכם.
\n",
+ " תכנון נכון של המחלקות יאפשר לכם לצלוח את התרגיל בקלות רבה יותר.\n",
+ "
\n", + " בתרגיל זה נממש טיפוס חדש מסוג Rectangle, הבנוי משתי נקודות:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " פעולות המלבן הן:\n", + "
\n", + "\n", + "\n",
+ " הדפסת מופע של המלבן צריכה להיראות כך:
\n",
+ "
\n", + "### Rectangle ###\n", + "My Width: 10\n", + "My Height: 10\n", + "My Surface: 100\n", + "My Perimeter: 40\n", + "\n", + "
\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " נוסף על הפעולות שהוזכרו מעלה, כתבו פעולה של המחלקה בשם rand_rects המגרילה מספר בין 1 ל־50.
\n",
+ " צרו רשימה המכילה מספר מלבנים בהתאם למספר שהוגרל, ועבור כל מלבן הגרילו נקודות על ידי שימוש בפעולות המתאימות.
\n",
+ " את כל הצורות הנ\"ל הכניסו לרשימה, והדפיסו את כל הצורות ששטחן גדול מ־900 והיקפן גדול מ־30.
בחנות הקומדי יש פריטים (items), ולכל פריט יש מחיר המופיע במחירון שהחנות מחזיקה.
\n",
+ " החנות מחזיקה מלאי מכל מוצר, שחלקו מוצב על המדפים וחלקו במחסן.
\n",
+ " לחנות יש יתרת מזומנים (balance) שמתעדכנת בהתאם לקניות של הלקוחות ובהתאם לרכש שהחנות מבצעת עבור המלאי.
\n",
+ " כתבו מחלקה המייצגת את החנות ובה המתודות הבאות:
\n",
+ "
\n", + "בתרגיל זה נממש שלוש מחלקות: מחלקה של ריבוע, מחלקה של קוביות ומחלקה של מגדל קוביות.
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ריבוע " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " ממשו את מחלקת ריבוע, Square.
\n",
+ " פעולת האתחול של המחלקה תקבל פרמטר של אורך, שהוא אורך הצלע של הריבוע.
\n",
+ " כפי שוודאי ידוע לכם, בריבוע כל הצלעות זהות באורכן.
\n",
+ " ממשו גם את הפעולות get_surface ו־get_perimeter של שטח והיקף כפי שעשיתם בתרגיל הקודם.\n",
+ "
\n",
+ "לקובייה יש בסיס (base) שהוא ריבוע, וצבע (color) שהוא צבע הקובייה. הצבע יכול להיות שחור או לבן.
\n",
+ "ממשו את מחלקת הקובייה, ודאגו להוסיף לה פעולות לחישוב הנפח (get_volume) ושטח המעטפת (get_surface).
\n", + " הדפסת הקובייה צריכה להיראות כך:\n", + "
\n", + "\n", + "Cube: base-10x10 color:black\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### מגדל הקוביות " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ "מגדל הקוביות שלנו מכיל רשימת קוביות שמהן הוא מורכב. כשנאתחל את מגדל הקוביות הוא תמיד יכיל 0 קוביות.
\n",
+ "קובייה יכולה להיות מעל קובייה אחרת רק אם הקובייה התחתונה היא בעלת בסיס עם צלע ארוכה יותר, ורק אם צבעה שונה.\n",
+ "
\n", + "הדפסה של מגדל הקוביות תיראה כך:\n", + "
\n", + "\n",
+ "2-Cube: base-10x10 color:black
1-Cube: base-15x15 color:white\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n", + " ממשו מחלקה בשם CubeTower המייצגת את מגדל הקוביות.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " הוסיפו למחלקה פעולה בשם add_cube שמקבלת קובייה ומניחה אותה בראש המגדל אם התנאים שהוזכרו מתקיימים, אחרת לא עושה כלום.
\n",
+ " אם זו הקובייה הראשונה במגדל אפשר להניח אותה ללא שום תנאי.\n",
+ "
\n",
+ " הוסיפו פעולה נוספת בשם randomize_tower שבוחרת מספר שלם בין 1 ל־100, נקרא לו $N$.
\n",
+ " הפעולה תיצור $N$ קוביות שלהן צלע באורך אקראי (עליכם להגריל אורך בין 1 ל־100), שצבען הוא שחור או לבן (בחרו באקראיות).
\n",
+ "
\n",
+ " צרו בלולאה 1,000 מגדלים ומלאו אותם בקוביות באמצעות המתודה המגרילה.
\n",
+ " מהו המגדל הגבוה ביותר שקיבלתם?\n",
+ "
\n",
+ " ממשו משחק סט סטנדרטי.
\n",
+ " המשחק יציג למשתמש 12 קלפי סט מתוך חפיסה מעורבבת של 81 קלפים, ויאפשר למשתמש לבחור אילו 3 קלפים מייצגים סט.\n",
+ "
\n",
+ " אם בחר המשתמש סט תקין, החליפו את שלושת הקלפים שבחר בקלפים הבאים בחפיסה.
\n",
+ " אם בחר סט שגוי, הדפיסו הודעת שגיאה.
\n",
+ " אם אי אפשר להרכיב סט מהקלפים שנפתחו, החזירו את הקלפים לחפיסה, טרפו אותה ופתחו 12 קלפים חדשים.
\n",
+ " בכל שלב, אם אין בחפיסה די קלפים לפתיחה – פתחו את הקלפים שנותרו בחפיסה.
\n",
+ " השחקן ניצח כאשר אי אפשר ליצור יותר סטים מהקלפים הנמצאים בחפיסה.
\n",
+ " שימו לב שכאשר נותרו 21 קלפים, בהכרח ישנם 3 קלפים מתוכם שמרכיבים סט.\n",
+ "
\n", + " השתמשו במחלקות.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### חלק 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " בשבוע שעבר למדנו מה הן מחלקות, וסקרנו טכניקות שונות הנוגעות לכתיבת קוד בעזרתן.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " למדנו כיצד ליצור מחלקה, שהיא בעצם תבנית שמתארת את התכונות ואת הפעולות השייכות לכל מופע שנוצר ממנה.
\n",
+ " הסברנו מהי פעולת אתחול (__init__
), שרצה מייד עם יצירתו של מופע חדש, ודיברנו על פעולות קסם נוספות.
\n",
+ " ראינו כיצד מגדירים משתני מחלקה, כאלו שמשותפים לכל המופעים,
\n",
+ " ודיברנו גם על תכונות ופעולות פרטיות ומוגנות, טכניקה שמאפשרת לנו להחליט אילו תכונות ופעולות אנחנו חושפים למשתמשים שלנו.
\n",
+ " לסיום, דיברנו גם על רעיון ההכלה, שמאפשר לנו להשתמש במופעי מחלקה אחת בתוך מופעים של מחלקה אחרת. \n",
+ "
\n", + " השבוע נרחיב את ארגז הכלים שלנו, ונדבר על רעיונות נוספים שקשורים לעולם המחלקות.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### אתר השירים" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " השבוע נתמקד ביצירת אתר השירים החדש והנוצץ \"שירומת\".
\n",
+ " נתחיל בכתיבת מחלקה המייצגת שיר עבור האתר.
\n",
+ "
\n",
+ " לכל שיר יש שם, מילים ורשימת יוצרים.
\n",
+ " כדי לנהל את רשימת היוצרים, צרו את הפעולות add_artist ו־remove_artist שיוסיפו ויסירו אומנים בהתאמה.
\n",
+ " לשיר תהיה גם פעולה שנקראת count_words שתחזיר את מספר המילים בשיר.
\n",
+ " כמו כן, לכל שיר יהיה מונה שיספור כמה פעמים הוא הודפס.
\n",
+ "
\n", + " הדפסת שיר תיראה כך:\n", + "
" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "\"Her Majesty's\" / The Beatles\n", + "-----------------------------\n", + "Her Majesty's a pretty nice girl\n", + "But she doesn't have a lot to say\n", + "Her Majesty's a pretty nice girl\n", + "But she changes from day to day\n", + "\n", + "I want to tell her that I love her a lot\n", + "But I gotta get a bellyful of wine\n", + "Her Majesty's a pretty nice girl\n", + "Someday I'm going to make her mine, oh yeah\n", + "Someday I'm going to make her mine\n", + "-----------------------------\n", + "Seen: 1 time(s)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " נסו לממש את מחלקת השיר בעצמכם. \n", + "
\n", + "\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n", + " נציג את הפתרון, ונסביר את המימוש מייד לאחר מכן:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class Song:\n", + " \"\"\"Represent a Song in our lyrics site.\n", + "\n", + " Parameters\n", + " ----------\n", + " name : str\n", + " The name of the song.\n", + " lyrics : str\n", + " The lyrics of the song.\n", + " artists : list of str or str, optional\n", + " Can be either a list, or a string separated by commas.\n", + "\n", + " Attributes\n", + " ----------\n", + " name : str\n", + " The name of the song.\n", + " lyrics : str\n", + " The lyrics of the song.\n", + " _views : int\n", + " Views counter, which indicates how many times the users printed\n", + " a specific song.\n", + " _artists : list of str\n", + " A list of the song's artists.\n", + " \"\"\"\n", + " def __init__(self, name, lyrics, artists=None):\n", + " self.name = name\n", + " self.lyrics = lyrics\n", + " self._views = 0\n", + " self._artists = self._reformat_artists(artists)\n", + "\n", + " def _reformat_artists(self, artists):\n", + " if isinstance(artists, str):\n", + " return self._listify_artists_from_string(artists)\n", + " elif artists is None:\n", + " return []\n", + " return artists\n", + "\n", + " def _listify_artists_from_string(self, artists):\n", + " \"\"\"Create list of artists from string.\"\"\"\n", + " for possible_split_token in (', ', ','):\n", + " if possible_split_token in artists:\n", + " return artists.split(possible_split_token)\n", + " return [artists]\n", + "\n", + " def add_artist(self, artist):\n", + " \"\"\"Add an artist to the song's artists list.\"\"\"\n", + " self._artists.append(artist)\n", + "\n", + " def remove_artist(self, artist):\n", + " \"\"\"Remove an artist from the song's artists list.\"\"\"\n", + " if len(self._artists) <= 1 or artist not in self._artists:\n", + " return False\n", + " self._artists.remove(artist)\n", + "\n", + " def get_artists(self):\n", + " \"\"\"Return the song's artists list.\"\"\"\n", + " return self._artists\n", + " \n", + " def count_words(self):\n", + " \"\"\"Return the word count in the song's lyrics.\"\"\"\n", + " return len(self.lyrics.split())\n", + "\n", + " def __str__(self):\n", + " self._views = self._views + 1\n", + " artists = ', '.join(self.get_artists())\n", + " title = f'\"{self.name}\" / {artists}'\n", + " separator = \"-\" * len(title)\n", + " return (\n", + " f\"{title}\\n\"\n", + " + f\"{separator}\\n\"\n", + " + f\"{self.lyrics}\\n\"\n", + " + f\"{separator}\\n\"\n", + " + f\"Seen: {self._views} time(s).\"\n", + " )\n", + "\n", + "\n", + "lyrics = \"\"\"\n", + "Her Majesty's a pretty nice girl\n", + "But she doesn't have a lot to say\n", + "Her Majesty's a pretty nice girl\n", + "But she changes from day to day\n", + "\n", + "I want to tell her that I love her a lot\n", + "But I gotta get a bellyful of wine\n", + "Her Majesty's a pretty nice girl\n", + "Someday I'm going to make her mine, oh yeah\n", + "Someday I'm going to make her mine\n", + "\"\"\".strip()\n", + "her_majestys = Song(\"Her Majesty's\", lyrics, \"The Beatles\")\n", + "print(her_majestys)\n", + "print(her_majestys.count_words())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " פעולת ה־__init__
של המחלקה Song
מגדירה את השם ואת הליריקה של השיר.
\n",
+ " היא גם מאתחלת את מספר הצפיות בשיר ל־0, ויוצרת את רשימת האומנים של השיר.
\n",
+ "
\n",
+ " כדי להקל על המשתמש במחלקה, אפשרנו להעביר את שמות האומנים כרשימה, כמחרוזת מופרדת בפסיקים, או לא להכניס אומנים כלל.
\n",
+ " עיבוד השמות לכדי צורה אחידה, רשימה של מחרוזות, יתבצע בפעולה _reformat_artists
, שתחליט כיצד לפעול לפי סוג המשתנה:
\n",
+ " אם הועברה לפרמטר האומנים מחרוזת, נשלח אותה ל־_listify_artists_from_string
שתיצור מהם רשימה. אם הועבר None
, נחזיר רשימה ריקה.\n",
+ "
\n",
+ " הפעולה count_words מפצלת את מילות השיר לרשימה, ומחזירה את מספר האיברים ברשימה.
\n",
+ "
\n",
+ " בקריאה ל־__str__
אנחנו מגדילים את ערכה של התכונה _views ב־1. כך נספור את הפעמים שביקשו להדפיס שיר. \n",
+ "
\n",
+ " אקרוסטיכון, כזכור לנו משבועות קודמים, הוא שיר ששרשור האות הראשונה של כל שורה בו יוצר ביטוי קריא.
\n",
+ " כחלק מהטכנולוגיה המשוכללת של \"שירומת\", נרצה ליצור מחלקה שמייצגת אקרוסטיכון.\n",
+ "
\n",
+ " כשחושבים על זה, אקרוסטיכון הוא סוג של שיר, כך שתכונות המחלקה ופעולות המחלקה אמורות להיות זהות לאלו של Song.
\n",
+ " למעשה, בבואנו לבנות את מחלקת האקרוסטיכון Acrostic נגלה במהרה שהיא העתק מדויק כמעט של מחלקת Song שכבר כתבנו.
\n",
+ " חסרה לנו רק הפעולה get_acrostic, נניח, שתחזיר לנו את האקרוסטיכון שנמצא בשיר.
\n",
+ " בהינתן המצב הזה, איך יהיה נכון לכתוב את מחלקת אקרוסטיכון?\n",
+ "
\n",
+ " הפתרון הראשון שקופץ לראש הוא פשוט להוסיף למחלקה Song את הפעולה \"get_acrostic\".
\n",
+ " זה, כמובן, לא מתאים כל כך.
\n",
+ " ברוב השירים אין אקרוסטיכון, והפעולה הזו לא מתאימה להם ולא שייכת למחלקה הכללית יותר, Song.
\n",
+ " הוספת הפעולה למחלקה Song גם תיצור עומס מיותר במחלקה – מה יקרה כשנרצה לתמוך בחמשירים? או בשירים מעגליים?
\n",
+ " עד מהרה כל מופע יכלול פעולות רבות שלא קשורות אליו, והשימוש במחלקה יהפוך מסורבל מאוד.\n",
+ "
\n",
+ " הפתרון השני, אם כך, הוא להעתיק את הקוד של מחלקת Song וליצור מחלקה כמעט זהה בשם Acrostic.
\n",
+ " במחלקה יהיו בדיוק כל התכונות והפעולות שנמצאות תחת Song, וכן את הפעולה \"get_acrostic\".
\n",
+ " במקרה הזה מדובר בשכפול קוד – כך שנראה שאף פתרון זה אינו מושלם.
\n",
+ " בכל פעם שנרצה לשנות משהו במחלקה Song נצטרך לשנות את שתי המחלקות,
\n",
+ " ועד מהרה פעולת התחזוקה של הקוד הכפול תהפוך להיות מפרכת, לא נעימה ומקור לבאגים נוראיים.\n",
+ "
\n",
+ " נסכם: אנחנו מחפשים מנגנון או טכניקה שיאפשרו לנו לשכפל את התכונות והפעולות של מחלקה אחת למחלקה אחרת.
\n",
+ " השכפול צריך להשאיר את המחלקה המקורית עובדת ועצמאית, וליצור מחלקה חדשה עם תכונות ופעולות שזהות לאלו של המחלקה המקורית.\n",
+ "
\n",
+ " הפתרון לבעיה שתיארנו בהקדמה נקרא \"ירושה\".
\n",
+ " נגדיר מחלקת Acrostic ריקה, שיורשת ממחלקת Song.
\n",
+ " כשאנחנו אומרים דבר כזה, אנחנו מתכוונים שהמחלקה הריקה Acrostic תקבל את כל התכונות והפעולות שנמצאות בתוך מחלקת Song.\n",
+ "
\n",
+ " התחביר של ירושה הוא פשוט:
\n",
+ " בסוף השורה שבה אנחנו מגדירים את המחלקה וקובעים את שמה, נוסיף סוגריים שבהם נציין את שם המחלקה שממנה אנחנו רוצים לרשת.\n",
+ "
\n",
+ " וכמו קסם, בעזרת שורה אחת פשוטה שכפלנו את התכונות והפעולות של מחלקת Song.
\n",
+ " ניצור מופע חדש של אקרוסטיכון בדיוק באותה הצורה שבה יצרנו מופע של שיר, רק שהפעם נשתמש במחלקת Acrostic במקום במחלקת Song.
\n",
+ " זה אפשרי כיוון ש־Acrostic ירשה את פעולת __init__
של Song. \n",
+ "
\n",
+ " בדוגמה שלמעלה ראינו כיצד יוצרים מופע חדש של שיר בעזרת המחלקה Acrostic.
\n",
+ " כשמחלקה יורשת ממחלקה אחרת, אפשר להתייחס למחלקה היורשת כאילו כל התכונות של מחלקת־העל (superclass), המחלקה שממנה היא יורשת, הועתקו אליה.
\n",
+ "
\n",
+ " כיוון ש־Acrostic ירשה את כל הפעולות ממחלקת־העל שלה, Song, ובהן גם את הפעולה __init__
,
\n",
+ " יצירת מופע חדש באמצעות קריאה ל־Acrostic קוראת למעשה לפעולה Song.__init__
.\n",
+ "
\n", + " פייתון לא מסתפקת בזה, ומעתיקה עבורנו גם את התיעוד של מחלקת־העל ושל הפעולות שבה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "help(Acrostic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " ירושה מאפשרת לנו להוסיף בקלות פעולות ל־Acrostic, מעבר לאלו שהיא ירשה ממחלקת Song.
\n",
+ " כדי לעשות זאת, פשוט נגדיר את הפעולה הרלוונטית במחלקה היורשת.
\n",
+ " נממש את הפעולה שלשמה התחלנו את כל העניין, get_acrostic, שתשרשר את האות הראשונה מכל שורה בשיר:\n",
+ "
\n", + " הפעולה get_acrostic אוספת את האות הראשונה מכל שורה, אם קיימת כזו, ומאחדת את כל האותיות שנאספו למחרוזת אחת:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "song = Acrostic(\"A Boat, Beneath a Sunny Sky\", lyrics, \"Lewis Carroll\")\n", + "song.get_acrostic()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " שימו לב - אומנם הפעולה קיימת במחלקה Acrostic, אך אין זה אומר שהיא קיימת במחלקה Song:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "song = Song(\"A Boat, Beneath a Sunny Sky\", lyrics, \"Lewis Carroll\")\n", + "song.get_acrostic()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " נוכל לראות שפייתון מבינה שמופע שנוצר מ־Acrostic הוא גם Acrostic, אבל הוא גם Song:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "song = Acrostic(\"A Boat, Beneath a Sunny Sky\", lyrics, \"Lewis Carroll\")\n", + "print(isinstance(song, Song))\n", + "print(isinstance(song, Acrostic))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " אבל לא להפך:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "song = Song(\"A Boat, Beneath a Sunny Sky\", lyrics, \"Lewis Carroll\")\n", + "print(isinstance(song, Song))\n", + "print(isinstance(song, Acrostic))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " מכאן שירושה היא חד־כיוונית: המחלקה היורשת מקבלת את כל התכונות והפעולות של מחלקת־העל, אבל לא להפך.
\n",
+ " אם מחלקה א יורשת ממחלקה ב, מופע שנוצר ממחלקה א יכול להשתמש בתכונות ובפעולות שמוגדרות במחלקה ב.
\n",
+ " למרות זאת, במקרה שכזה, מופע שנוצר ממחלקה ב לא יוכל להשתמש בתכונות ובפעולות שמוגדרות במחלקה א.\n",
+ "
\n", + " כשמשתמשים בירושה, נהוג שתת־המחלקה היורשת יכולה לגשת ולשנות גם תכונות פרטיות של מחלקת־העל שאותה היא יורשת.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### תרגיל ביניים: ססטי... מה?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "לפי ויקיפדיה, ססטינה הוא שיר בעל מבנה נוקשה שמציית לכללים הבאים:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " ממשו את המחלקה Sestina.
\n",
+ " המחלקה תכיל את כל התכונות והפעולות של שיר רגיל, נוסף על הפעולה is_valid שתבדוק אם הסטטינה תקינה.
\n",
+ " בבדיקתכם, התעלמו מהחוקים הנוגעים לבית האחרון.
\n",
+ " הפעולה תחזיר True
אם כן ו־False
אם לא.
\n",
+ " בדקו את הפעולה שלכם על \"ססטינת ערפד\" של ניל גיימן.\n",
+ "
\n",
+ " לפעמים נרצה לרשת ממחלקה מסוימת, אבל חלק מהפעולות או מהתכונות במחלקת־העל לא יתאימו לצרכים שלנו.
\n",
+ " במקרים אלו כן נרצה לרשת ממחלקת־העל את תכונותיה ופעולותיה, אבל נגדיר מחדש תכונות ופעולות שאנחנו רוצים לשנות. \n",
+ "
\n",
+ " ניקח כדוגמה את מחלקת Instrumental.
\n",
+ " קטע כלי (או שיר אינסטרומנטלי) הוא קטע מוזיקלי ללא שירה.
\n",
+ "
\n", + " ניצור מתוך המחלקה מופע עבור הקטע של Yiruma, יצירתו המהממת River Flows in You:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "song = Instrumental(\"River Flows in You\", \"\", \"Yiruma\")\n", + "print(song)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בשלב הזה נגלה שפעולת ה־__str__
עושה פחות חסד עם קטעים כליים.
\n",
+ " תעלול נחמד בירושה הוא היכולת לדרוס את הפעולה של מחלקת־העל בתת־מחלקה שיורשת ממנה.
\n",
+ " בדריסה, אנחנו \"מבטלים\" את הפעולה של מחלקת־העל (היא לא תרוץ), ומחליפים אותה בפעולה חדשה שניצור עבור תת־המחלקה.\n",
+ "
\n",
+ " נדרוס את הפעולה __str__
ונממש צורת תצוגה שמתאימה יותר לקטעים כליים:\n",
+ "
\n",
+ " בתא שלמעלה הגדרנו מחדש את הפעולה __str__
שבמחלקה Instrumental.
\n",
+ " ויתרנו על הצגת מילות השיר (כי אין כאלו) ועל הקווים המפרידים הסמוכים להם.\n",
+ "
\n",
+ " כמה נחמד!
\n",
+ " עכשיו כשנדפיס את המופע, מי שתיקרא כדי להמיר את המופע למחרוזת היא הפעולה Instrumental.__str__
ולא Song.__str__
.\n",
+ "
\n",
+ " איך פייתון מחליטה לאיזו פעולה לקרוא?
\n",
+ " השיטה היא תמיד לחפש תחילה במחלקה שממנה נוצר המופע.
\n",
+ " אם לא מצאנו את הפעולה שם, נעבור לחפש אותה במחלקת־העל.
\n",
+ "
\n",
+ " סדר המחלקות שבו נחפש את הפעולה קיבל את השם \"סדר בחירת פעולות\" (Method Resolution Order; או MRO).
\n",
+ " אפשר לראות את סדר בחירת הפעולות של song אם נקרא ל־Instrumental.mro()
:\n",
+ "
\n", + " זה אומר שכשאנחנו יוצרים מופע מהמחלקה Instrumental, פייתון תחפש פעולות ותכונות קודם כול אצלה, ורק אז במחלקה Song, הרי היא מחלקת־העל.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " נשים לב שבראש השרשרת עומדת המחלקה object.
\n",
+ " כשאנחנו מגדירים מחלקה \"רגילה\", ללא ירושה, פייתון אוטומטית מניחה שהיא יורשת ממחלקת object.
\n",
+ " מחלקה זו מגדירה התנהגויות בסיסיות עבור המחלקה שיצרנו.
\n",
+ " לדוגמה: הפעולה __init__
שמוגדרת ב־object מאפשרת לנו לאתחל מופעים חדשים מבלי שנגדיר __init__
במחלקה שלנו:
\n",
+ "
\n",
+ " הפעולה __str__
שמוגדרת ב־object מאפשרת לנו לראות את הייצוג המכוער של אובייקט כשאנחנו מדפיסים אותו,
\n",
+ " גם בלי לממש את __str__
במחלקה שלנו:\n",
+ "
\n",
+ " מקום נוסף לשיפור במחלקה Instrumental הוא פעולת ה־__init__
.
\n",
+ " כיוון שלקטעים כליים אין מילים, הפרמטר השני שאנחנו מעבירים לפעולת האתחול (lyrics) הוא מיותר לחלוטין.\n",
+ "
\n",
+ " היה עדיף לו __init__
של המחלקה Instrumental היה קצת שונה מה־__init__
של המחלקה Song.
\n",
+ " מה עושים? את הפעולה שהורשה דורסים!
\n",
+ " נגדיר __init__
חדש ללא הפרמטר lyrics:\n",
+ "
\n",
+ " בקוד שלמעלה דרסנו את הפעולה __init__
.
\n",
+ " חדי העין מביניכם זיהו שהיא כמעט זהה לפעולת ה־__init__
של Song.
\n",
+ " ההבדלים הם שלא מוגדר בה הפרמטר lyrics, ושתכונת המופע lyrics מוגדרת תמיד למחרוזת ריקה. \n",
+ "
\n",
+ " מצד אחד – השגנו את מה שרצינו. מצד שני – מטרת העל של ירושה הייתה מלכתחילה למנוע שכפול קוד.
\n",
+ " נצמצם את כפילות הקוד בין Instrumental.__init__
לבין Song.__init__
–
\n",
+ " במקום להעתיק את השורות שכתובות ב־Song.__init__
, נקרא לפעולת האתחול הזו מתוך Instrumental.__init__
:\n",
+ "
\n",
+ " מה יקרה כעת בעת יצירת מופע חדש של Instrumental?
\n",
+ "
Instrumental.__init__
תרוץ.Song.__init__
כאשר מועבר לה הפרמטר self באופן מפורש.Song.__init__
תרוץ, השורה self.name = name
שנמצאת בתוך הפעולה תתייחס למופע שיצרנו ממחלקת Instrumental.self
של הפעולה Song.__init__
את המופע שיצרנו במחלקת Instrumental.\n",
+ " עשינו פה טריק מוזר ואולי קצת מלוכלך.
\n",
+ " פנינו ישירות לפעולת הקסם Song.__init__
, ו\"השתלנו\" בה את ה־self שמייצג את המופע הנוכחי.\n",
+ "
\n",
+ " למה עשינו זאת?
\n",
+ " אם נקרא ל־Song()
, ייווצר אוטומטית מופע חדש של שיר \"רגיל\", והוא יהיה זה שייכנס לפרמטר self של Song.__init__
.
\n",
+ " לעומת זאת, אם נקרא ישירות ל־Song.__init__()
, נוכל להעביר את הפרמטר self באופן מפורש, בעצמנו.
\n",
+ " הטריק הזה מאפשר לנו להעביר לתוך הפרמטר self של Song.__init__
מופע שיצרנו בעזרת מחלקת Instrumental,
\n",
+ " או במילים אחרות – הטריק הזה מאפשר לנו להפעיל את פעולת האתחול של Song עבור המופע שנוצר מ־Instrumental.\n",
+ "
\n",
+ " למה זה שימושי?
\n",
+ " כיוון שאז אנחנו מנצלים את פעולת האתחול של Song שנראית כך:\n",
+ "
\n",
+ " באמצעות שורת הקוד שהוספנו ל־Instrumental שקוראת לפעולה הזאת, פעולת האתחול של Song,
\n",
+ " אנחנו חוסכים את שכפול הקוד בתת־המחלקה וגורמים לכך שהפעולה של מחלקת־העל תפעל ותגדיר עבורנו את תכונות המחלקה.
\n",
+ " זו דרך נוחה להגיד לפייתון \"אמנם דרסנו את הפעולה של מחלקת־העל, אבל עדיין נרצה לבצע גם את מה שמחלקת־העל עושה\".
\n",
+ "
\n",
+ " זה עובד כיוון שהעברנו את self, המופע שלנו, לפעולה Song.__init__
,
\n",
+ " מה שיגרור את הגדרתן של self.name
, self.lyrics
, self._views
ו־self._artists
עבור המופע שנוצר מ־Instrumental.\n",
+ "
\n",
+ " קריאה לפעולה שדרסנו במחלקת־העל היא טריק נפוץ ושימושי מאוד.
\n",
+ " למעשה, נהוג להשתמש בו הרבה גם על פעולות שהן לא __init__
.
\n",
+ " נוכל להחיל את אותו הטריק גם על __str__
, לדוגמה, ולחסוך את ההעלאה של self._views
ב־1:\n",
+ "
\n",
+ " ממשו בעצמכם את אותו תעלול עבור __str__
.
\n",
+ " הפתרון מופיע בתא שלהלן.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " בתא שלמעלה אנחנו רואים איך משתמשים בפעולה של מחלקת־העל,
\n",
+ " אבל עדיין מגדירים גם התנהגות עצמאית משל עצמנו.\n",
+ "
\n",
+ " כיוון שהטריק הזה נפוץ יחסית, פייתון נותנת בידינו דרך נוחה להתייחס למחלקת־העל, כשהפרמטר הראשון שנעביר לפעולה שבה הוא self.
\n",
+ " במקום לכתוב בכל פעם את הביטוי המתיש Song.__init__(self)
, נוכל לכתוב super().__init__()
.
\n",
+ " הפונקציה super מאפשרת לנו להעביר את המופע שיצרנו בתת־המחלקה, לתוך פעולה הנמצאת במחלקת־העל.\n",
+ "
\n",
+ " חשוב להבין היטב את הרעיון של קריאה לפעולה במחלקת־העל מתוך הפעולה הדורסת בתת־מחלקה.
\n",
+ " הטכניקה הזו מאפשרת לנו למנוע שכפול קוד, ואם בעתיד תשתנה מעט הפעולה במחלקת־העל, תת־המחלקה \"תזכה\" אוטומטית בשינויים האלו.
\n",
+ "
\n",
+ " כידוע לכם בכפר הדרדסים יש הרבה דרדסים \"רגילים\", אבל יש גם כמה דרדסים מפורסמים, כמו דרדסבא, דרדסית ודרדשף.
\n",
+ "
\n",
+ " לכל דרדס (Smurf) יש את התכונה name שמכילה את שמו, ואת הפעולות eat ו־sleep.
\n",
+ " לדרדס המיוחד \"דרדסאבא\" (PapaSmurf) יש גם את הפעולה give_order, שמקבלת פעולה של דרדס רגיל ומפעילה אותו עליו.
\n",
+ " ל\"דרדסית\" (Smurfette) יש את הפעולה kill_gargamel, שמקבלת כפרמטר מופע שנוצר ממחלקת Gargamel ומשנה את התכונה is_alive שבו ל־False
.
\n",
+ " ל\"דרדשף\" (ChefSmurf) יש את הפעולה create_food, שמקבלת את שם המנה שהוא מכין וכמה \"חתיכות\" (slices) הוא יצר ממנה. \n",
+ "
\n",
+ " דרדסים ישנים 8 שעות בדיוק. דרדס שישן לא יכול לבצע פעולות אחרות אלא אם הוא דרדסבא, שהוא חזק ומהיר והוא דרדס והוא גם חזק.
\n",
+ " דרדס יכול לאכול מזון רק אם ChefSmurf הכין אותו באמצעות הפעולה create_food, ורק אם נשארו עוד \"חתיכות\" מאותה מנה.
\n",
+ " הפעולה eat של כל דרדס תקבל רשימה של מנות ותאכל מכולן. הרשימה יכולה להכיל כל מספר איברים שהוא.\n",
+ "
\n",
+ " בשנות ה־90 העליזות החלה להישמע ביקורת הולכת וגוברת על רעיון הירושה.
\n",
+ "
\n",
+ "הביקורת העיקרית היא על כך שירושה יוצרת תלות בין מחלקת־העל לתתי־המחלקות שיורשות ממנה.
\n",
+ "כאשר מחלקה יורשת ממחלקה אחרת, כל הפעולות והתכונות של מחלקת־העל נחשפות לתת־המחלקה.
\n",
+ "נהוג שבתת־המחלקה אף מותר לשנות תכונות מוגנות של מחלקת־העל.
\n",
+ "התנהגות זו מובילה לצימוד גבוה מאוד בין מחלקות־העל לתתי־המחלקות, ופוגעת ברעיון הסתרת הנתונים שנמצא בליבת הרעיון של תכנות מונחה עצמים.\n",
+ "
\n",
+ " סוג נוסף של ביקורת שמועברת תדיר על ירושה נוגעת לתסמונת בשם \"מחלקת־העל השברירית\".
\n",
+ " תסמונת זו מדברת על כך שלאחר הירושה, שינוי במחלקת־העל עלול ליצור תקלים בקוד של תתי־המחלקות שיורשות ממנה.
\n",
+ " נדון בבעיה זו בהרחבה בהמשך הפרק.\n",
+ "
\n",
+ " שימוש רב במחלקות ויצירת היררכיית ירושה מורכבת ועמוסה עלולים גם לגרום ל\"בעיית היו־יו\" –
\n",
+ " מתכנת שצריך לעבור על מחלקות בתוכנית רצוא ושוב כדי להבין את המטרה של חלק קטן בקוד.
\n",
+ " היררכיה עמוסה שכזו גם מקשה על תחזוקת התוכנית וגורמת לניפוי שגיאות להיות קשה יותר במידה ניכרת.\n",
+ "
\n",
+ " בספר האלמותי של כנופיית הארבעה, \"תבניות עיצוב\" (Design Patterns), הכותבים מותחים ביקורת נוקבת על הבעיות שירושה עלולה להכניס לקוד.
\n",
+ " הם מציעים לבחור בהכלה במקום בירושה כשהדבר אפשרי.\n",
+ "
\n",
+ " ירושה גורמת לקוד להיות חשוף לתסמונת \"מחלקת־העל השברירית\".
\n",
+ " התסמונת מתארת בעיה נפוצה בירושה:
\n",
+ " שינויים במחלקת־העל עלולים לגרור תוצאות בלתי צפויות בתתי־המחלקות שיורשות ממנה, אפילו אם מחלקת העל עובדת באופן מושלם.\n",
+ "
\n",
+ " כדי לקבל מושג איך נראית התסמונת בעולם האמיתי, נראה דוגמה למימוש של אתר הקניות \"מוצרי לכון ובניו\".
\n",
+ " נגדיר מחלקה פשוטה בשם Clickable שמתארת עצם לחיץ. המחלקה תאפשר לנו לספור כמה פעמים לחצו על מופע מסוים:\n",
+ "
\n",
+ " באתר הקניות המדובר ישנו מופע של כפתור שנוצר מהמחלקה CrazyButton.
\n",
+ " המחלקה CrazyButton יורשת מהמחלקה Clickable.
\n",
+ " המיוחד במחלקת CrazyButton הוא שכל לחיצה נחשבת כלחיצה כפולה:\n",
+ "
\n",
+ " יום אחד, הסתכל אחיתופל המתכנת על הקוד. \"רגע! יש פה קוד כפול!\" הוא נזעק,
\n",
+ " \"הפעולה double_click במחלקה Clickcable עושה כמעט את מה שעושה הפעולה click\".
\n",
+ " אחיתופל ניגש בחופזה לתקן את הקוד, ולהשתמש ב־click פעמיים במקום בערך המפורש 2:\n",
+ "
\n", + " ואכן, המחלקה Clickable עודנה עובדת מצוין: \n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "buy_now = Clickable()\n", + "buy_now.click()\n", + "buy_now.double_click()\n", + "print(buy_now.clicks)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " אך מה יקרה אם נרצה להשתמש ב־CrazyButton?\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "buy_now = CrazyButton()\n", + "buy_now.click()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אחיתופל אומנם תיקן את הקוד של המחלקה Clickable, שנראית עכשיו תקינה לחלוטין.
\n",
+ " הבעיה היא שבתת־המחלקה CrazyButton הפעולה click מסתמכת על המימוש של הפעולה double_click שבמחלקת־העל שלה.
\n",
+ " כך נוצרת שרשרת קריאות אין־סופית: בעת קריאה ל־click ב־CrazyButton, נקראת הפעולה double_click, שקוראת בחזרה לפעולה click, וחוזר חלילה.\n",
+ "
\n",
+ " בספר Code Complete מוצג כלל אצבע שזכה לאהדה בקרב מתכנתים רבים:
\n",
+ " כשמתארים קשר בין שתי מחלקות בעזרת המילים \"סוג של\" (is a), הקשר התכנותי ביניהן יהיה לרוב ירושה.
\n",
+ " כשמתארים קשר בין שתי מחלקות בעזרת המילה \"יש...\" (has a), הקשר התכנותי ביניהן יהיה לרוב הכלה.
\n",
+ "
\n",
+ " אם אנחנו יכולים להגיד \"א הוא סוג של ב\", כאשר מדובר בשמות של מחלקות, ייתכן שנכון להשתמש בירושה.
\n",
+ " לדוגמה: כלב הוא סוג של חיה, ולכן ייתכן שמחלקת Dog תירש ממחלקת Animal.
\n",
+ " מכונית היא סוג של רכב, ולכן הגיוני שמחלקת Car תירש ממחלקת Vehicle.\n",
+ "
\n",
+ " אם אנחנו יכולים להגיד \"אצל א יש ב\", כאשר מדובר בשמות של מחלקות, ייתכן שנכון להשתמש בהכלה.
\n",
+ " לדוגמה: למכונית יש מנוע, ולכן הגיוני שבמחלקה Car תופיע התכונה engine, שהיא מופע שנוצר מהמחלקה Engine.
\n",
+ " זה עובד על תכונות באופן כללי, ולא רק על קשר של הכלה. לכפתור באתר יש כמה לחיצות, ולכן הגיוני ש־clicks תהיה תכונה של המחלקה Button.\n",
+ "
\n",
+ " ברברה ליסקוב היא אחת מנשות מדעי המחשב המשפיעות בהיסטוריה, ואחת משלוש הנשים היחידות שזכו בפרס טיורינג.
\n",
+ " תרומתה הנודעת ביותר נקראת \"עקרון ההחלפה של ליסקוב\" שנוסח פורמלית בשנת 1987.
\n",
+ " בפשטות, העיקרון גורס כך:\n",
+ "
\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "כל מופע שנוצר ממחלקת־על מסוימת, חייב להמשיך לעבוד כרגיל גם אם ביום בהיר אחד יחליטו ליצור אותו מתת־מחלקה שיורשת מאותה מחלקת־על.
\n", + "
\n",
+ " נדמיין שיש לנו מחלקה של שיר ומחלקה של אקרוסטיכון שיורשת ממנה.
\n",
+ " לפי ליסקוב, כל מופע שנוצר מהמחלקה \"שיר\" אמור להמשיך לפעול כרגיל, אפילו אם שינינו את הקוד כך שיווצר מהמחלקה \"אקרוסטיכון\". \n",
+ "
\n",
+ " דוגמה פופולרית שנותן רוברט מרטין (הידוע בשם העט Uncle Bob) היא שכל ריבוע הוא גם מלבן.
\n",
+ " חלקנו נמהר להשתמש בהורשה כדי לייצג את הרעיון הזה:\n",
+ "
\n",
+ " בקוד שלמעלה מימשנו מחלקת מלבן Rectangle, שבה התכונות \"גובה\" (height) ו\"רוחב\" (width), ואת הפעולה \"חישוב שטח\" (get_area).
\n",
+ " בקוד ישנה מחלקה נוספת בשם ריבוע (Square) שפעולת האתחול שלה מקבלת רק אורך של צלע אחת, שכן כל צלעותיו של ריבוע זהות.
\n",
+ " מחלקת הריבוע יורשת ממחלקת המלבן שבנינו.
\n",
+ " הרעיון יעבוד נהדר:\n",
+ "
\n",
+ " אבל שימו לב שאנחנו מפרים את עקרון ההחלפה של ליסקוב.
\n",
+ " על פי העיקרון, כל שימוש במחלקה יוכל להיות מוחלף בתת־מחלקה שיורשת ממנה.
\n",
+ " לפי אותו עיקרון, אנחנו אמורים להיות מסוגלים להחליף את הקריאה למחלקת מלבן בקריאה למחלקת ריבוע.
\n",
+ " משמע, עבור:\n",
+ "
\n", + " אמור להתאפשר:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(Square(5, 6))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " לְמַאי נַפְקָא מִנַּהּ? (יענו, ככה החלטתי לתכנת, למה מה תעשה לי?)\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אז כמו שראינו – יצירת ריבוע שיגרתית כן תעבוד כמצופה.
\n",
+ " עד שלפתע, אחד המתכנתים ירצה לשנות את רוחבו של הריבוע:\n",
+ "
\n",
+ " לאחר עריכת רוחב הריבוע ציפינו ששטח הריבוע יהיה 25, אך ארכיטקטורת הקוד שלנו כשלה בטיפול במקרה הזה.
\n",
+ " הקוד שלנו שינה רק את הרוחב של הריבוע ולא את אורכו, בזמן שבריבוע דבר כזה לא אמור להתאפשר.
\n",
+ " בשורה התחתונה – הקוד שלנו נשבר כיוון שלא עקבנו אחרי עקרון ההחלפה.\n",
+ "
\n",
+ " נוכל לתקן את הבאג בקלות יחסית אם נהפוך את width ואת height למשתנים פרטיים ב־Rectangle,
\n",
+ " אבל כדאי לשים לב שבדרך כלל אם אנחנו פונים לתקן את מחלקת־העל כדי שדברים יעבדו – סימן שמשהו בעייתי בבסיס הארכיטקטורה שלנו.
\n",
+ " ננסה בכל זאת:\n",
+ "
\n", + " הדבר הראשון שקופץ לעין זה שיצרנו פה מפלצת. הקוד שלנו הפך מסורבל בניסיון לטפל בכל מקרי הקצה.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " הבעיה הבאה תצוץ כשנבנה פונקציה שאמורה לעבוד על מקרה כללי של מלבן או ריבוע:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def set_and_print(my_shape):\n", + " my_shape.set_height(2)\n", + " my_shape.set_width(3)\n", + " print(my_shape)\n", + "\n", + "\n", + "set_and_print(Rectangle(4, 5))\n", + "set_and_print(Square(4))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " המטרה הברורה של הפונקציה הייתה למצוא את השטח של הצורה לאחר שערכנו את גודלו ל־3 על 2.
\n",
+ " מובן שהפונקציה לא החזירה את המצופה ממנה כשהעברנו לה ריבוע, כיוון שאי אפשר לשנות רק את רוחבו או רק את אורכו של הריבוע.\n",
+ "
\n", + " בשלב הזה נפסיק לנסות לשכנע את הריבוע שהוא גם מלבן, ופשוט נממש את שתי המחלקות בנפרד: \n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class Rectangle:\n", + " def __init__(self, width, height):\n", + " self.width = width\n", + " self.height = height\n", + " \n", + " def get_area(self):\n", + " return self.width * self.height\n", + "\n", + " def __str__(self):\n", + " dimensions = f\"{self.width}x{self.height}\"\n", + " return f\"Size of {dimensions} rectangle is {self.get_area()}\"\n", + "\n", + "\n", + "class Square:\n", + " def __init__(self, side):\n", + " self.side = side\n", + " \n", + " def get_area(self):\n", + " return self.side ** 2\n", + "\n", + " def __str__(self):\n", + " dimensions = f\"{self.side}x{self.side}\"\n", + " return f\"Size of {dimensions} square is {self.get_area()}\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " נזכה בקוד קצר, נוח לתחזוקה, שמאפשר למי שישתמש במחלקות שלנו לקבל בדיוק את מה שהוא רוצה בלי החשש שיטעה.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### העדיפו הכלה על ירושה כשאפשר" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " נממש תוכנה לקבלת דואר אלקטרוני, שיודעת לתמוך בקבלה ובשליחה של דואר מ־Gmail ומ־Walla – ספקי דואר אלקטרוני פופולריים.
\n",
+ " נתחיל במימוש מחלקה כללית עבור התוכנה שלנו.
\n",
+ " ההדפסות בפעולות הן לצורך ההמחשה. בדרך כלל נמליץ שלא לכלול הדפסות בתוך פעולות מחלקה או בתוך פונקציות.\n",
+ "
\n",
+ " כעת נממש את המחלקות של ספקי שירות הדואר האלקטרוני.
\n",
+ " פתרון שעושה שימוש בירושה עשוי להיראות כך:\n",
+ "
\n", + " נדגים שימוש במחלקה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mail = Walla(username='Yam', password='correcthorsebatterystaple')\n", + "mail.read()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " נשים לב שאם נרצה לעבור שירות דוא\"ל, נהיה חייבים ליצור מופע חדש לחלוטין של תוכנת דוא\"ל:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mail = Gmail(username='Yam', password='correcthorsebatterystaple')\n", + "mail.read()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " מה יקרה אם נשתמש בהכלה במקום בירושה?
\n",
+ " נתחיל בהתאמת המחלקה EmailClient:\n",
+ "
\n",
+ " בקוד שלמעלה הוספנו את התכונה provider ל־EmailClient.
\n",
+ " תכונה זו תכיל מופע של ספק הדוא\"ל.
\n",
+ " המחלקות של שירותי הדוא\"ל יישארו כפי שהן, אך לא יירשו מ־EmailClient:\n",
+ "
\n", + " נראה דוגמה לשימוש:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mail = EmailClient(\n", + " username='Yam',\n", + " password='correcthorsebatterystaple',\n", + " provider=Walla(),\n", + ")\n", + "\n", + "mail.read()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בניגוד לדוגמה שהשתמשה בירושה, אם נרצה להחליף ספק דוא\"ל לא נצטרך ליצור מופע חדש של תוכנת דואר אלקטרוני.
\n",
+ " פשוט נחליף את הספק:\n",
+ "
\n", + " במחברת זו סקרנו את הרעיון של ירושה בתכנות מונחה עצמים.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " למדנו מהי ירושה ואיך משתמשים בה בפייתון,
\n",
+ " ראינו איך דורסים פעולות ותכונות בתת־המחלקה, ואיך משתמשים ב־super כדי להתייחס למחלקת־העל.
\n",
+ " סקרנו גם את הבעיות שמתעוררות כשמשתמשים בירושה, ולמדנו שיש לנהוג משנה זהירות לפני שבוחרים לממש משהו בעזרת מחלקות.
\n",
+ " דיברנו על רעיונות תיאורטיים, כמו העדפת הכלה על ירושה, על סינדרום מחלקת־העל השברירית ועל עקרון ההחלפה של ליסקוב.\n",
+ "
\n",
+ " מתכנתים טובים משתמשים בירושה במשורה, רק אחרי שווידאו שבחירה בפתרון הזה לא תוסיף סיבוכיות מיותרת לקוד.
\n",
+ " במחברת הבאה נסקור טכניקות פופולריות שנולדו בזכות רעיון הירושה, ונלמד כיצד משתמשים בהן.\n",
+ "
\n",
+ " לאדון פסו בגז' יש חנות גדולה לממכר ספרים.
\n",
+ " ניצור מחלקה בסיסית שמייצגת מוצר בחנות של פסו:\n",
+ "
\n",
+ " הלקוח יכול לבצע הזמנה דרך המרשתת, דרך הגעה לאחד הסניפים או טלפונית.
\n",
+ " נממש את המחלקה שאחראית להזמנה הטלפונית:\n",
+ "
\n", + " דמיינו שכעת עליכם לממש את המחלקות שאחראיות להזמנות מהסניפים ומהמרשתת.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " עד מהרה נגלה שהקוד במחלקות הללו דומה מאוד לקוד שיצרנו במחלקת PhoneOrder.
\n",
+ " אם כך, נשמע שהדבר הנכון הוא ליצור מחלקה ששלוש המחלקות יורשות ממנה:\n",
+ "
\n", + " כתבו תוכנה שמדמה מערכת קבצים.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כל קובץ הוא מסוג מסוים – טקסטואלי, בינארי או תיקייה.
\n",
+ " תיקייה היא קובץ שמכיל בתוכו רשימת קבצים, שיכולים להיות טקסטואליים, בינאריים או תיקיות.
\n",
+ " נדמיין, לדוגמה, את היררכיית התיקיות הבאה:\n",
+ "
\n",
+ " בדוגמה שלמעלה יש 5 קבצים תחת תיקיית week08: שניים מהם תיקיות (images, resources) ו־3 מהם מחברות.
\n",
+ " התיקייה resources ריקה, ובתיקייה images יש את הקבצים הטקסטואליים exercise.svg ו־recall.svg ואת הקובץ הבינארי logo.jpg.
\n",
+ " במערכת שלנו, הנתיב לתיקיית week08 הוא /week08, והנתיב לקובץ logo.jpg הוא /week08/images/logo.jpg.\n",
+ "
\n",
+ " צרו בתוכנה שלכם מערכת לניהול משתמשים. לכל משתמש יש שם משתמש וסיסמה.
\n",
+ " משתמש יכול להיות מסוג \"מנהל מערכת\" או \"משתמש רגיל\".
\n",
+ "
\n",
+ " לקובץ בינארי או טקסטואלי יש משקל המיוצג בקילובייטים, תוכן, ומשתמש שיצר אותו.
\n",
+ " על קובץ טקסטואלי ניתן להפעיל את הפעולה count, שמקבלת מחרוזת לחיפוש ומחזירה כמה פעמים המחרוזת הופיעה בקובץ.
\n",
+ " אם הקובץ הבינארי הוא מסוג תמונה, צרו עבורו את הפעולה get_dimensions שמחזירה את אורך התמונה ואת רוחבה. אין צורך לממש את תוכן הפעולה עצמה.\n",
+ "
\n",
+ " לכל קובץ שאינו תיקייה צריכה להיות הפעולה .read()
, שאליה מעבירים כפרמטר משתמש.
\n",
+ " אם המשתמש הוא זה שיצר את הקובץ או שהוא מנהל המערכת, מהפעולה יוחזר תוכן הקובץ. אחרת יוחזר None
.\n",
+ "
\n", + " ממשו את משחק המאפיה.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " המחלקה Game תחזיק את הנתונים על המשחק.
\n",
+ " היא תאפשר למשתמש חדש להצטרף למשחק בעזרת הפעולה add_player, כשהפרמטר שמועבר לה הוא שם השחקן.
\n",
+ " כשהפעולה start תיקרא, יחולק תפקיד לכל אחד מהשחקנים: אזרח, שוטר או איש מאפיה. רק השחקן יודע מה תפקידו במשחק.
\n",
+ " בגרסת המשחק שלנו, יהיה רק איש מאפיה אחד ואיש משטרה אחד.
\n",
+ " אם הפעולה start נקראה אך אין די משתתפים כך שיהיה לפחות אזרח אחד, החזירו מהפעולה False ואל תתחילו את המשחק.\n",
+ "
\n",
+ " בכל סיבוב 3 חלקים – רצח על ידי המאפיה, עיכוב על ידי השוטר והצבעת האזרחים.
\n",
+ " כשהפעולה start_round נקראת, מתרחשות לפי הסדר הפעולות האלה:
\n",
+ "
\n",
+ " המשחק מסתיים בניצחון למאפיה כאשר נשארים רק שני משתתפים במשחק, שאחד מהם הוא איש המאפיה.
\n",
+ " המשחק מסתיים בניצחון לאזרחים אם השוטר עיכב את איש המאפיה, או אם האזרחים הוציאו להורג את איש המאפיה.\n",
+ "
\n", + " בונוס: ממשו את השאלה כבוט לטלגרם.\n", + "
" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/content/week08/2_Inheritance_Part_2.ipynb b/content/week08/2_Inheritance_Part_2.ipynb new file mode 100644 index 0000000..6b1237e --- /dev/null +++ b/content/week08/2_Inheritance_Part_2.ipynb @@ -0,0 +1,1770 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בשיעור הקודם למדנו על ירושה – מנגנון תכנותי שמאפשר יצירת מחלקה על בסיס תכונותיה ופעולותיה של מחלקה אחרת.
\n",
+ " סקרנו באילו מקרים נכון לבצע ירושה, ודיברנו על חילוקי הדעות ועל הסיבוכים האפשריים שירושה עלולה לגרום. \n",
+ "
\n",
+ " חקרנו את היכולת של תת־מחלקה להגדיר מחדש פעולות של מחלקת־העל וקראנו לכך \"דריסה\" של פעולה.
\n",
+ " דיברנו גם על הפונקציה super שמאפשרת לנו לקרוא בקלות לפעולות במחלקת־העל.\n",
+ "
\n",
+ " ירושה היא כר פורה לשיח בין תיאורטיקנים של מדעי המחשב.
\n",
+ " נכתבו עליה מילים רבות, והיא נושא מרכזי בדיונים על הנדסת תוכנה.
\n",
+ " במחברת זו נעמיק ונסקור שימושים וטכניקות שהתפתחו מתוך רעיון הירושה במחלקות.\n",
+ "
\n",
+ " הרעיון הבסיסי ביותר בירושה הוא העובדה שיכולה להיות יותר מרמה אחת של ירושה.
\n",
+ " קרי: אם מחלקה ב ירשה ממחלקה א, מחלקה ג יכולה לרשת ממחלקה ב.\n",
+ "
\n",
+ " בקטע הקוד הבא, לדוגמה, המחלקה יונק (Mammal) יורשת מהמחלקה \"חיה\" (Animal).
\n",
+ " המחלקות עטלף (Bat) וארנב (Rabbit) יורשות מהמחלקה \"יונק\".\n",
+ "
\n", + " במקרה כזה, המחלקות Bat ו־Rabbit ירשו הן את התכונות והפעולות של המחלקה Mammal, והן את אלו של Animal.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אפשר לקבל את שרשרת מחלקות־העל של מחלקה מסוימת לפי סדרן בעזרת class_name.mro()
:\n",
+ "
\n",
+ " בפנייה לפעולה כלשהי, תחפש פייתון את הפעולה במחלקה הראשונה שמופיעה ב־MRO.
\n",
+ " אם הפעולה לא מופיעה שם, תיגש פייתון למחלקה שאחריה, כך עד שהיא תגיע ל־object שתמיד יהיה בראש השרשרת.
\n",
+ " אם הפעולה לא קיימת באף אחת מהמחלקות שמופיעות ב־MRO, פייתון תזרוק NameError.\n",
+ "
\n",
+ " מבחינה הנדסית, מומלץ להימנע ככל האפשר מירושה מרובת רמות כשאין בכך צורך ממשי.
\n",
+ " ירושה מרובת רמות תגדיל את הסיכוי לתסמונת מחלקת־העל השברירית, תקשה על בדיקת התוכנית ותיצור בעיות תחזוקה בעתיד.\n",
+ "
\n",
+ " כל חיה משמיעה צליל האופייני לה: כלב נובח, פרה גועה ויונה הומה.
\n",
+ " נממש מחלקה עבור כל חיה:\n",
+ "
\n", + " נשים לב שצורת כל המחלקות דומה – מקרה קלאסי לירושה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class Animal:\n", + " def __init__(self, name, gender):\n", + " self.name = name\n", + " self.gender = gender\n", + "\n", + "\n", + "class Dog(Animal):\n", + " def make_sound(self):\n", + " print(\"Woof\")\n", + "\n", + " def __str__(self):\n", + " return f\"I'm {self.name} the dog!\"\n", + "\n", + "\n", + "class Cow(Animal):\n", + " def make_sound(self):\n", + " print(\"Muuuuuuuuuu\")\n", + "\n", + " def __str__(self):\n", + " return f\"I'm {self.name} the cow!\"\n", + "\n", + "\n", + "class Dove(Animal):\n", + " def make_sound(self):\n", + " print(\"Kukukuku!\")\n", + "\n", + " def __str__(self):\n", + " return f\"I'm {self.name} the dove!\"\n", + "\n", + "\n", + "print(Dove(\"Rexi\", \"Female\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " לעיתים קרובות אנחנו רוצים לממש קבוצת מחלקות שיש להן את אותן תכונות ופעולות – בדיוק כמו במקרה של Dog, Cow ו־Dove.
\n",
+ " במקרה כזה נפנה באופן טבעי לירושה, שבה מחלקת־העל תכיל את התכונות והפעולות המשותפות לכל המחלקות.\n",
+ "
\n",
+ " החיסרון במחלקת Animal הוא שכעת אפשר ליצור ישירות ממנה מופעים.
\n",
+ " זה לא מה שהתכוונו שיקרה. המחלקה הזו קיימת רק כדי לייצג רעיון מופשט של חיה, ולאפשר ירושת תכונות ופעולות ממקור אחד.
\n",
+ " מטרת התוכנית היא לאפשר לבעלי חיים להשמיע קול, ולכן אין משמעות ביצירת מופע מהמחלקה Animal – שהרי ל\"חיה\" כרעיון מופשט אין קול.
\n",
+ "
\n",
+ " חיסרון נוסף הוא שמחלקות יכולות לרשת ממחלקת Animal מבלי לממש את הפעולה make_sound.
\n",
+ " אם היינו מתכנתים פיסת קוד כללית שמטפלת בחיות, ייתכן שזה היה תקין, אבל לא זה המקרה בקוד שלמעלה.
\n",
+ " מטרת התוכנה שלנו הייתה מלכתחילה לייצג קולות של חיות, ומחלקה שיורשת מ־Animal ולא מממשת את make_sound היא פתח לבאגים בעתיד.\n",
+ "
\n",
+ " הפתרון לשתי הבעיות שהוצגו כאן נקרא בהנדסת תוכנה \"מחלקה מופשטת\" (Abstract Class).
\n",
+ " זהו מצב שבו אנחנו משתמשים במחלקת־על ל־3 צרכים:\n",
+ "
\n", + " נהוג לכתוב במחלקת־העל המופשטת את כותרות הפעולות שעל תת־המחלקות שיורשות ממנה לממש:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class Animal:\n", + " def __init__(self, name, gender):\n", + " self.name = name\n", + " self.gender = gender\n", + " \n", + " def make_sound(self):\n", + " pass\n", + "\n", + " def __str__(self):\n", + " pass\n", + "\n", + "\n", + "class Dog(Animal):\n", + " def make_sound(self):\n", + " print(\"Woof\")\n", + "\n", + " def __str__(self):\n", + " return f\"I'm {self.name} the dog!\"\n", + "\n", + "\n", + "class Cow(Animal):\n", + " def make_sound(self):\n", + " print(\"Muuuuuuuuuu\")\n", + "\n", + " def __str__(self):\n", + " return f\"I'm {self.name} the cow!\"\n", + "\n", + "\n", + "class Dove(Animal):\n", + " def make_sound(self):\n", + " print(\"Kukukuku!\")\n", + "\n", + " def __str__(self):\n", + " return f\"I'm {self.name} the dove!\"\n", + "\n", + "\n", + "print(Dove(\"Rexi\", \"Female\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " הרעיון של מחלקה מופשטת כה נפוץ, שהמודול הפייתוני abc (קיצור של Abstract Base Class) מאפשר לנו להגדיר מחלקה כמופשטת.
\n",
+ " נציץ בתיעוד של המודול וננסה לעקוב אחריו, למרות כל השטרודלים המוזרים שיש שם:\n",
+ "
@abstractmethod
.\n",
+ " חדי העין ישימו לב שהחזרנו את פעולת האתחול __init__
לכל תתי־המחלקות.
\n",
+ " זה קרה כיוון שהגדרנו את __init__
כפעולה מופשטת במחלקת Animal.
\n",
+ " להגדרה של פעולה כמופשטת שתי השלכות מיידיות:\n",
+ "
\n",
+ " עכשיו כשנרצה ליצור מופע מ־Animal, נגלה שזה בלתי אפשרי, כיוון ש־Animal.__init__
מוגדרת כמופשטת:\n",
+ "
\n",
+ " אם ננסה ליצור תת־מחלקה שיורשת מ־Animal ולא מממשת את אחת הפעולות המופשטות שלה, נגלה שלא נוכל ליצור ממנה מופעים.
\n",
+ " פייתון תזרוק שגיאה שאחת הפעולות המופשטות לא מוגדרת בתת־המחלקה:\n",
+ "
\n",
+ " עוד דבר מעניין שכדאי לשים לב אליו הוא השימוש הכבד ב־**kwargs
.
\n",
+ " כיוון שהפכנו את __init__
למופשטת, אנחנו חייבים לממש אותה בכל תתי־המחלקות שיורשות מ־Animal.
\n",
+ " למרות זאת, ה־__init__
\"המעניינת\" שעושה השמות לתוך תכונות המופע היא זו של מחלקת־העל Animal,
\n",
+ " זו שמקבלת את הפרמטרים name ו־gender ומשנה לפיהם את מצב המופע. \n",
+ "
\n",
+ " ביצירת מופע של אחת מהחיות, נרצה להעביר את הפרמטרים name ו־gender שמיועדים ל־Animal.__init__
.
\n",
+ " אבל ביצירת מופע של יונה, של פרה או של כלב אנחנו נקרא בפועל ל־__init__
שמימשו תתי־המחלקות.
\n",
+ " בשלב הזה, תתי־המחלקות צריכות למצוא דרך לקבל את הפרמטרים הרלוונטיים ולהעביר אותם למחלקת־העל שעושה השמות ל־self.
\n",
+ " כדי להעביר את כל הפרמטרים שמחלקת־העל צריכה, גם אם חתימת הפעולה Animal.__init__
תשתנה בעתיד, אנחנו משתמשים ב־**kwargs
.\n",
+ "
\n",
+ " השימוש במחלקות מופשטות נפוץ בתכנות כדי לאפשר הרחבה נוחה של התוכנה.
\n",
+ " מערכות שמאפשרות למתכנתים להרחיב את יכולותיהן בעזרת תוספים, לדוגמה, ישתמשו במחלקות מופשטות כדי להגדיר למתכנת דרך להתממשק עם הקוד של התוכנה.\n",
+ "
\n",
+ " בחנות הפרחים של מושניק מוכרים זרי פרחים.
\n",
+ " נמכר עם אגרטל או בלעדיו, ויש בו 3–30 פרחים מסוגים שונים: ורדים, סייפנים וסחלבים.
\n",
+ " לכל אחד מהפרחים צבע שונה.\n",
+ "
\n",
+ " המחיר של סייפן הוא 4 ש\"ח ושל סחלב 10 ש\"ח.
\n",
+ " המחיר של ורד נקבע לפי הצבע שלו: ורד לבן עולה 5 ש\"ח, וורד אדום עולה 6 ש\"ח.
\n",
+ " עבור זר עם אגרטל, על הלקוח להוסיף 20 ש\"ח.\n",
+ "
\n",
+ " כדי למשוך לקוחות חדשים, מדי פעם מושניק נותן הנחה על הזרים.
\n",
+ " לכל אחת מההנחות מושניק מצרף הערה שמסבירה מה הסיבה שבגינה ניתנה ההנחה.
\n",
+ " ישנם שני סוגים של הנחות בחנות: הנחה באחוזים והנחה שקלית.
\n",
+ " לכל זר יכולות להיות כמה הנחות, אשר יחושבו לפי סדר צירופן.\n",
+ "
\n",
+ " לדוגמה, אם הזר עלה 200 ש\"ח ומושניק החליט לתת הנחה של 10 אחוזים, הזר יעלה כעת 180 ש\"ח.
\n",
+ " אם מושניק החליט לתת הנחה נוספת על הזר, הפעם של 30 ש\"ח, מחירו של הזר יהיה כעת 150 ש\"ח.\n",
+ "
\n", + " אפשרו ללקוחות שרוצים חשבונית עבור רכישתם לקבל פירוט מודפס של חישוב המחיר של הזר.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ירושה מרובה" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " ישר ולעניין – יש לי חדשות משוגעות עבורכם: כל מחלקה יכולה לרשת מיותר ממחלקה אחת!\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " ניצור מחלקה המייצגת כלי נשק, ומחלקה אחרת המייצגת כלי רכב.
\n",
+ " מחלקת כלי הנשק תכלול את כוח כלי הנשק (strength) ופעולת תקיפה (attack).
\n",
+ " מחלקת כלי הרכב תכלול את המהירות המרבית של כלי הרכב (max_speed) ופעולה של הערכת זמן הגעה משוער (estimate_arrival_time).\n",
+ "
\n",
+ " אם נרצה ליצור מחלקת טנק, לדוגמה, ייתכן שנרצה לרשת גם ממחלקת כלי הנשק וגם ממחלקת כלי הרכב.
\n",
+ " פייתון מאפשרת לנו לעשות זאת בקלות יחסית:\n",
+ "
\n",
+ " בדוגמה שלמעלה מחלקת Tank יורשת את התכונות ואת הפעולות, הן של מחלקת Vehicle והן של מחלקת Weapon.
\n",
+ " כל מה שהיינו צריכים לעשות זה לציין בסוגריים שאחרי המחלקה Tank את כל המחלקות שמהן אנחנו רוצים לרשת, כשהן מופרדות בפסיק זו מזו.
\n",
+ " ניצור טנק לדוגמה, ונראה איך הוא קיבל את התכונות של כל מחלקות־העל שמהן הוא ירש:\n",
+ "
\n",
+ " לפני שנסביר בהרחבה איך הכול עובד, נעצור רגע כדי להגיד שירושה מרובה היא נושא שיחה נפיץ.
\n",
+ " בגלל הסיבוכיות והשכבות הנוספות שהוא מוסיף לכל מופע, תמצאו מתכנתים רבים שמתנגדים בתוקף לרעיון הירושה המרובה.
\n",
+ "
\n",
+ " נציג בזריזות את אחת הבעיות הקלאסיות שנובעות מירושה מרובה – בעיית היהלום.
\n",
+ " בבעיה זו מוצגת תת־מחלקה שיורשת מ־2 מחלקות־על, שהן עצמן תת־מחלקות של מחלקה נוספת.
\n",
+ " לדוגמה: מחלקת \"כפתור\" יורשת ממחלקת \"לחיץ\" וממחלקת \"מלבן\", שיורשות ממחלקת \"אובייקט\". \n",
+ "
\n",
+ " אם גם \"לחיץ\" וגם \"מלבן\" מימשו את פעולת __str__
אבל מחלקת \"כפתור\" לא, באיזו גרסה של __str__
מחלקת \"כפתור\" צריכה להשתמש?
\n",
+ " ומה בנוגע למצב שבו גם \"לחיץ\" וגם \"אובייקט\" מימשו את הפעולה?
\n",
+ "
\n",
+ " כפי שוודאי הצלחתם להבין, ירושה מרובה יכולה להכניס אותנו להרבה \"פינות\" שלא מוגדרות היטב.
\n",
+ " מהסיבה הזו פייתון החליטה לעשות מעשה, וליישר בכוח את העץ.
\n",
+ " נביט בשרשרת הירושה של מחלקת Tank:\n",
+ "
\n",
+ " אף על פי שטכנית היינו מצפים לראות עץ שבו Weapon ו־Vehicle נמצאות באותה רמה ומצביעות על Tank,
\n",
+ " בפועל פייתון \"משטחת\" את עץ הירושה כך ש־Tank יורשת מ־Weapon שיורשת מ־Vehicle.
\n",
+ " הסדר של המחלקות לאחר השיטוח נקבע לפי אלגוריתם שנקרא C3 Linearization, אבל זה לא משהו שבאמת חשוב לדעת בשלב הזה.\n",
+ "
\n",
+ " רעיון נפוץ המבוסס על ירושה מרובה הוא Mixin.
\n",
+ " Mixin היא מחלקה שאין לה תכלית בפני עצמה, והיא קיימת כדי \"לתרום\" תכונות ופעולות למחלקה שתירש אותה.
\n",
+ " לרוב נשתמש בשתי Mixins לפחות, כדי ליצור מהן מחלקות מורכבות יותר, שכוללות את הפעולות והתכונות של כל ה־Mixins.
\n",
+ " לדוגמה: ניצור מחלקת \"כפתור\" (Button) שיורשת ממחלקת \"מלבן גדול\" (LargeRectangle) וממחלקת \"לחיץ\" (Clickable).\n",
+ "
\n", + " ניצור כפתור ונראה שהוא אכן קיבל את התכונות והפעולות משתי המחלקות:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "buy_now = Button()\n", + "buy_now.click()\n", + "print(f\"Button size (from LargeRectangle class): {buy_now.size()}\")\n", + "print(f\"Button clicks (from Clickable class): {buy_now.clicks}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בחלק משפות התכנות האחרות ל־Mixins יש תחביר מיוחד, אולם בפייתון משתמשים פשוט בירושה מרובה.
\n",
+ " מהסיבה הזו, בין היתר, ההבדל בין Mixins לבין ירושה מרובה עלול להיראות מעורפל מעט.
\n",
+ " הדגש ב־Mixins הוא שהן מוגדרות כארגז תכונות או פעולות שאפשר לרשת, והן לא מיועדות לכך שיצרו ישירות מהן מופעים.
\n",
+ " אפשר להגיד שכל מקרה של Mixins משתמש בירושה מרובה, אך לא כל מקרה של ירושה מרובה כולל Mixins.\n",
+ "
\n",
+ " שחמט הוא משחק שבו שני שחקנים מתמודדים זה מול זה על לכידת מלכו של היריב.
\n",
+ " לכל אחד מהשחקנים יש צבא שמורכב מכלי משחק, ועליהם לעשות בצבא שימוש מושכל כדי להשיג יתרון על פני השחקן השני.
\n",
+ " כדי להבדיל בין כלי המשחק של השחקנים, כלי המשחק של שחקן אחד לבנים ואילו של השחקן השני שחורים.\n",
+ "
\n",
+ " ממשו לוח של משחק שחמט בגודל 8 על 8 משבצות.
\n",
+ "
\n",
+ " בשחמט 6 סוגים של כלי משחק: רגלי, צריח, פרש, רץ, מלך ומלכה.
\n",
+ " בפתיחת המשחק, שורות 1 ו־2 מלאות בכליו של השחקן הלבן ושורות 7 ו־8 מלאות בכליו של השחקן השחור.
\n",
+ " הכלים מסודרים על הלוח כדלקמן:\n",
+ "
\n", + " חוקי התנועה של הכלים מפורטים להלן:\n", + "
\n", + "\n", + "\n",
+ " כלי בתנועה לא יכול \"לדלג\" מעל כלי משחק אחרים – אם כלי נמצא בדרכו, הכלי שבתנועה לא יוכל לגשת למשבצות שנמצאות מעבר לאותו כלי חוסם.
\n",
+ " יוצא דופן לכלל זה הוא פרש – שיכול לדלג מעל כלים אחרים.
\n",
+ " אם הכלי החוסם הוא כלי של האויב, הכלי בתנועה רשאי לעבור ולעמוד על המשבצת של הכלי החוסם (נקרא גם \"להכות אותו\"), ולהסיר את הכלי החוסם מהמשחק.
\n",
+ " יוצא דופן לכלל זה הוא רגלי – הוא לא יכול להכות חייל שחוסם אותו מלפנים, והוא כן יכול להכות חייל שנמצא באלכסון הימני או השמאלי של כיוון ההתקדמות שלו.\n",
+ "
\n",
+ " השחמטאים שביניכם יכולים להתעלם כרגע ממהלכים \"מיוחדים\", כמו הכאה דרך הילוכו, הצרחה או הכתרה.
\n",
+ " כמו כן, בינתיים נתעלם מהחוק שקובע שאם המלך מאוים, השחקן חייב לבצע מהלך שיסיר ממנו את האיום.\n",
+ "
\n",
+ " ממשו מחלקת Board שתכיל תכונה בשם board.
\n",
+ " ביצירת הלוח, ייווצר לוח תקני מאויש בכלי משחק כפי שהוסבר לעיל.\n",
+ "
\n",
+ " לצורך כך, צרו מחלקה כללית בשם Piece המייצגת כלי משחק בשחמט.
\n",
+ " לכל כלי משחק צבע (color), השורה שבה הוא נמצא (row) והעמודה שבה הוא נמצא (column).
\n",
+ " צרו גם מחלקות עבור כל אחד מהכלים: Pawn (רגלי), Rook (צריח), Knight (פרש), Bishop (רץ), Queen (מלכה) ו־King (מלך).
\n",
+ "
\n",
+ " אפשרו לכל אחד מכלי המשחק לזוז על הלוח לפי החוקיות שתוארה מעלה.
\n",
+ " דאגו שקריאה ל־Board תדפיס לוח ועליו כלי משחק לפי המצב העדכני של הלוח.\n",
+ "
\n",
+ " נפתח ונאמר שאם אתם אמיצים במידה מספקת – אנחנו ממליצים לכם לנסות לפתור את התרגיל בעצמכם.
\n",
+ " הוא בהחלט לא פשוט ועלול לקחת לא מעט זמן, ולכן הפתרון שלנו מוגש פה:\n",
+ "
\n",
+ " בשבועיים האחרונים אנחנו משתמשים לא מעט במחלקות כדי לתרגל רעיונות חשובים בתכנות.
\n",
+ " עם הכוח באה האחריות – למרות כל הטכניקות המגניבות שלמדנו, כדאי להימנע מלסבך הנדסית את הקוד שלנו יתר על המידה.\n",
+ "
\n",
+ " על כביש מדברי חשוך, כשמשב רוח קריר בשערכם, אתם מבחינים באור המבליח באופק. ראשכם נהיה כבד עליכם ואתם מחליטים לחנות להלילה.
\n",
+ " אתם ניגשים לדלפק הקבלה במלון הסמוך, פוגשים בקבלה את הילברט, ומבקשים ממנו לשכור חדר.
\n",
+ " הילברט שואל אתכם אם אתם \"טובים במחשבים\", ומבקש שתעזרו לו לכתוב את התוכנה לניהול בית העסק שלו.\n",
+ "
\n", + " לכל חדר במלון קליפורניה נתונים כמפורט:\n", + "
\n", + "\n", + "\n",
+ " בחדר יחיד אפשר להכניס עד מיטה אחת, בחדר זוגי ובחדר מלכותי שתיים, ובחדר שופרא דשופרא יש מקום לעד 3 מיטות.
\n",
+ " אפשר להוסיף מיטה לכל חדר או להוציא ממנו מיטה, כל עוד יש בחדר לפחות מיטה אחת ומספר המיטות אינו גבוה מהתפוסה המרבית של החדר.\n",
+ "
\n",
+ " בינתיים, עד שהילברט ימצא תעלול על־אנושי כלשהו, מספר החדרים במלון שלו מוגבל.
\n",
+ " צרו מחלקה שתנהל את החדרים במלון שלו.
\n",
+ " המחלקה תכיל את הפעולה search_room שבהפעלתה תחזיר להילברט את החדר הפנוי הראשון שהיא מוצאת.
\n",
+ " חלק מהמשתמשים מעדיפים ללון בקומה מסוימת או בחדר מסוג מסוים. אפשרו להעביר לפונקציה את הפרמטרים הללו ולחפש לפיהם. \n",
+ "
\n",
+ " על כל חדר שאינו שופרא דשופרא יוכל הלקוח לבצע פעולת upgrade.
\n",
+ " הפעולה תעביר אותו לחדר בדרגה אחת גבוהה יותר, אם יש חדר כזה פנוי.\n",
+ "
\n",
+ " חניכים שובבים (לא אתם חלילה) החליטו לדלג על התרגיל לדוגמה, שמדגים בצורה מאלפת שימוש ברעיונות של מחלקה מופשטת וב־Mixins.
\n",
+ " כדי לוודא שהם מבינים את החומר, השתמשו במימוש שבדוגמה (או במימוש שכתבתם בעצמכם, כמובן) ועזרו להם לממש שחמט גנואים.\n",
+ "
\n",
+ " בשחמט גנואים רוחבו של הלוח הוא 11 משבצות ואורכו 10 משבצות.
\n",
+ "\n",
+ "
\n",
+ " בשורה 1, בעמודה ז (או g בגרסה האנגלית) מוצב גנו, ובעמודות ח ו־ט (h ו־i באנגלית) מוצבים גמלים.
\n",
+ " בשורה 10, בעמודה ה (או e בגרסה האנגלית) מוצב גנו, ובעמודות ג ו־ד (c ו־d באנגלית) מוצבים גמלים.\n",
+ "
\n",
+ " בשורה 1, בעמודה ה (או e בגרסה האנגלית) מוצבת המלכה, ובעמודות ג ו־ד (c ו־d באנגלית) מוצבים רצים.
\n",
+ " בשורה 10, בעמודה ז (או g בגרסה האנגלית) מוצבת המלכה, ובעמודות ח ו־ט (h ו־i באנגלית) מוצבים רצים.\n",
+ "
\n", + " שאר הכלים מסודרים באותו הסדר.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " גמלים נעים בצורה דומה לפרשים: 3 צעדים אנכית ואז צעד אחד אופקית, או 3 צעדים אופקית ואז צעד אחד אנכית.
\n",
+ " גנואים יכולים לבחור בכל תור אם הם רוצים לנוע כגמל או כפרש.
\n",
+ " שני כלי המשחק, גמל וגנו, יכולים לדלג מעל כלים אחרים מבלי להיחסם.
\n",
+ " תוכלו להשתמש בתווים ⛀ ו־⛂ כדי לייצג גמלים ובתווים ⛁ ו־⛃ כדי לייצג גנואים.\n",
+ "
\n",
+ " בגרסה זו של המשחק ישנה הרחבה קלה לתנועת הרגלים הבסיסית.
\n",
+ " אם רגלי לא נע מעולם, הוא יכול לנוע עד 3 צעדים במעלה הלוח.
\n",
+ " אם רגלי נע רק צעד אחד במעלה הלוח, הוא עדיין רשאי להתקדם 2 צעדים נוספים במעלה הלוח.
\n",
+ " רגלי שהתקדם 2 צעדים או יותר רשאי לנוע רק צעד אחד במעלה הלוח בכל פעם.\n",
+ "
\n",
+ " נוסף על כך, בלי קשר לשחמט גנואים וסתם בשביל הכיף, ממשו חד־קרן.
\n",
+ " חד־קרן הוא כלי שיכול לנוע בתור אחד מספר בלתי מוגבל של צעדי פרש לאותו הכיוון.
\n",
+ " לצורך ייצוג חד־קרן לבן על הלוח תוכלו להשתמש בתו 🦄, ועבור השחור השתמשו ב־🐴 (זה מה יש).
\n",
+ " וכן, אנחנו יודעים שהוא הורס את הדפסת הלוח. מותר לו. הוא חד־קרן.\n",
+ "
\n", + " חשבתם שבמקרה התעוררתי היום במצב רוח להמצאת גרסאות שחמט משוגעות? אז זהו, שלא.\n", + " הכול. כבר. קיים.\n", + "
" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/content/week08/3_Exceptions.ipynb b/content/week08/3_Exceptions.ipynb new file mode 100644 index 0000000..da07fac --- /dev/null +++ b/content/week08/3_Exceptions.ipynb @@ -0,0 +1,1653 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " עוד בשבוע הראשון, כשרק התחלתם את הקורס, הזהרנו אתכם שהמחשב עלול להיות עמית קשוח לעבודה.
\n",
+ " הוא תמיד מבצע בדיוק את מה שהוריתם לו לעשות, ולא מסוגל להתגבר לבד גם על הקלה שבטעויות.\n",
+ "
\n",
+ " במהלך הקורס נתקלתם פעמים רבות בחריגות (\"שגיאות\") שפייתון התריעה לכם עליהן.
\n",
+ " חלק מההתרעות על חריגות התרחשו בגלל טעויות בסיסיות בקוד, כמו נקודתיים חסרות בסוף שורת if
,
\n",
+ " וחלק מהן התרחשו בגלל בעיות שהתגלו מאוחר יותר – כמו קובץ שניסיתם לפתוח אבל לא היה קיים במחשב.\n",
+ "
\n",
+ " בפרק זה ניכנס בעובי הקורה בכל הנוגע לחריגות.
\n",
+ " נבין אילו סוגי חריגות יש, איך מפענחים אותן ואיך הן בנויות בפייתון, איך מטפלים בהן ואיך יוצרים חריגות בעצמנו.\n",
+ "
\n",
+ " חריגה (Exception) מייצגת כשל שהתרחש בזמן שפייתון ניסתה לפענח את הקוד שלנו או להריץ אותו.
\n",
+ " כשהקוד קורס ומוצגת לנו הודעת שגיאה, אפשר להגיד שפייתון מתריעה על חריגה (raise an exception).\n",
+ "
\n", + " נבדיל בין שני סוגי חריגות: שגיאות תחביר וחריגות כלליות.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### שגיאת תחביר" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " פייתון תתריע לפנינו על שגיאת תחביר (syntax error) כשנכתוב קוד שהיא לא מסוגלת לפענח.
\n",
+ " לרוב זה יתרחש כשלא עמדנו בכללי התחביר של פייתון ושכחנו תו מסוים, בין אם זה סגירת סוגריים, גרשיים או נקודתיים בסוף שורה.
\n",
+ " ודאי נתקלתם בשגיאה דומה בעבר:\n",
+ "
\n",
+ " פייתון משתדלת לספק לנו כמה שיותר מידע על מקור השגיאה:
\n",
+ "
SyntaxError
.\n",
+ " כשמדובר בשגיאות תחביר כדאי להסתכל על מיקום השגיאה בעין ביקורתית.
\n",
+ " בחלק מהפעמים, פייתון תכלול בשגיאה מידע לא מדויק על מיקום השגיאה:\n",
+ "
\n",
+ " אפשר לראות בקוד שלמעלה ששכחנו לסגור את הסוגריים שפתחנו בשורה הראשונה.
\n",
+ " הודעת השגיאה שפייתון תציג לנו מצביעה על הלולאה כמקור לבעיה, כאשר הבעיה האמיתית היא בבירור אי סגירת הסוגריים.
\n",
+ " ההודעות הלא מדויקות של פייתון בהקשר של שגיאות תחביר מבלבלות לעיתים קרובות מתכנתים מתחילים.
\n",
+ " המלצתנו, אם הסתבכתם עם שגיאה כזו - בדקו אם מקור השגיאה הוא בסביבה, ולאו דווקא במקום שפייתון מורה עליו.\n",
+ "
\n",
+ " גם כשהקוד שלכם תואם את כללי התחביר של פייתון, לפעמים עלולות להתגלות בעיות בזמן הרצת הקוד.
\n",
+ " כפי שוודאי כבר חוויתם, מנעד הבעיות האפשריות הוא רחב מאוד – החל בחלוקה באפס, עבור לטעות בשם המשתנה וכלה בניסיון לפתיחת קובץ לא קיים.
\n",
+ "
\n",
+ " בניגוד להתרעה על חריגה במצב של שגיאות תחביר, פייתון תתריע על חריגות אחרות רק כשהיא תגיע להריץ את הקוד שגורם לחריגה.
\n",
+ " נראה דוגמה לחריגה שכזו:\n",
+ "
\n", + " חשבו על כל החריגות שמשתמש שובב יכול לסחוט מהקוד שבתא למעלה.\n", + "
\n", + "\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " אחת החריגות הראשונות שנחשוב עליהן היא חלוקה באפס, שלא מוגדרת חשבונית.
\n",
+ " נראה מה יקרה כשננסה לבצע השמה של הערך 0 למשתנה b:\n",
+ "
\n",
+ " פייתון התריעה בפנינו על ZeroDivisionError בשורה 3, וגם הפעם היא פלטה לנו הודעה מפורטת יחסית.
\n",
+ " בדיוק כמו בשגיאת התחביר, השורה האחרונה היא הודעה שמספרת לנו מה קרה בפועל: חילקתם באפס וזה אסור.
\n",
+ " באותה שורה נראה גם את סוג החריגה (ZeroDivisionError) – הקטגוריה הכללית שאליה החריגה משתייכת.
\n",
+ "
\n", + " נוכל לראות סוג אחר של חריגה אם נעביר לאחד המשתנים אות במקום מספר:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "a = int(\"5\")\n", + "b = int(\"a\")\n", + "print(a // b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " גם הפעם פייתון התריעה בפנינו על חריגה, אבל מסוג שונה.
\n",
+ " חריגה מסוג ValueError מעידה שהערך שהעברנו הוא מהסוג (טיפוס) הנכון, אבל הוא לא התאים לביטוי שביקשנו מפייתון להריץ.
\n",
+ " ההודעה שפייתון הציגה מסבירה לנו שאי אפשר להמיר את המחרוזת \"a\" למספר עשרוני.\n",
+ "
\n", + " ניקח לדוגמה את ההתרעה האחרונה שקיבלנו על חריגה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "a = int(\"5\")\n", + "b = int(\"a\")\n", + "print(a // b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " ננסה להבין לעומק את החלקים השונים של ההודעה:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " במקרה של התרעה על חריגה שהתרחשה בתוך פונקציה, ההודעה תציג מעקב אחר שרשרת הקריאות שגרמו לה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def division(a, b):\n", + " return int(a) // int(b)\n", + "\n", + "\n", + "division(\"meow\", 5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " ההודעה הזו מכילה Traceback – מעין סיפור שמטרתו לעזור לנו להבין מדוע התרחשה החריגה.
\n",
+ " ב־Traceback נראה את השורה שבה התרחשה החריגה, ומעליה את שרשרת הקריאות לפונקציות שגרמו לשורה הזו לרוץ.
\n",
+ " כדי להבין טוב יותר את ה־Traceback, נהוג לקרוא אותו מהסוף להתחלה.\n",
+ "
\n",
+ " תחילה, נביט בשורה האחרונה ונקרא מה הייתה הסיבה שבגינה פייתון התריעה לנו על החריגה.
\n",
+ " ההודעה היא invalid literal for int() with base 10: 'meow'
– ניסינו להמיר את המחרוזת \"meow\" למספר שלם, וזה לא תקין.
\n",
+ " כדאי להסתכל גם על סוג החריגה (ValueError) כדי לקבל מושג כללי על היצור שאנחנו מתעסקים איתו.\n",
+ "
\n",
+ " נמשיך ל־Traceback.
\n",
+ " בפסקה שמעל השורה שבה מוצגת הודעת השגיאה, נסתכל על שורת הקוד שגרמה להתרעה על חריגה: return int(a) // int(b)
.
\n",
+ " בשלב זה יש בידינו די נתונים לצורך פענוח ההתרעה על החריגה: ניסינו לבצע המרה לא חוקית של המחרוזת \"meow\" למספר שלם בשורה 2.\n",
+ "
\n",
+ " אם עדיין לא נחה דעתכם וקשה לכם להבין מאיפה הגיעה ההתרעה על החריגה, תוכלו להמשיך ולטפס במעלה ה־Traceback.
\n",
+ " נעבור לקוד שגרם לשורה return int(a) // int(b)
לרוץ: division(\"meow\", 5)
.
\n",
+ " נוכל לראות שהקוד הזה מעביר לפרמטר הראשון של הפונקציה division את הערך \"meow\", שאותו היא מנסה להמיר למספר שלם.
\n",
+ " עכשיו ברור לחלוטין מאיפה מגיעה ההתרעה על החריגה.\n",
+ "
\n", + " לפעמים אנחנו יודעים מראש על שורת קוד שכתבנו שעלולה לגרום להתרעה על חריגה.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " פתיחת קובץ לקריאה, לדוגמה, עלולה להיכשל אם הנתיב לקובץ לא קיים במחשב.
\n",
+ " נכתוב פונקציה שמקבלת נתיב לקובץ ומחזירה את התוכן שלו כדי להדגים את הרעיון:\n",
+ "
\n", + " ננסה לאחזר את התוכן של הקובץ castle.txt:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "princess_location = get_file_content('castle.txt')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " במקרה שלמעלה ניסינו לפתוח קובץ שלא באמת קיים במחשב, ופייתון התריעה לנו על חריגת FileNotFoundError.
\n",
+ " מכאן, שהפונקציה get_file_content עשויה לגרום להתרעה על חריגה מסוג FileNotFoundError בכל פעם שיועבר לה נתיב שאינו קיים.
\n",
+ "
\n",
+ " כמתכנתים אחראים, חשוב לנו שהתוכנה לא תקרוס בכל פעם שהמשתמש מספק נתיב שגוי לקובץ.
\n",
+ " פייתון מאפשרת לנו להגדיר מראש כיצד לטפל במקרים שבהם אנחנו צופים שהיא תתריע על חריגה, ובכך למנוע את קריסת התוכנית.
\n",
+ " נעשה זאת בעזרת מילות המפתח try
ו־except
.
\n",
+ "
\n",
+ " לפני שנצלול לקוד, נבין מהו הרעיון הכללי של try
ושל except
.
\n",
+ " המטרה שלנו היא לספק התנהגות חלופית לקוד שעשוי להיכשל בגלל התרעה על חריגה מסוימת שחזינו שעשויה לקרות.\n",
+ "
\n",
+ " שימוש ב־try
וב־except
בפייתון נראה פחות או יותר כך:\n",
+ "
\n", + " נממש בקוד:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_file_content(filepath):\n", + " try: # נסה לבצע את השורות הבאות\n", + " with open(filepath) as file_handler:\n", + " return file_handler.read()\n", + " except FileNotFoundError: # ...אם נכשלת בגלל סוג החריגה הזה, נסה לבצע במקום\n", + " print(f\"Couldn't open the file: {filepath}.\")\n", + " return \"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " וננסה לאחזר שוב את התוכן של הקובץ castle.txt:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "princess_location = get_file_content(\"castle.txt\")\n", + "princess_location" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כפי שאפשר לראות בדוגמה, הפונקציה לא התריעה על חריגת FileNotFoundError, אלא הדפיסה לנו הודעה והחזירה מחרוזת ריקה.
\n",
+ " זה קרה כיוון שעטפנו את הקוד שעלול להתריע על חריגה ב־try
,\n",
+ " והגדרנו לפייתון בתוך ה־except
מה לבצע במקרה של כישלון.\n",
+ "
\n",
+ " התחביר של try
... except
הוא כדלהלן:
\n",
+ "
try:
.except ExceptionType:
, כאשר ExceptionType הוא סוג החריגה שנרצה לתפוס.try
רץ.try
... except
\n",
+ " \n",
+ " השורות שב־try
ירוצו כרגיל.
\n",
+ " אם לא תהיה התרעה על חריגה, פייתון תתעלם ממה שכתוב בתוך ה־except
.
\n",
+ " אם אחת השורות בתוך ה־try
גרמה להתרעה על חריגה מהסוג שכתוב בשורת ה־except
,
\n",
+ " פייתון תפסיק מייד לבצע את הקוד שכתוב ב־try
, ותעבור להריץ את הקוד המוזח בתוך ה־except
.
\n",
+ "
\n",
+ " ניקח את דוגמת הקוד שלמעלה, וננסה להבין כיצד פייתון קוראת אותה.
\n",
+ " פייתון תתחיל בהרצת השורה with open(\"castle.txt\") as file_handler:
ותתריע על חריגה, משום שהקובץ castle.txt לא נמצא.
\n",
+ " כיוון שהחריגה היא מסוג FileNotFoundError
, היא תחפש את המילים except FileNotFoundError:
מייד בסיום ההזחה.
\n",
+ " הביטוי הזה קיים בדוגמה שלנו, ולכן פייתון תבצע את מה שכתוב בהזחה שאחריו במקום להתריע על חריגה.\n",
+ "
try
... except
\n",
+ " \n",
+ " היכולת החדשה שקיבלנו נקראת \"לתפוס חריגות\", או \"לטפל בחריגות\".
\n",
+ " היא מאפשרת לנו לתכנן קוד שיגיב לבעיות שעלולות להתעורר במהלך ריצת הקוד שלנו.\n",
+ "
\n",
+ " כתבו פונקציה שמקבלת שני מספרים, ומחלקת את המספר הראשון בשני.
\n",
+ " אם המספר השני הוא אפס, החזירו 0 בתור התוצאה.
\n",
+ " השתמשו בחריגות.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " משתמשים פרחחים במיוחד לא יעצרו כאן.
\n",
+ " הפונקציה get_file_content מוגנת מניסיון לאחזר קבצים לא קיימים, זה נכון,
\n",
+ " אך משתמש שובב מהרגיל עשוי לנסות להעביר לפונקציה מחרוזות עם תווים שאסור לנתיבים להכיל:\n",
+ "
\n", + " נביט בחריגה ובקוד המקורי, ונגלה שבאמת לא ביקשנו לתפוס בשום מקום חריגה מסוג OSError.\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_file_content(filepath):\n", + " try:\n", + " with open(filepath) as file_handler:\n", + " return file_handler.read()\n", + " except FileNotFoundError:\n", + " print(f\"Couldn't open the file: {filepath}.\")\n", + " return \"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " מכאן, נוכל לבחור לתקן את הקוד באחת משתי דרכים.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " הדרך הראשונה היא להשתמש בקוד שכבר יצרנו לטיפול בחריגות מסוג FileNotFoundError.
\n",
+ " במקרה כזה, נצטרך לשנות את מה שכתוב אחרי ה־except
ל־tuple שאיבריו הם כל סוגי השגיאות שבהן נרצה לטפל:\n",
+ "
\n", + " בקוד שלמעלה גרמנו לכך, שהן חריגות מסוג FileNotFoundError והן חריגות מסוג OSError יטופלו באותה הצורה.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אבל מה אם נרצה שחריגות OSError תטופלנה בצורה שונה מחריגות FileNotFoundError?
\n",
+ " במקרה הזה נפנה לדרך השנייה, שמימושה פשוט למדי – נוסף לקוד הקיים, נכתוב פסקת קוד חדשה שעושה שימוש ב־except
:\n",
+ "
\n",
+ " בקוד שלמעלה הוספנו ל־get_file_content קטע קוד נוסף.
\n",
+ " ה־except
שהוספנו מאפשר לפייתון לטפל בחריגות מסוג OSError, כמו החריגה שקפצה לנו כשהכנסנו תווים בלתי חוקיים לנתיב הקובץ.
\n",
+ " נראה את הקוד בפעולה:\n",
+ "
\n",
+ " לשמחתנו, אנחנו לא מוגבלים במספר ה־except
־ים שאפשר להוסיף אחרי ה־try
.\n",
+ "
\n",
+ " בכל פעם שפייתון מתריעה על חריגה, היא גם מציגה את הקטגוריה שאליה שייכת אותה חריגה.
\n",
+ " כפי שכבר ראינו, שגיאות תחביר שייכות לקטגוריה SyntaxError, וחריגות שנובעות מערך שגוי שייכות לקטגוריה ValueError.
\n",
+ " למדנו גם להכיר שגיאות FileNotFoundError ושגיאות OSError, וללא ספק נתקלתם בחריגות שונות ומשונות בעצמכם במהלך הקורס.\n",
+ "
\n",
+ " אפשר להגיד, אם כך, שבפייתון יש סוגים רבים של חריגות שהם חלק מהשפה.
\n",
+ " מרוב סוגי חריגות, לפעמים קל לאבד את הידיים והרגליים ונעשה לא פשוט להבין על איזו חריגה פייתון עשויה להתריע. \n",
+ "
\n", + " נציע כמה דרכים מועילות להתמודד עם הבעיה, ולמצוא מהם סוגי החריגות שעשויים להיווצר בעקבות קוד שכתבתם:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "python exceptions read file.
\n", + " לנוחיותכם, בתיעוד של פייתון ישנו עמוד שמסביר על כל סוגי החריגות שפייתון מגדירה.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " על איזה סוג חריגה תתריע פייתון כאשר ניגש לרשימה במיקום שאינו קיים?
\n",
+ " מה בנוגע לגישה לרשימה במיקום שהוא מחרוזת?
\n",
+ " מהן סוגי החריגות שעליהם עלולה פייתון להתריע בעקבות הרצת הפעולה index
על רשימה?\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n", + " כתבו פונקציה בשם super_division שמקבלת מספר בלתי מוגבל של פרמטרים מספריים.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " הפונקציה תבצע חלוקה של המספר הראשון במספר השני.
\n",
+ " את התוצאה היא תחלק במספר השלישי לכדי תוצאה חדשה, את התוצאה החדשה היא תחלק במספר הרביעי וכן הלאה.
\n",
+ " לדוגמה: עבור הקריאה super_division(100, 10, 5, 2)
הפונקציה תחזיר 1, כיוון שתוצאת הביטוי $100 / 10 / 5 / 2$ היא 1.\n",
+ "
\n", + " עשו שימוש בטיפול בחריגות, ונסו לתפוס כמה שיותר מקרים של משתמשים שובבים.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### פעפוע של חריגות במעלה שרשרת הקריאות" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " התרעה על חריגה גורמת לריצת התוכנית להתנהג בצורה שונה מעט ממה שהכרנו עד כה.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " במהלך המחברת נתקלנו בשני מקרים אפשריים:\n", + "
\n", + "\n", + "try-except
שתואם לסוג החריגה, ואז החריגה נתפסת.try-except
ואז החריגה מקריסה את התוכנית. במקרה שכזה, מוצג לנו Traceback.\n",
+ " אבל מתברר שהסיפור מאחורי הקלעים הוא מורכב מעט יותר.
\n",
+ " אם פונקציה מסוימת לא יודעת כיצד לטפל בהתרעה על חריגה, היא מבקשת עזרה מהפונקציה שקראה לה.
\n",
+ " זה קצת כמו לבקש סוכר מהשכנים, גרסת החריגות והפונקציות.\n",
+ "
\n",
+ " נניח שבפונקציה A ישנה שורה שגרמה להתרעה על חריגה, והיא לא עטופה ב־try-except
.
\n",
+ " לפני שהתוכנית תקרוס, ההתרעה על החריגה תִּשָּׁלַח לפונקציה הקוראת, B, זו שהפעילה את פונקציה A שבה התרחשה ההתרעה על החריגה.
\n",
+ " בשלב הזה פייתון נותנת לנו הזדמנות נוספת לתפוס את החריגה.
\n",
+ " אם בתוך פונקציה B השורה שקראה לפונקציה A עטופה ב־try-except
שתופס את סוג החריגה הנכונה, החריגה תטופל.
\n",
+ " אם לא, החריגה תועבר לפונקציה C שקראה לפונקציה B, וכך הלאה, עד שנגיע לראש שרשרת הקריאות.
\n",
+ " אם אף אחד במעלה שרשרת הקריאות לא תפס את החריגה, התוכנית תקרוס ויוצג לנו Traceback.\n",
+ "
\n",
+ " ננסה להדגים באמצעות קטע קוד לא מתוחכם במיוחד.
\n",
+ " הנה האפשרות השנייה שדיברנו עליה – אף אחד בשרשרת הקריאות לא תופס את החריגה, התוכנה קורסת ומודפס Traceback:\n",
+ "
\n", + " והנה דוגמה לאפשרות הראשונה – שבה אנחנו תופסים את החריגה מייד כשהיא מתרחשת:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def a():\n", + " print(\"Dividing by zero...\")\n", + " try:\n", + " return 1 / 0\n", + " except ZeroDivisionError:\n", + " print(\"Never Dare Anyone to Divide By Zero!\")\n", + " print(\"https://reddit.com/2rkuek//")\n", + " print(\"End of a.\")\n", + "\n", + "\n", + "def b():\n", + " print(\"Calling a...\")\n", + " a()\n", + " print(\"End of b.\")\n", + "\n", + "\n", + "def c():\n", + " print(\"Calling b...\")\n", + " b()\n", + " print(\"End of c.\")\n", + "\n", + "\n", + "print(\"Start.\")\n", + "print(\"Calling c...\")\n", + "c()\n", + "print(\"Stop.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " האפשרות המעניינת היא האפשרות השלישית.
\n",
+ " מה קורה אם מישהו במעלה שרשרת הקריאות, נניח הפונקציה c, היא זו שמחליטה לתפוס את החריגה:\n",
+ "
\n",
+ " שימו לב שבמקרה הזה דילגנו על השורות שמדפיסות את ההודעה על סיום ריצתן של הפונקציות a ו־b.
\n",
+ " בשורה מספר 3 התבצעה חלוקה לא חוקית ב־0 שגרמה להתרעה על חריגה מסוג ZeroDivisionError.
\n",
+ " כיוון שהקוד לא היה עטוף ב־try-except
, ההתרעה על החריגה פעפעה לשורה a()
שנמצאת בפונקציה b.
\n",
+ " גם שם אף אחד לא טיפל בחריגה באמצעות try-except
, ולכן ההתרעה על החריגה המשיכה לפעפע לפונקציה שקראה ל־b, הלא היא c.
\n",
+ " ב־c סוף כל סוף הקריאה ל־b הייתה עטופה ב־try-except
, ושם התבצע הטיפול בחריגה.
\n",
+ " מאותה נקודה שבה טופלה החריגה, התוכנית המשיכה לרוץ כרגיל.\n",
+ "
\n", + " קראו את הקוד הבא, ופתרו את הסעיפים שאחריו.\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "MONTHS = [\n", + " \"January\", \"February\", \"March\", \"April\", \"May\", \"June\",\n", + " \"July\", \"August\", \"September\", \"October\", \"November\", \"December\",\n", + "]\n", + "\n", + "\n", + "def get_month_name(index):\n", + " \"\"\"Return the month name given its number.\"\"\"\n", + " return MONTHS[index - 1]\n", + "\n", + "\n", + "def get_month_number(name):\n", + " \"\"\"Return the month number given its name.\"\"\"\n", + " return MONTHS.index(name) + 1\n", + "\n", + "\n", + "def is_same_month(index, name):\n", + " return (\n", + " get_month_name(index) == name\n", + " and get_month_number(name) == index\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "is_same_month
, שאחת מהן מחזירה True
והשנייה מחזירה False
.None
אם התרחשה התרעה על חריגה.None
במקרה שכזה.is_same_month
שתחזיר True
בזמן שהיא אמורה להחזיר False
?is_same_month
במקום בפונקציות שהתריעו על חריגה. החזירו False
אם התרחשה התרעה על חריגה.\n",
+ " כל התרעה על חריגה מיוצגת בפייתון באמצעות מופע.
\n",
+ " בעזרת מילת המפתח as
, נוכל ליצור משתנה שיצביע למופע הזה.\n",
+ "
\n",
+ " נכתוב אחרי ה־except
ולפני הנקודתיים את הביטוי as VarName
, כאשר VarName הוא שם משתנה חדש שיצביע למופע של ההתרעה על החריגה.
\n",
+ " ביטוי זה יאפשר לנו לגשת למשתנה VarName שכולל את הפרטים על החריגה, מכל שורה שמוזחת תחת ה־except
:\n",
+ "
\n",
+ " היופי במשתנה החדש שנוצר, err, זה שהוא מופע פייתוני שנוצר מתוך המחלקה ZeroDivisionError.
\n",
+ " ZeroDivisionError, אם כך, היא מחלקה לכל דבר: יש לה __init__
, פעולות ותכונות, כמו שאפשר לראות בדוגמת הקוד שלמעלה.
\n",
+ " נבדוק אם יש לה __str__
מועיל:\n",
+ "
\n",
+ " כמה נוח!
\n",
+ " זו דרך ממש טובה להדפיס למשתמש הודעת שגיאה המתארת בדיוק מה הגורם לשגיאה שחווה.\n",
+ "
\n",
+ " זה זמן טוב לעצור רגע ולחשוב.
\n",
+ " ישנם סוגי חריגות רבים, ולכל סוג חריגה יש מחלקה שמייצגת אותו.
\n",
+ " האם זה אומר שכולן יורשות מאיזו מחלקת \"חריגה\" מופשטת כלשהי?
\n",
+ " נבדוק באמצעות גישה ל־Method Resolution Order של המחלקה.\n",
+ "
\n",
+ " וואו! זו שרשרת ירושות באורך שלא היה מבייש את שושלת המלוכה הבריטית.
\n",
+ " אז נראה שהחריגה של חלוקה באפס (ZeroDivisionError) היא מקרה מיוחד של חריגה חשבונית.
\n",
+ " חריגה חשבונית (ArithmeicError), בתורה, יורשת מ־Exception, שהיא עצמה יורשת מ־BaseException.\n",
+ "
\n", + " נביט במדרג הירושה המלא המוצג בתיעוד של פייתון כדי להתרשם:\n", + "
" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "BaseException\n", + " +-- SystemExit\n", + " +-- KeyboardInterrupt\n", + " +-- GeneratorExit\n", + " +-- Exception\n", + " +-- StopIteration\n", + " +-- StopAsyncIteration\n", + " +-- ArithmeticError\n", + " | +-- FloatingPointError\n", + " | +-- OverflowError\n", + " | +-- ZeroDivisionError <---- !הנה אנחנו\n", + " +-- AssertionError\n", + " +-- AttributeError\n", + " +-- BufferError\n", + " +-- EOFError\n", + " +-- ImportError\n", + " | +-- ModuleNotFoundError\n", + " +-- LookupError\n", + " | +-- IndexError\n", + " | +-- KeyError\n", + " +-- MemoryError\n", + " +-- NameError\n", + " | +-- UnboundLocalError\n", + " +-- OSError\n", + " | +-- BlockingIOError\n", + " | +-- ChildProcessError\n", + " | +-- ConnectionError\n", + " | | +-- BrokenPipeError\n", + " | | +-- ConnectionAbortedError\n", + " | | +-- ConnectionRefusedError\n", + " | | +-- ConnectionResetError\n", + " | +-- FileExistsError\n", + " | +-- FileNotFoundError\n", + " | +-- InterruptedError\n", + " | +-- IsADirectoryError\n", + " | +-- NotADirectoryError\n", + " | +-- PermissionError\n", + " | +-- ProcessLookupError\n", + " | +-- TimeoutError\n", + " +-- ReferenceError\n", + " +-- RuntimeError\n", + " | +-- NotImplementedError\n", + " | +-- RecursionError\n", + " +-- SyntaxError\n", + " | +-- IndentationError\n", + " | +-- TabError\n", + " +-- SystemError\n", + " +-- TypeError\n", + " +-- ValueError\n", + " | +-- UnicodeError\n", + " | +-- UnicodeDecodeError\n", + " | +-- UnicodeEncodeError\n", + " | +-- UnicodeTranslateError\n", + " +-- Warning\n", + " +-- DeprecationWarning\n", + " +-- PendingDeprecationWarning\n", + " +-- RuntimeWarning\n", + " +-- SyntaxWarning\n", + " +-- UserWarning\n", + " +-- FutureWarning\n", + " +-- ImportWarning\n", + " +-- UnicodeWarning\n", + " +-- BytesWarning\n", + " +-- ResourceWarning" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כיוון שאנחנו כבר מנוסים יחסית בענייני ירושה, בשלב הזה נוכל לפתח כמה רעיונות מעניינים.
\n",
+ " האם עצם זה ש־ZeroDivisionError היא תת־מחלקה של Exception, גורם לכך שנוכל לתפוס אותה בעזרת Exception?
\n",
+ " אם יתברר שכן, נוכל לתפוס מספר גדול מאוד של סוגי התרעות על חריגות בצורה הזו.
\n",
+ " נבדוק!\n",
+ "
\n",
+ " ובכן, כן.
\n",
+ " אנחנו יכולים לכתוב בצורה הזו קוד שיתפוס את מרב סוגי ההתרעות על חריגות.\n",
+ "
\n",
+ " בשלב זה נציין שבניגוד לאינטואיציה, חשוב שנהיה ממוקדים בטיפול שלנו בחריגות.
\n",
+ " חניכים שזה עתה למדו על הרעיון של טיפול בחריגות מקיפים לפעמים את כל הקוד שלהם ב־try-except
. מדובר ברעיון רע למדי.
\n",
+ " כלל האצבע שלנו מעתה יהיה זה:\n",
+ "
\n", + "בכל שימוש ב־\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "try-except
, צמצמו את כמות הקוד שנמצאת ב־try
, וטפלו בחריגה כמה שיותר ספציפית.\n", + "
\n",
+ " טיפול בחריגות הוא מנגנון שבהינתן התרעה על חריגה, מאפשר לנו להריץ קוד חלופי או קוד שיטפל בבעיה שנוצרה.
\n",
+ " אם אנחנו לא יודעים בדיוק מה הבעיה, או לא מתכוונים לטפל בה בדרך הגיונית – עדיף שלא נתפוס אותה.
\n",
+ " טיפול בחריגות שלא לצורך עלול ליצור \"תקלים שקטים\" בתוכנה שלנו, שאותם יהיה לנו קשה מאוד לאתר לאחר מכן.\n",
+ "
\n",
+ " במחברת זו למדנו מהן חריגות, כיצד לקרוא הודעות שגיאה של פייתון, וכיצד לטפל בהתרעות על חריגות שנוצרות עקב כשל בריצת התוכנית.
\n",
+ " ראינו כיצד חריגות מיוצגות בפייתון, איך הן פועלות מאחורי הקלעים ואיך לגלות אילו חריגות עלולות לצוץ בזמן ריצת הקוד שלנו.
\n",
+ " למדנו גם שמוטב לתפוס שגיאה באופן נקודתי עד כמה שאפשר, ורק כשאנחנו יודעים כיצד לטפל בה בדרך הגיונית.\n",
+ "
\n",
+ " כתבו פונקציה בשם calc שמקבלת כפרמטרים שני מספרים וסימן של פעולה חשבונית, בסדר הזה.
\n",
+ " הסימן יכול להיות אחד מאלה: +
, -
, *
או /
.
\n",
+ " מטרת הפונקציה היא להחזיר את תוצאת הפעולה החשבונית שהופעלה על שני המספרים.
\n",
+ " בפתרונכם, ודאו שאתם מטפלים בכל ההתרעות על חריגות שיכולות לצוץ בעקבות קלט מאתגר שהזין המשתמש.\n",
+ "
\n",
+ " כתבו פונקציה בשם search_in_directory שמקבלת נתיב, ורשימה של מילות מפתח.
\n",
+ " התוכנה תנסה לפתוח את כל הקבצים הנמצאים בנתיב, ותדפיס עבור כל מילת מפתח את כל הקבצים שבהם היא נמצאת.
\n",
+ " התוכנה תרוץ גם על תתי־התיקיות שנמצאות בנתיב שסופק (ועל תתי־תתי התיקיות, וכן הלאה), אם יש כאלו.
\n",
+ "
\n", + " לדוגמה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "search_in_directory(r\"C:\\Projects\\Notebooks\\week08\", [\"class\", \"int\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " תדפיס את הפלט הבא:\n", + "
" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "----------------------------------------\n", + "class\n", + "----------------------------------------\n", + "C:\\Projects\\Notebooks\\week08\\1_Inheritance.ipynb\n", + "C:\\Projects\\Notebooks\\week08\\2_Inheritance_Part_2.ipynb\n", + "C:\\Projects\\Notebooks\\week08\\3_Exceptions.ipynb\n", + "C:\\Projects\\Notebooks\\week08\\4_Exceptions_Part_2.ipynb\n", + "C:\\Projects\\Notebooks\\week08\\images\\exception_parts.svg\n", + "----------------------------------------\n", + "int\n", + "----------------------------------------\n", + "C:\\Projects\\Notebooks\\week08\\1_Inheritance.ipynb\n", + "C:\\Projects\\Notebooks\\week08\\2_Inheritance_Part_2.ipynb\n", + "C:\\Projects\\Notebooks\\week08\\3_Exceptions.ipynb\n", + "C:\\Projects\\Notebooks\\week08\\4_Exceptions_Part_2.ipynb\n", + "C:\\Projects\\Notebooks\\week08\\images\\chessboard.svg\n", + "C:\\Projects\\Notebooks\\week08\\images\\diamond_problem.svg\n", + "C:\\Projects\\Notebooks\\week08\\images\\exception_parts.svg\n", + "C:\\Projects\\Notebooks\\week08\\images\\exception_propogation.svg\n", + "C:\\Projects\\Notebooks\\week08\\images\\inheritance.svg\n", + "C:\\Projects\\Notebooks\\week08\\images\\logo.jpg\n", + "C:\\Projects\\Notebooks\\week08\\images\\multilevel_inheritance.svg\n", + "C:\\Projects\\Notebooks\\week08\\images\\multiple_inheritance.svg\n", + "C:\\Projects\\Notebooks\\week08\\images\\multiple_inheritance.svg.old\n", + "C:\\Projects\\Notebooks\\week08\\images\\try_except_flow.svg\n", + "C:\\Projects\\Notebooks\\week08\\images\\try_except_syntax.svg" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " טפלו בכמה שיותר התרעות על חריגות שעלולות לצוץ במהלך ריצת התוכנית.\n", + "
" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/content/week08/4_Exceptions_Part_2.ipynb b/content/week08/4_Exceptions_Part_2.ipynb new file mode 100644 index 0000000..dd65467 --- /dev/null +++ b/content/week08/4_Exceptions_Part_2.ipynb @@ -0,0 +1,1693 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " במחברת הקודמת התמודדנו לראשונה עם חריגות.
\n",
+ " למדנו לפרק הודעות שגיאה לרכיביהן ולחלץ מהן מידע מועיל, העמקנו בדרך הפעולה של Traceback ודיברנו על סוגי החריגות השונים בפייתון.
\n",
+ " ראינו לראשונה את מילות המפתח try
ו־except
, ולמדנו כיצד להשתמש בהן כדי לטפל בחריגות.\n",
+ "
\n",
+ " דיברנו על כך שטיפול בחריגות עשוי למנוע את קריסת התוכנית, וציינו גם שכדאי לבחור היטב באילו חריגות לטפל.
\n",
+ " הבהרנו שאם נטפל בחריגות ללא אבחנה, אנחנו עלולים ליצור \"תקלים שקטים\" שפייתון לא תדווח לנו עליהם ויהיו קשים לאיתור. \n",
+ "
\n",
+ " לבסוף, הצגנו כיצד השגיאות בפייתון הן בסך הכול מופע שנוצר ממחלקה שמייצגת את סוג החריגה.
\n",
+ " הראינו כיצד לקבל גישה למופע הזה מתוך ה־except
, וראינו את עץ הירושה המרשים של סוגי החריגות בפייתון. \n",
+ "
\n",
+ " במחברת זו נמשיך ללמוד על טיפול בחריגות.
\n",
+ " עד סוף המחברת תוכלו להתריע בעצמכם על חריגה וליצור סוגי חריגות משל עצמכם.
\n",
+ " זאת ועוד, תלמדו על יכולות מתקדמות יותר הנוגעות לטיפול בחריגות בפייתון, ועל הרגלי עבודה נכונים בכל הקשור בעבודה עם חריגות.\n",
+ "
\n",
+ " לעיתים חשוב לנו לוודא ששורת קוד תתבצע בכל מקרה, גם אם הכול סביב עולה באש.
\n",
+ " לרוב, זה קורה כאשר אנחנו פותחים משאב כלשהו (קובץ, חיבור לאתר אינטרנט) וצריכים למחוק או לסגור את המשאב בסוף הפעולה.
\n",
+ " במקרים כאלו, חשוב לנו שהשורה תתבצע אפילו אם הייתה התרעה על חריגה במהלך הרצת הקוד.\n",
+ "
\n",
+ " ננסה, לדוגמה, לכווץ את כל התמונות בתיקיית images לארכיון בעזרת המודול zipfile.
\n",
+ " אין מה לחשוש – המודול מובן יחסית וקל לשימוש.
\n",
+ " כל שנצטרך לעשות זה ליצור מופע של ZipFile ולהפעיל עליו את הפעולה write כדי לצרף לארכיון קבצים.
\n",
+ " אם אתם מרגישים נוח, זה הזמן לכתוב את הפתרון לכך בעצמכם. אם לא, ודאו שאתם מבינים היטב את התאים הבאים. \n",
+ "
\n", + " נתחיל ביבוא המודולים הרלוונטיים:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import zipfile" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " וככלי עזר, נכתוב generator שמקבל כפרמטר נתיב לתיקייה, ומחזיר את הנתיב לכל הקבצים שבה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_file_paths_from_folder(folder):\n", + " \"\"\"Yield paths for all the files in `folder`.\"\"\"\n", + " for file in os.listdir(folder):\n", + " path = os.path.join(folder, file)\n", + " yield path" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " עכשיו נכתוב פונקציה שיוצרת קובץ ארכיון חדש, מוסיפה אליו את הקבצים שבתיקיית התמונות וסוגרת את קובץ הארכיון:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def zip_folder(folder_name):\n", + " our_zipfile = zipfile.ZipFile('images.zip', 'w')\n", + "\n", + " for file in get_file_paths_from_folder(folder_name):\n", + " our_zipfile.write(file)\n", + " \n", + " our_zipfile.close()\n", + "\n", + "\n", + "zip_folder('images')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אבל מה יקרה אם תיקיית התמונות גדולה במיוחד ונגמר לנו המקום בזיכרון של המחשב?
\n",
+ " מה יקרה אם אין לנו גישה לאחד הקבצים והקריאה של אותו קובץ תיכשל?
\n",
+ " נטפל במקרים שבהם פייתון תתריע על חריגה:\n",
+ "
\n",
+ " התא למעלה מפר עיקרון חשוב שדיברנו עליו:
\n",
+ " עדיף שלא לתפוס את החריגה אם לא יודעים בדיוק מה הסוג שלה, למה היא התרחשה וכיצד לטפל בה.
\n",
+ " אבל רגע! אם לא נתפוס את החריגה, כיצד נוודא שהקוד שלנו סגר את קובץ הארכיון באופן מסודר לפני שהתוכנה קרסה?\n",
+ "
\n",
+ " זה הזמן להכיר את מילת המפתח finally
, שבאה אחרי ה־except
או במקומו.
\n",
+ " השורות שכתובות ב־finally
יתבצעו תמיד, גם אם הקוד קרס בגלל חריגה.
\n",
+ " שימוש ב־finally
ייראה כך:\n",
+ "
\n",
+ " שימו לב שאף על פי שהקוד שנמצא בתוך ה־try
קרס, ה־finally
התבצע.\n",
+ "
\n",
+ " למעשה, finally
עקשן כל כך שהוא יתבצע אפילו אם היה return
: \n",
+ "
\n", + " נשתמש במנגנון הזה כדי לוודא שקובץ הארכיון באמת ייסגר בסופו של דבר, ללא תלות במה שיקרה בדרך:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def zip_folder(folder_name):\n", + " our_zipfile = zipfile.ZipFile('images.zip', 'w')\n", + "\n", + " try:\n", + " for file in get_file_paths_from_folder(folder_name):\n", + " our_zipfile.write(file)\n", + " finally:\n", + " our_zipfile.close()\n", + " print(f\"Is our_zipfiles closed?... {our_zipfile}\")\n", + "\n", + "\n", + "zip_folder('images')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " ונבדוק שזה יעבוד גם אם נספק תיקייה לא קיימת, לדוגמה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "zip_folder('NO_SUCH_DIRECTORY')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " יופי! עכשיו כשראינו התרעה על חריגת FileNotFoundError כשמשתמש הכניס נתיב לא תקין לתיקייה, ראוי שנטפל בה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def zip_folder(folder_name):\n", + " our_zipfile = zipfile.ZipFile('images.zip', 'w')\n", + "\n", + " try:\n", + " for file in get_file_paths_from_folder(folder_name):\n", + " our_zipfile.write(file)\n", + " except FileNotFoundError as err:\n", + " print(f\"Critical error: {err}.\\nArchive is probably incomplete.\")\n", + " finally:\n", + " our_zipfile.close()\n", + " print(f\"Is our_zipfiles closed?... {our_zipfile}\")\n", + "\n", + "\n", + "zip_folder('NO_SUCH_DIRECTORY')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " יותר טוב!
\n",
+ " היתרון בצורת הכתיבה הזו הוא שגם אם תהיה התרעה על חריגה שאינה מסוג FileNotFoundError והתוכנה תקרוס,
\n",
+ " נוכל להיות בטוחים שקובץ הארכיון נסגר כראוי.\n",
+ "
\n",
+ " עד כה למדנו על 3 מילות מפתח שקשורות במנגנון לטיפול בחריגות של פייתון: try
, except
ו־finally
.
\n",
+ " אלו רעיונות מרכזיים בטיפול בחריגות, ותוכלו למצוא אותם בצורות כאלו ואחרות בכל שפת תכנות עכשווית שמאפשרת טיפול בחריגות.\n",
+ "
\n",
+ " אלא שבפייתון ישנה מילת מפתח נוספת שהתגנבה למנגנון הטיפול בחריגות: else
.
\n",
+ " תחת מילת המפתח הזו יופיעו פעולות שנרצה לבצע רק אם הקוד שב־try
רץ במלואו בהצלחה,
\n",
+ " או במילים אחרות: באף שלב לא הייתה התרעה על חריגה; אף לא except
אחד התבצע.\n",
+ "
\n",
+ " \"אבל רגע\", ישאלו חדי העין מביניכם.
\n",
+ " \"הרי המטרה היחידה של else
היא להריץ קוד אם הקוד שב־try
רץ עד סופו,
\n",
+ " אז למה שלא פשוט נכניס אותו כבר לתוך ה־try
, מייד אחרי הקוד שרצינו לבצע?\"
\n",
+ "
\n",
+ " וזו שאלה שיש בה היגיון רב –
\n",
+ " הרי קוד שקורס ב־try
ממילא גורם לכך שהקוד שנמצא אחריו ב־try
יפסיק לרוץ.
\n",
+ " אז למה לא פשוט לשים שם את קוד ההמשך? מה רע בקטע הקוד הבא?\n",
+ "
\n",
+ " ההבדל הוא רעיוני בעיקרו.
\n",
+ " המטרה שלנו היא להעביר את הרעיון שמשתקף מהקוד שלנו לקוראו בצורה נהירה יותר, קצת כמו בספר טוב.
\n",
+ " מילת המפתח else
תעזור לקורא להבין איפה חשבנו שעשויה להיות ההתרעה על החריגה,
\n",
+ " ואיפה אנחנו רוצים להמשיך ולהריץ קוד פייתון שקשור לאותו קוד.\n",
+ "
\n",
+ " ישנו יתרון נוסף בהפרדת הקוד ל־try
ול־else
–
\n",
+ " השיטה הזו עוזרת לנו להפריד בין הקוד שבו ייתפסו התרעות על חריגות, לבין הקוד שירוץ אחריו ושבו לא יטופלו חריגות.
\n",
+ " כיוון שהשורות שנמצאות בתוך ה־else
לא נמצאות בתוך ה־try
, פייתון לא תתפוס התרעות על חריגות שהתרחשו במהלך הרצתן.
\n",
+ " שיטה זו עוזרת לנו ליישם את כלל האצבע שמורה לנו לתפוס התרעות על חריגות באופן ממוקד –
\n",
+ " בעזרת else
לא נתפוס התרעות על חריגות בקוד שבו לא התכוונו מלכתחילה לתפוס התרעות על חריגות.\n",
+ "
\n",
+ " כתבו פונקציה בשם print_item שמקבלת כפרמטר ראשון רשימה, וכפרמטר שני מספר ($n$).
\n",
+ " הפונקציה תדפיס את האיבר ה־$n$־י ברשימה.
\n",
+ " טפלו בכל ההתרעות על חריגות שעלולות להיווצר בעקבות הרצת הפונקציה.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n", + " לסיכום, ניצור קטע קוד שמשתמש בכל מילות המפתח שלמדנו בהקשר של טיפול בחריגות:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def read_file(path):\n", + " try:\n", + " princess = open(path, 'r')\n", + " text = princess.read()\n", + " except (FileNotFoundError, PermissionError) as err:\n", + " print(f\"Can't find file '{path}'.\\n{err}.\")\n", + " text = None\n", + " else:\n", + " princess.close()\n", + " finally:\n", + " return text\n", + "\n", + "\n", + "print(read_file('resources/castle.txt3'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "try
, except
, else
, finally
.\n",
+ " \n",
+ " כתבו פונקציה בשם estimate_read_time, שמקבלת נתיב לקובץ, ומודדת בתוך כמה זמן פייתון קוראת את הקובץ.
\n",
+ " על הפונקציה להוסיף לקובץ בשם log.txt שורה שבה כתוב את שם הקובץ שניסיתם לקרוא, ובתוך כמה שניות פייתון קראה את הקובץ.
\n",
+ " הפונקציה תטפל בכל מקרי הקצה ובהתרעות על חריגות שבהם היא עלולה להיתקל.\n",
+ "
\n",
+ " עד כה התמקדנו בטיפול בהתרעות על חריגות שעלולות להיווצר במהלך ריצת התוכנית.
\n",
+ " בהגיענו לכתוב תוכניות גדולות יותר שמתכנתים אחרים ישתמשו בהן, לעיתים קרובות נרצה ליצור בעצמנו התרעות על חריגות.\n",
+ "
\n",
+ " התרעה על חריגה, כפי שלמדנו, היא דרך לדווח למתכנת שמשהו בעייתי התרחש בזמן ריצת התוכנית.
\n",
+ " נוכל ליצור התרעות כאלו בעצמנו, כדי להודיע על בעיות אפשריות למתכנתים שמשתמשים בקוד שלנו.\n",
+ "
\n",
+ " יצירת התרעה על חריגה היא עניין פשוט למדי שמורכב מ־3 חלקים:
\n",
+ "
raise
.\n", + " זה ייראה כך:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "raise ValueError(\"Just an example.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " נראה דוגמה לקוד אמיתי שמממש התרעה על חריגה.
\n",
+ " הקוד הבא לקוח מהמודול datetime, והוא רץ בכל פעם שמבקשים ליצור מופע חדש של תאריך.
\n",
+ " שימו לב כיצד יוצר המודול בודק את כל אחד מחלקי התאריך, ואם הערך חורג מהטווח שהוגדר – הוא מתריע על חריגה עם הודעת חריגה ממוקדת:\n",
+ "
\n",
+ " מטרת הפונקציה היא להבין אם השעה שהועברה ל־datetime תקינה.
\n",
+ " בפונקציה, בודקים אם השעה היא מספר בטווח 0–23, אם מספר הדקות הוא מספר בטווח 0–59 וכן הלאה.
\n",
+ " אם אחד התנאים לא מתקיים – מתריעים למתכנת שניסה ליצור את מופע התאריך על חריגה.\n",
+ "
\n",
+ " הקוד משתמש בתעלול מבורך – ביצירת מופע ממחלקה של חריגה, אפשר להשתמש ביותר מפרמטר אחד.
\n",
+ " הפרמטר הראשון תמיד יוקדש להודעת השגיאה, אבל אפשר להשתמש בשאר הפרמטרים כדי להעביר מידע נוסף על החריגה.
\n",
+ " בדרך כלל מעבירים שם מידע על הערכים שגרמו לבעיה, או את הערכים עצמם.\n",
+ "
\n",
+ " בתור רשת לכלי עבודה אתם מנסים לספור את מלאי הסולמות, כרסומות ומחרטות שקיימים אצלכם.
\n",
+ " כתבו מחלקה שמייצגת חנות (Store), ולה 3 תכונות:
\n",
+ " מספר הסולמות (ladders), מספר הכרסומות (millings) ומספר המחרטות (lathes) במלאי.\n",
+ "
\n",
+ " כתבו פונקציה בשם count_inventory שמקבלת רשימת מופעים של חנויות, ומחזירה את מספר הפריטים הכולל במלאי.
\n",
+ " צרו התרעות על חריגות במידת הצורך, בין אם במחלקה ובין אם בפונקציה.\n",
+ "
\n",
+ " טכניקה מעניינת שמשתמשים בה מדי פעם היא ניסוח מחדש של התרעה על חריגה.
\n",
+ " נבחר לנהוג כך כשהניסוח מחדש יעזור לנו למקד את מי שישתמש בקוד שלנו.
\n",
+ " בטכניקה הזו נתפוס בעזרת try
חריגה מסוג מסוים, וב־except
ניצור התרעה חדשה על חריגה עם הודעת שגיאה משלנו.\n",
+ "
\n", + " נראה דוגמה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "DAYS = [\n", + " 'Sunday', 'Monday', 'Tuesday', 'Wednesday',\n", + " 'Thursday', 'Friday', 'Saturday',\n", + "]\n", + "\n", + "def get_day_by_number(number):\n", + " try:\n", + " return DAYS[number - 1]\n", + " except IndexError:\n", + " raise ValueError(\"The number parameter must be between 1 and 7.\")\n", + "\n", + "\n", + "for i in range(1, 9):\n", + " print(get_day_by_number(i))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### טיפול והתרעה" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " טכניקה נוספת היא ביצוע פעולות מסוימות במהלך ה־except
, והתרעה על החריגה מחדש.
\n",
+ " השימוש בטכניקה הזו נפוץ מאוד.
\n",
+ "
\n", + " שימוש בה הוא מעין סיפור קצר בשלושה חלקים:\n", + "
\n", + "\n", + "\n", + " לדוגמה:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ADDRESS_BOOK = {\n", + " 'Padfoot': '12 Grimmauld Place, London, UK',\n", + " 'Jerry': 'Apartment 5A, 129 West 81st Street, New York, New York',\n", + " 'Clark': '344 Clinton St., Apt. 3B, Metropolis, USA',\n", + "}\n", + "\n", + "\n", + "def get_address_by_name(name):\n", + " try:\n", + " return ADDRESS_BOOK[name]\n", + " except KeyError as err:\n", + " with open('errors.txt', 'a') as errors:\n", + " errors.write(str(err))\n", + " raise KeyError(str(err))\n", + "\n", + "\n", + "for name in ('Padfoot', 'Clark', 'Jerry', 'The Ink Spots'):\n", + " print(get_address_by_name(name))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " למעשה, הרעיון של התרעה מחדש על חריגה הוא כה נפוץ, שמפתחי פייתון יצרו עבורו מעין קיצור.
\n",
+ " אם אתם נמצאים בתוך except
ורוצים לזרוק בדיוק את החריגה שתפסתם, פשוט כתבו raise
בלי כלום אחריו:\n",
+ "
\n",
+ " בתוכנות גדולות במיוחד נרצה ליצור סוגי חריגות משלנו.
\n",
+ " נוכל לעשות זאת בקלות אם נירש ממחלקה קיימת שמייצגת חריגה:\n",
+ "
\n", + " בשלב זה, נוכל להתריע על חריגה בעזרת סוג החריגה שיצרנו:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_address_by_name(name):\n", + " try:\n", + " return ADDRESS_BOOK[name]\n", + " except KeyError:\n", + " raise AddressUnknownError(f\"Can't find the address of {name}.\")\n", + "\n", + "\n", + "for name in ('Padfoot', 'Clark', 'Jerry', 'The Ink Spots'):\n", + " print(get_address_by_name(name))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " זכרו שהירושה כאן משפיעה על הדרך שבה תטופל החריגה שלכם.
\n",
+ " אם, נניח, AddressUnknownError הייתה יורשת מ־KeyError, ולא מ־Exception,
\n",
+ " זה אומר שכל מי שהיה עושה except KeyError
היה תופס גם חריגות מסוג AddressUnknownError.\n",
+ "
\n", + " יש לא מעט יתרונות ליצירת שגיאות משל עצמנו:\n", + "
\n", + "\n", + "\n",
+ " כבכל ירושה, תוכלו לדרוס את הפעולות __init__
ו־__str__
של מחלקת־העל שממנה ירשתם.
\n",
+ " דריסה כזו תספק לכם גמישות רבה בהגדרת החריגות שיצרתם ובשימוש בהן.\n",
+ "
\n", + " נראה דוגמה קצרצרה ליצירת חריגה מותאמת אישית:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class DrunkUserError(Exception):\n", + " \"\"\"Exception raised for errors in the input.\"\"\"\n", + "\n", + " def __init__(self, name, bac, *args, **kwargs):\n", + " super().__init__(*args, **kwargs)\n", + " self.name = name\n", + " self.bac = bac # Blood Alcohol Content\n", + "\n", + " def __str__(self):\n", + " return (\n", + " f\"{self.name} must not drriiiive!!! @_@\"\n", + " f\"\\nBAC: {self.bac}\"\n", + " )\n", + "\n", + "\n", + "def start_driving(username, blood_alcohol_content):\n", + " if blood_alcohol_content > 0.024:\n", + " raise DrunkUserError(username, blood_alcohol_content)\n", + " return True\n", + "\n", + "\n", + "start_driving(\"Kipik\", 0.05)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## נימוסים והליכות" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " טיפול בחריגות היא הדרך הטובה ביותר להגיב על התרחשויות לא סדירות ולנהל אותן בקוד הפייתון שאנחנו כותבים.
\n",
+ " כפי שכבר ראינו במחברות קודמות, בכלים מורכבים ומתקדמים יש יותר מקום לטעויות, וקווים מנחים יעזרו לנו להתנהל בצורה נכונה.
\n",
+ " נעבור על כמה כללי אצבע ורעיונות מועילים שיקלו עליכם לעבוד נכון עם חריגות:\n",
+ "
\n",
+ " באופן כללי, נעדיף להיות כמה שיותר ממוקדים בטיפול בחריגות.
\n",
+ " כשאנחנו מטפלים בחריגה, אנחנו יוצאים מנקודת הנחה שאנחנו יודעים מה הבעיה וכיצד יש לטפל בה.
\n",
+ " לדוגמה, אם משתמש הזין ערך שלא נתמך בקוד שלנו, נרצה לעצור את קריסת התוכנית ולבקש ממנו להזין ערך מתאים.\n",
+ "
\n",
+ " לא נרצה, לדוגמה, לתפוס התרעות על חריגות שלא התכוונו לתפוס מלכתחילה.
\n",
+ " אנחנו מעוניינים לטפל רק בבעיות שאנחנו יודעים שעלולות להתרחש.
\n",
+ " אם ישנה בעיה שאנחנו לא יודעים עליה – אנחנו מעדיפים שפייתון תצעק כדי שנדע שהיא קיימת.
\n",
+ " \"השתקה\" של בעיות שאנחנו לא יודעים על קיומן היא פתח לתקלים בלתי צפויים וחמורים אף יותר.\n",
+ "
\n",
+ " בקוד, הנקודה הזו תבוא לידי ביטוי כשנכתוב אחרי ה־except
את רשימת סוגי החריגות שבהן נטפל.
\n",
+ " נשתדל שלא לטפל ב־Exception, משום שאז נתפוס כל סוג חריגה שיורש ממנה (כמעט כולם).
\n",
+ " נשתדל גם לא לדחוס אחרי ה־except
סוגי חריגות שאנחנו לא יודעים אם הם רלוונטיים או לא.\n",
+ "
\n",
+ " יתרה מזאת, טיפול בשגיאות יתבצע רק על קוד שאנחנו יודעים שעלול לגרום להתרעה על חריגה.
\n",
+ " קוד שלא קשור לחריגה שהולכת להתרחש – לא יהיה חלק מהליך הטיפול בשגיאות.\n",
+ "
\n",
+ " בקוד, הנקודה הזו תבוא לידי ביטוי בכך שבתוך ה־try
יוזחו כמה שפחות שורות קוד.
\n",
+ " תחת ה־try
נכתוב אך ורק את הקוד שעלול להתריע על חריגה, ושום דבר מעבר לו.
\n",
+ " כך נדע שאנחנו לא תופסים בטעות חריגות שלא התכוונו לתפוס מלכתחילה.\n",
+ "
\n",
+ " אנחנו מעוניינים שהמתכנת שישתמש בקוד יקבל התרעות על חריגות שיבהירו לו מהן הבעיות בקוד שכתב, ויאפשרו לו לטפל בהן.
\n",
+ " אם כתבנו מודול או פונקציה שמתכנת אחר הולך להשתמש בה, לדוגמה, נקפיד ליצור התרעות על חריגות שיעזרו לו לנווט בקוד שלנו.\n",
+ "
\n",
+ " לעומת המתכנת, אנחנו שואפים שמי שישתמש בתוכנית (הלקוח של המוצר, נניח) לעולם לא יצטרך להתמודד עם התרעות על חריגות.
\n",
+ " התוכנית לא אמורה לקרוס בגלל חריגה אף פעם, אלא לטפל בחריגה ולחזור לפעולה תקינה.
\n",
+ " אם החריגה קיצונית ומחייבת את הפסקת הריצה של התוכנית, עלינו לפעול בצורה אחראית:
\n",
+ " נבצע שמירה מסודרת של כמה שיותר פרטים על הודעת השגיאה, נסגור חיבורים למשאבים, נמחק קבצים שיצרנו ונכבה את התוכנה בצורה מסודרת.\n",
+ "
\n",
+ " בכל הקשור לשפות תכנות, ישנן שתי גישות נפוצות לטיפול במקרי קצה בתוכנית.
\n",
+ "
\n",
+ " הגישה הראשונה נקראת LBYL, או Look Before You Leap (\"הסתכל לפני שאתה קופץ\").
\n",
+ " גישה זו דוגלת בבדיקת השטח לפני ביצוע כל פעולה.
\n",
+ " הפעולה תתבצע לבסוף, רק כשנהיה בטוחים שהרצתה חוקית ולא גורמת להתרעה על חריגה.
\n",
+ " קוד שכתב מי שדוגל בשיטה הזו מתאפיין בשימוש תדיר במילת המפתח if
.\n",
+ "
\n",
+ " הגישה השנייה נקראת EAFP, או Easier to Ask for Forgiveness than Permission (\"קל יותר לבקש סליחה מלבקש רשות\").
\n",
+ " גישה זו דוגלת בביצוע פעולות מבלי לבדוק לפני כן את היתכנותן, ותפיסה של התרעה על חריגה אם היא מתרחשת.
\n",
+ " קוד שכתב מי שדוגל בשיטה הזו מתאפיין בשימוש תדיר במבני try-except
.\n",
+ "
\n",
+ " נראה שתי דוגמאות להבדלים בגישות.
\n",
+ "
\n", + " נכתוב פונקציה שמקבלת מחרוזת ומיקום ($n$), ומחזירה את התו במיקום ה־$n$־י במחרוזת.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " לפניכם הקוד בגישת LBYL, ובו אנחנו מנסים לבדוק בזהירות אם אכן מדובר במחרוזת, ואם יש בה לפחות $n$ תווים.
\n",
+ " רק אחרי שאנחנו מוודאים שכל דרישות הקדם מתקיימות, אנחנו ניגשים לבצע את הפעולה.\n",
+ "
\n",
+ " והנה אותו קוד בגישת EAFP. הפעם פשוט ננסה לאחזר את התו, ונסמוך על מבנה ה־try-except
שיתפוס עבורנו את החריגות:\n",
+ "
\n", + " נתכנת פונקציה שמקבלת נתיב לקובץ וטקסט, וכותבת את הטקסט לקובץ.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " הנה הקוד בגישת LBYL, ובו אנחנו מנסים לבדוק בזהירות אם הקובץ אכן בטוח לכתיבה.
\n",
+ " רק אחרי שאנחנו מוודאים שיש לנו גישה אליו, שאכן מדובר בקובץ ושאפשר לכתוב אליו, אנחנו מבצעים את הכתיבה לקובץ.\n",
+ "
\n",
+ " והנה אותו קוד בגישת EAFP. הפעם פשוט ננסה לכתוב לקובץ, ונסמוך על מבנה ה־try-except
שיתפוס עבורנו את החריגות:\n",
+ "
\n", + " מתכנתי פייתון נוטים יותר לתכנות בגישת EAFP.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### אחריות אישית" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " טיפול בחריגה ימנע מהתוכנה לקרוס, ועשוי להחביא את העובדה שהייתה בעיה בזרימת התוכנית.
\n",
+ " לרוב זה מצוין ובדיוק מה שאנחנו רוצים, אבל מתכנתים בתחילת דרכם עלולים להתפתות לנצל את העובדה הזו יתר על המידה.
\n",
+ " לפניכם דוגמה לקטע קוד שחניכים רבים משתמשים בו בתחילת דרכם:\n",
+ "
\n",
+ " הטריק הזה נקרא \"השתקת חריגות\".
\n",
+ " ברוב המוחלט של המקרים זה לא מה שאנחנו רוצים.
\n",
+ "
\n",
+ " השתקת החריגה עלולה לגרום לתקל בהמשך ריצת התוכנית, ויהיה לנו קשה מאוד לאתר אותו בעתיד.
\n",
+ " פעמים רבות השתקה שכזו מעידה על כך שהחריגה נתפסה מוקדם מדי.
\n",
+ " במקרים כאלו, עדיף לטפל בהתרעה על החריגה בפונקציה שקראה למקום שבו התרחשה ההתרעה על החריגה.\n",
+ "
\n",
+ " אם תגיעו למצב שבו אתם משתיקים חריגות, עצרו ושאלו את עצמכם אם זה הפתרון הטוב ביותר.
\n",
+ " לרוב, עדיף יהיה לטפל בהתרעה על החריגה ולהביא את התוכנה למצב תקין,
\n",
+ " או לפחות לשמור את פרטי ההתרעה לקובץ המתעד את ההתרעות על החריגות שהתרחשו בזמן ריצת התוכנה. \n",
+ "
\n",
+ " לפניכם דוגמאות קוד מחרידות להפליא.
\n",
+ " תקנו אותן כך שיתאימו לנימוסים והליכות שלמדנו בסוף המחברת.
\n",
+ " היעזרו באינטרנט במידת הצורך.\n",
+ "
\n",
+ " כתבו פונקציה המיועדת למתכנתים בחברת \"The Syndicate\".
\n",
+ " הפונקציה תקבל כפרמטרים נתיב לקובץ (filepath) ומספר שורה (line_number).
\n",
+ " הפונקציה תחזיר את מה שכתוב בקובץ שנתיבו הוא filepath בשורה שמספרה הוא line_number.
\n",
+ " נהלו את השגיאות היטב. בכל פעם שישנה התרעה על חריגה, כתבו אותה לקובץ log.txt עם חותמת זמן וההודעה. \n",
+ "
\n",
+ " צפנת פענח ניסה להעביר ליוליוס פואמה מעניינת שכתב.
\n",
+ " בניסיוננו להתחקות אחר עקבותיו של צפנת פענח, ניסינו לשים את ידינו על המסר – אך גילינו שהוא מוצפן.\n",
+ "
\n", + " בתיקיית resources מצורפים שני קבצים: users.txt ו־passwords.txt.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "כל שורה בקובץ users.txt נראית כך:\n", + "
" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "1|Jesse Henderson|lowejohn@gmail.com|95675 Debra Canyon Apt. 862 Port Jeremy, MD 16600|2000-10-15 21:50:07|Digitized exuding knowledge user" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " העמודה הראשונה מייצגת את מספר המשתמש, העמודה השנייה מייצגת את שמו ושאר העמודות מייצגות פרטים מזהים עליו.
\n",
+ " העמודות מופרדות בתו |.\n",
+ "
\n", + "כל שורה בקובץ בקובץ passwords.txt נראית כך:\n", + "
" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "0|0|i8gvD1!pOIPiLvOY5W72yZU9C#" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " שתי העמודות הראשונות הן מספרי המשתמש, כפי שהם מוגדרים ב־users.txt.
\n",
+ " העמודה השלישית היא סיסמת ההתקשרות ביניהם.\n",
+ "
\n", + " כתבו את הפונקציות הבאות:\n", + "
\n", + "\n", + "\n",
+ " לצורך פתרון החידה, מצאו את סיסמת ההתקשרות של המשתמשים Zaphnath Paaneah ו־Gaius Iulius Caesar.
\n",
+ " פענחו בעזרתה את המסר הסודי שבקובץ message.txt.\n",
+ "
\n", + " השתמשו בתרגיל כדי לתרגל את מה שלמדתם בנושא טיפול בחריגות.\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def digest(key, data):\n", + " S = list(range(256))\n", + " j = 0\n", + "\n", + " for i in range(256):\n", + " j = (j + S[i] + ord(key[i % len(key)])) % 256\n", + " S[i], S[j] = S[j], S[i]\n", + "\n", + " j = 0\n", + " y = 0\n", + "\n", + " for char in data:\n", + " j = (j + 1) % 256\n", + " y = (y + S[j]) % 256\n", + " S[j], S[y] = S[y], S[j]\n", + " yield chr(ord(char) ^ S[(S[j] + S[y]) % 256])\n", + "\n", + "\n", + "def decrypt(key, message):\n", + " return ''.join(digest(key, message))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/content/week08/5_Summary.ipynb b/content/week08/5_Summary.ipynb new file mode 100644 index 0000000..471c288 --- /dev/null +++ b/content/week08/5_Summary.ipynb @@ -0,0 +1,290 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בהנדסת חשמל, \"שער לוגי\" הוא רכיב בסיסי ולו כניסה אחת או יותר, ויציאה אחת בלבד.
\n",
+ " הכניסות בשער הלוגי משמשות בתור הקלט שלו, והיציאה בתור הפלט.
\n",
+ " כל כניסה יכולה לקבל את הערך \"אמת\" או \"שקר\".
\n",
+ " השער הלוגי יבצע פעולה לוגית בהתאם לכניסות שלו, ויחזיר ערך חדש – שגם הוא אמת או שקר.\n",
+ "
\n",
+ " שער לוגי שוודאי יצא לכם לשמוע עליו הוא AND.
\n",
+ " לשער הלוגי AND שתי כניסות. פלט השער יהיה True רק אם הקלט בשתי הכניסות הוא True, אחרת – יהיה הפלט False.
\n",
+ " שער לוגי אחר שוודאי יצא לכם לשמוע עליו הוא NOT.
\n",
+ " לשער הלוגי NOT כניסה אחת. פלט השער יהיה True אם הקלט הוא False, או False אם הקלט הוא True. \n",
+ "
\n",
+ " שערים עם כניסה אחת נקראים שערים אוּנָרִיִּים (unary), ושערים עם שתי כניסות נקראים שערים בִּינָרִיִּים (binary).
\n",
+ "
\n",
+ " חיבור של כמה שערים לוגיים אחד לשני ייצור רכיב לוגי.
\n",
+ " בדוגמה הבאה, הקלטים (מלמעלה למטה) True, False, False, True יניבו True.\n",
+ "
\n",
+ " צרו את המחלקות LogicGate, UnaryGate, BinaryGate ו־Connector.
\n",
+ " כמו כן, צרו את המחלקות AndGate, NotGate, OrGate, NandGate ו־XorGate.
\n",
+ " במידת הצורך, קראו עוד על שערים לוגיים.\n",
+ "
\n", + "לאחר שכתבתם את הקוד, ודאו שהקוד הבא, שמדמה את המעגל מהאיור למעלה, מדפיס רק True:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "circuit = Connector(gate=OrGate, input_gates=[OrGate, AndGate])\n", + "print(circuit.send_inputs([False, False, False, False]) == False)\n", + "print(circuit.send_inputs([False, False, False, True]) == False)\n", + "print(circuit.send_inputs([False, False, True, False]) == False)\n", + "print(circuit.send_inputs([False, False, True, True]) == True)\n", + "print(circuit.send_inputs([False, True, False, False]) == True)\n", + "print(circuit.send_inputs([False, True, False, True]) == True)\n", + "print(circuit.send_inputs([False, True, True, False]) == True)\n", + "print(circuit.send_inputs([False, True, True, True]) == True)\n", + "print(circuit.send_inputs([True, False, False, False]) == True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " בונוס: ודאו גם שהקוד הבא עובד ומדפיס True:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כתבו מחלקה המייצגת קובייה.
\n",
+ " לכל קובייה יש צבע, מספר פאות וערכים שמופיעים על הפאות.
\n",
+ " הטלה של קובייה תבחר באופן אקראי ערך מאחת מפאות הקובייה ותחזיר אותו.\n",
+ "
\n",
+ " על כל פאה של \"קובייה מספרית\" מופיע מספר שערכו בין 1 למספר הפאות בקובייה. כל מספר יופיע פעם אחת בלבד על הקובייה.
\n",
+ " ב\"קובייה לא מאוזנת\" ישנו גם ערך שנקרא \"סיכוי הטלה\", שמפרט עבור כל פאה מה הסיכוי שתצא בהטלה.\n",
+ "
\n",
+ " שם הצבע של הקובייה חייב להיות מורכב מאותיות בלבד, סיכויי ההטלה חייבים להיות חיוביים ומספר הפאות חייב להיות תואם למספר הערכים המופיעים על הפאות.
\n",
+ "
\n",
+ " במשחק \"Yamtzee\" יש שק גדול שמכיל המון \"קוביות אורך\", ושק גדול נוסף של קוביות לא מאוזנות בצבעים שונים שנקראות \"קוביות אות\".
\n",
+ " 10% מהקוביות בכל שק הן אדומות, 25% הן ירוקות ו־65% הן כחולות.
\n",
+ " קוביית אורך היא קובייה מספרית עם 10 פאות. אם הקובייה מורה 1, זורקים אותה שוב עד שיוצא מספר אחר.
\n",
+ " קוביית אות היא קובייה לא מאוזנת עם 26 פאות, כאשר בכל פאה אות מהא\"ב האנגלי. סיכוי ההטלה עבור כל אחד מהערכים הוא לפי התדירות של האות.
\n",
+ "
\n",
+ " בתחילת המשחק, המשתמש בוחר את מספר המשתתפים במשחק, ומה הניקוד שאליו צריך להגיע כדי שהמשחק יסתיים.
\n",
+ "
\n",
+ " כל שחקן בתורו מטיל את קוביית האורך, ומטיל מספר קוביות אות הזהה למספר שהתקבל בקוביית האורך.
\n",
+ " לדוגמה, אם קוביית האורך שהטלתי מורה 3, עליי להטיל 3 קוביות אות.\n",
+ "
\n",
+ " המשתתף צריך להשתמש בקוביות האות שיצאו לו כדי ליצור מילה תקנית בשפה האנגלית, שאורכה 2 אותיות לפחות.
\n",
+ " הוא יכול לסדר את הקוביות מחדש ולבחור שלא להשתמש בחלק מהן, אבל הוא לא יכול להשתמש באותה קוביית אות פעמיים.
\n",
+ " למרות זאת, יכול לקרות מצב שיותר מקוביית אות אחת תציג את אותה האות.\n",
+ "
\n",
+ " אם המשתמש הצליח להרכיב מילה, עבור כל אות שבה השתמש הוא זוכה ב־$\\lfloor{\\frac{12}{\\lceil{f}\\rceil^{\\frac{3}{4}}}}\\rfloor$ נקודות, כאשר f זו תדירות האות בא\"ב האנגלי, מעוגלת כלפי מעלה.
\n",
+ " במילים: 12 חלקי הביטוי הבא – הסיכוי שהאות תצא מעוגל כלפי מעלה, בחזקת 1.5 ואז להוציא מזה שורש. כל זה – מעוגל כלפי מטה.
\n",
+ " לדוגמה, עבור המילה \"zone\" יזכה השחקן ב־17 נקודות לפי החישוב הזה:\n",
+ "
\n",
+ " קוביית אורך אדומה מעניקה למשתתף שהטיל אותה עוד קוביית אות במתנה.
\n",
+ " קוביית אות אדומה מאפשרת למשתתף להשתמש באות שמופיעה על הקובייה כמה פעמים שירצה.
\n",
+ " קוביות ירוקות נותנות למשתתף את האפשרות לבחור אם להטילן מחדש.
\n",
+ "
\n",
+ " ממשו את Yamtzee.
\n",
+ " השתמשו ב־words.txt כדי לוודא שהמילים שהכניס המשתמש תקינות.\n",
+ "
ValueError Traceback (most recent call last)
<ipython-input-27-71566ce329ec> in <module>
1 a = int("5")
----> 2 b = int("a")
3 print(a // b)
ValueError: invalid literal for int() with base 10: 'a'
\n",
+ " אחת מהמהפיכות המשמעותיות שאנחנו חווים בעידן המודרני היא התפוצצות חסרת תקדים של כמות המידע שנמצא בהישג ידינו.
\n",
+ " בזכות המחשוב והכלים המתקדמים שפיתחנו, איסוף נתונים לגבי כמעט כל דבר זה דבר שבשגרה.
\n",
+ " המוסכמות לגבי הצורך בשקיפות עבור אותם נתונים תופסים תאוצה, והשימוש בהם והניתוח שלהם עוזר לנו להגיע למסקנות מהר יותר מאי פעם.\n",
+ "
\n",
+ " גופים רבים מתרגלים לחשוף את הנתונים שלהם לציבור: החל בחברות הגדולות, דרך ארגוני שקיפות וכלה בארגונים ממשלתיים.
\n",
+ " אבל מה הם בכלל אותם נתונים, ואת מי הם משמשים?\n",
+ "
\n", + " ניתן דוגמאות לכמה מאגרי נתונים מעניינים:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "נסו לדמיין כמה מסקנות מעניינות מתכנת מנוסה יכול להפיק מבסיסי הנתונים הללו.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " וזה לא מסתיים כאן – ההתעסקות בנתונים הפכה להיות ממש ענף שלם!
\n",
+ " בעידן המודרני קשה להתחמק מהמושג \"Big Data\" (נְתוּנֵי עָתֵק), שמדבר על מאגרי מידע בלתי נתפסים בגודלם.
\n",
+ " אמנם מדעי הנתונים הוא תחום עיסוק עצום שנספיק לגעת רק בקצה־קצהו במסגרת הקורס, אבל הוא בהחלט עשיר ומרתק.
\n",
+ " הנה כמה דוגמאות מעניינות לשימושים שלו בעולם כיום:\n",
+ "
\n",
+ " במסגרת השבוע אמנם לא נעסוק בנתוני עתק, אבל בהחלט נעשה את צעדינו הראשונים בעבודה עם מאגרי מידע.
\n",
+ " נלמד לקרוא מידע, לאחסן אותו, ליצור מאגרי מידע בעצמנו ולהפיק ממידע שכזה תועלת.\n",
+ "
\n",
+ "חברנו החביב הרשל מנהל בר בכוכב אורנוס המרוחק.
\n",
+ " לבר לקוחות רבים, אך לאחרונה האיגוד ההיפר־גלקטי מקשה עליו בניהול המקום עם תקנות משונות.\n",
+ "
\n",
+ " משהתרגל למצב, הרשל מנסה להגדיל את רווחיו מהמכירות בבר.
\n",
+ " הוא מתכוון לשפר את החשיפה לקהל חדש, ובד בבד גם להדק את התפריט שמוגש ללקוחות.
\n",
+ " כמו שאתם ודאי מתארים לעצמכם, להרשל יש מערכת המאגדת את הזמנותיהם של כל לקוחותיו.
\n",
+ " המידע מסודר בצורה טבלאית המכילה את זמן ההזמנה מדויק, את המוצר שהוזמן ומחירו במערכת.\n",
+ "
\n",
+ " השתמשו בקובץ resources/cocktails.csv כדי לענות על השאלות הבאות:
\n",
+ "
\n",
+ " אחרי שריעננתם קצת את הזיכרון שלכם בפייתון ועבדתם קצת עם נתונים,
\n",
+ " ודאי חשתם בסרבול שבהתעסקות עם הנתונים – במיוחד אם לא השתמשתם במודולים חיצוניים.
\n",
+ " השגיאות במהלך ניסיון הטיפול בקובץ, הסרבול שבהפרדה לשדות והצורך לכתוב קוד ארוך –
\n",
+ " כולם הופכים את ההתעסקות עם מידע מהסוג הזה לפחות כייפי.\n",
+ "
\n", + " עוד לא הכנסנו לתמונה את שאר הבעיות שבעבודה עם קבצים כאלו:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " למזלנו, בשנות ה־80 נוצרה טכנולוגיה פופולרית שמטרתה לפתור את הבעיות הללו – ועוד בעיות רבות נוספות.
\n",
+ " לטכנולוגיה קוראים מסדי נתונים רלציוניים (Relational databases),
\n",
+ " והיא טכנולוגיה פופולרית מאוד שעוזרת לנו לאחסן נתונים בצורה טבלאית, לנהל אותם ולשאול עליהם שאלות.
\n",
+ " כמה היה נוח לו הייתה להרשל טכנולוגיה שכזו, הא?\n",
+ "
\n",
+ " במקרה של הרשל, היינו כנראה מאחסנים את הנתונים תחת טבלה שנקראת drinks_sold.
\n",
+ " כל שורה הייתה מייצגת ישות – אוסף סדור אחד של פריטים, רכישה אחת של משקה.
\n",
+ " במקרה שלנו אוסף סדור יחיד מכיל את: מספר ההזמנה, שם המזמין, תאריך ההזמנה, שם המשקה ומחירו. \n",
+ "
\n",
+ " בשבוע הקרוב אנחנו הולכים להתעסק עם טבלאות שמכילות נתונים שכאלו.
\n",
+ " מסד נתונים יורכב מטבלה אחת או יותר, ובכל טבלה שכזו יהיו לנו שורות רבות.
\n",
+ " כל שורה מייצגת ישות אחת (פריט אחד) באוסף הנתונים שלנו.\n",
+ "
\n",
+ " בהקבלה לפייתון, אפשר לדמיין שורה בטבלה כ־tuple:
\n",
+ " רצף של כמה נתונים שבאים אחד אחרי השני, שלכל אחד מהם יש טיפוס מסוים וערך מסוים.
\n",
+ " אם נרצה לדייק אפילו יותר, נוכל לייצג שורה בטבלה כמילון:
\n",
+ " עבור כל עמודה בטבלה יהיה מפתח תואם במילון, ואליו ייכנס הערך שנמצא באותה עמודה. \n",
+ "
\n", + " אחסון השורה הראשונה כ־tuple יראה כך:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "metadata": {}, + "outputs": [], + "source": [ + "row1 = (1, \"Ronit Feldman\", datetime.datetime(2019, 11, 9), \"Crazy Earl\", 57)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " ומילון שנוצר מהשורה הראשונה יראה כך:\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "metadata": {}, + "outputs": [], + "source": [ + "row1 = {\n", + " \"id\": 1,\n", + " \"name\": \"Ronit Feldman\",\n", + " \"order_date\": datetime.datetime(2019, 11, 9),\n", + " \"drink_name\": \"Crazy Earl\",\n", + " \"drink_price\": 57,\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " סביר שעד כה דמיינתם את הנתונים כטבלה ב־Excel (או Google Sheets, אנחנו לא שופטים).
\n",
+ " נראה שצדקתם! זה באמת דומה מאוד.\n",
+ "
\n",
+ " אם כן, אוסף של שורות כאלו הוא טבלה, וכמה טבלאות יחדיו יוצרות לנו מסד נתונים של ממש.
\n",
+ " נוכל לדמיין טבלה כרשימה של tuple־ים, או כרשימה של מילונים.\n",
+ "
\n",
+ " יצגו את טבלת drinks_sold הירוקה שהופיעה למעלה בעזרת מבנה נתונים פייתוני.
\n",
+ " השתמשו במילונים לשם כך, ושמרו את מבנה הנתונים שמאגד אותם במשתנה ששמו drinks_sold.\n",
+ "
\n",
+ " הרכיב המעניין ביותר בכל מסד נתונים הוא \"המנוע\", או מערכת הניהול של מסד הנתונים (DBMS).
\n",
+ " זו התוכנה שעובדת מאחורי הקלעים, ומאפשרת לכם לערוך את המידע במסד הנתונים ולשאול שאלות לגביו.
\n",
+ " חברות גדולות משקיעות משאבים רבים כדי לייעל ולשפר את הביצועים של המנועים הללו.
\n",
+ " לבינתיים, אנחנו נשתמש במנוע קליל שנקרא SQLite.\n",
+ "
\n",
+ " אחזור מידע ממסדי הנתונים מתבצע באמצעות ניסוח שאלות בשפה שנקראת SQL.
\n",
+ " צעדינו הראשונים יעסקו בשימוש בשפה לצורך תחקור הנתונים הקיימים במסד הנתונים.
\n",
+ " בעתיד, נרחיב את יכולותינו כך שנוכל גם לעדכן, להוסיף או למחוק את הנתונים במסד.
\n",
+ " פקודת אחזור או שינוי של מידע במסד הנתונים שנכתבה ב־SQL נקראת \"שאילתה\" (או query).
\n",
+ " במהלך יחידת הלימוד הזו נלמד לנסח שאילתות כדי לאחזר או לשנות מידע, ונריץ אותן בעזרת המנוע של מסד הנתונים.\n",
+ "
\n", + " השאילתות שנלמד לבצע יחזירו מידע, גם הן, בצורה טבלאית.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " לצורך השבוע, הכנו לכם מסד נתונים פשוט עליו תוכלו להתנסות.
\n",
+ " בנינו לכם גם אתר שיאפשר לכם להעלות מסד נתונים ולהריץ עליו שאילתות, בכתובת sql.pythonic.guru.
\n",
+ " בהמשך הפרק נעבור להשתמש בתוכנות שיותר מתאימות למשימה הזו.\n",
+ "
\n",
+ " כדי להתחיל לעבוד, הורידו מפה את קובץ מסד הנתונים לדוגמה שיצרנו עבורכם.
\n",
+ " היכנסו לאתר והעלו לשם את הקובץ שהורדתם.
\n",
+ " זהו מסד הנתונים שעליו נעבוד. הוא מכיל מידע מעניין על סרטים, כולל דירוגים, הצוות שהשתתף ביצירתם וכדומה.
\n",
+ " מייד בסיום ההעלאה יפתח לכם חלון שבראשו תיבת טקסט – בעזרתה תעלו את השאילתות שלכם, ובתחתיתו טבלה – בה יופיעו תוצאות השאילתה.\n",
+ "
SELECT
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n", + " נתחיל מלהכניס לתיבת הטקסט שאילתה פשוטה שתציג לנו את שמות כל הסרטים שקיימים במאגר:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT title\n", + "FROM movies;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כיוון שמאגר הנתונים שלנו גדול במיוחד, הממשק הגרפי ישאל אתכם אם אתם רוצים להציג את כל הנתונים.
\n",
+ " בחרו \"לא\" כדי למנוע קריסה של הלשונית.\n",
+ "
\n",
+ " בשאילתה שתי מילות מפתח: SELECT
ו־FROM
.
\n",
+ " אחרי מילת המפתח FROM
נכתוב שם של טבלה שממנה נרצה לשלוף את הנתונים.
\n",
+ " אחרי מילת המפתח SELECT
נכתוב את שם העמודה שממנה נרצה לשלוף את הנתונים.
\n",
+ " את השאילתה נסיים בנקודה פסיק.\n",
+ "
\n", + " אפשר לתרגם את השאילתה באופן מילולי ל\"בחר את כל הכותרות מתוך הטבלה סרטים\".\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " נסו להכניס את השאילתה לאתר ולהריץ. מצליחים לזהות סרטים מוכרים?
\n",
+ "
\n",
+ " נהוג לכנות את החלק בשאילתה שקשור ל־SELECT
בשם \"פסוקית ה־SELECT
\" (באנגלית: SELECT
clause).
\n",
+ " באותה צורה, נהוג לכנות את החלק בשאילתה שקשור ל־FROM
בשם \"פסוקית ה־FROM
\" (באנגלית: FROM
clause).\n",
+ "
\n",
+ " כיצד נדע אילו טבלאות קיימות במסד הנתונים, ואילו עמודות קיימות בכל טבלה?
\n",
+ "
\n",
+ " אם אתם מכירים את שם הטבלה ורוצים לגלות מה העמודות שלה, יש טריק זריז וכייפי.
\n",
+ " במקום לבקש פרטים על עמודה מסוימת, אפשר לכתוב אחרי מילת המפתח SELECT
את הסימן *
.
\n",
+ " הסימן הזה יגיד למסד הנתונים שאנחנו רוצים לאחזר את כל העמודות בטבלה.\n",
+ "
\n", + " נסו אצלכם!\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT *\n", + "FROM movies;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " מה שמות העמודות בטבלה principals?\n", + "
\n", + "\n",
+ "אבל מה אם אנחנו לא יודעים אילו טבלאות יש לנו במסד הנתונים?
\n",
+ " עומדות בפנינו עוד כמה אפשרויות:\n",
+ "
\n",
+ " לפעמים יופיעו באינטרנט דוגמאות לשאילתות SQL שנראות מעט שונה ממה שתלמדו פה.
\n",
+ " השוני נובע מכך שמנועים שונים של מסדי נתונים משתמשים לפעמים בתחביר מעט שונה של SQL,
\n",
+ " אבל בשורה התחתונה – זו אותה הגברת בשינוי אדרת.\n",
+ "
\n",
+ " זה הזמן להציג לכם כיצד נראה מסד הנתונים שיצרנו עבורכם!
\n",
+ " זה הולך להיראות קצת מוגזם בהתחלה, אבל אין צורך להיבהל. נסביר כל דבר כשיגיע הזמן הנכון.\n",
+ "
\n",
+ " קחו את הזמן לחקור קצת את מסד הנתונים באמצעות SELECT
ו־FROM
.
\n",
+ "
SELECT
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ " אולי גיליתם כבר בעצמכם שאפשר לאחזר כמה עמודות מטבלה כלשהי במכה.
\n",
+ " נכתוב שאילתה שתחזיר לנו שתי עמודות מטבלת movies: את id ואת title.
\n",
+ " כדי לעשות זאת נפריד בין שמות העמודות באמצעות פסיק:\n",
+ "
\n",
+ " נוסיף שאפשר גם לשנות את שמן של עמודות ששלפתם עליהן, בעזרת מילת המפתח AS
.
\n",
+ " מייד אחרי העמודה שאת שמה אתם רוצים לשנות, הוסיפו את מילת המפתח AS
,
\n",
+ " ואחריה כתבו את השם החדש שתרצו לתת לעמודה:\n",
+ "
\n",
+ " בדוגמה שלמעלה השתמשנו במילת המפתח AS
.
\n",
+ " בעזרתה, הצגנו את העמודה id שבטבלת movies תחת השם החדש imdb_id.
\n",
+ " את title מאותה טבלה הצגנו תחת השם החדש english_name.
\n",
+ " יכולנו גם לבחור לתת רק לאחת מהעמודות שם חדש, או, כפי שעשינו קודם, לבחור לשלוף אותן בשמן המקורי.\n",
+ "
\n",
+ " שאילתות SELECT
ככלל לא משנות מידע במסד הנתונים עצמו.
\n",
+ " הן רק מחזירות פלט לפי השאילתה שהעברתם למסד הנתונים.\n",
+ "
SELECT
), תווים מיוחדים או מילים בעברית שמתועתקות לאנגלית.\n",
+ " שליפה של נתונים רבים מדי עלולה להיות בעוכרינו.
\n",
+ " היא דורשת ממסד הנתונים לבצע פעולות מיותרות שלוקחות זמן,
\n",
+ " והעברת הנתונים באינטרנט ככל הנראה תאט מאוד את המהירות שבה נוכל לראות את תוצאות השאילתה.\n",
+ "
\n",
+ " לשם כך באה לעזרתינו מילת המפתח LIMIT
.
\n",
+ " היא מאפשרת לנו להגיד כמה רשומות נרצה לאחזר בעזרת השאילתה שכתבנו.
\n",
+ " המילה הזו תבוא בסוף השאילתה, ואחריה נציין את מספר הרשומות המירבי שנרצה לאחזר.\n",
+ "
\n", + " ננסה, לדוגמה, לשלוף מטבלת movies רק 100 סרטים:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT *\n", + "FROM movies\n", + "LIMIT 100;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " תוכלו להיווכח בעצמכם בשינוי המשמעותי במהירות שבה התוצאות חוזרות.
\n",
+ "
\n",
+ " בחלק מהניבים השונים של SQL, אתם תראו שאנשים משתמשים במילה TOP
במקום במילה LIMIT
.
\n",
+ " זו אותה הגברת בשינוי אדרת.\n",
+ "
\n",
+ " מילת המפתח LIMIT
לא תחזיר בהכרח את אותן רשומות בכל פעם.\n",
+ "
\n",
+ " כתבו שאילתה שמציגה 100 אנשי מפתח מטבלת principals.
\n",
+ " השאילתה תציג את כל העמודות בטבלה.
\n",
+ " כתבו את השאילתה כך שבתוצאות העמודה characters תופיע בשם personas.\n",
+ "
\n",
+ " נסו לפתור את התרגיל בלי להציץ בתיעוד שסיפקנו למעלה.
\n",
+ "
\n",
+ " אפשר יחסית בקלות ליצור עמודה נוספת שתופיע כחלק מתוצאות השאילתה שלנו.
\n",
+ " דוגמה די מטופשת תהיה, לדוגמה, להוסיף לצד כל העמודות של סרט מסוים עמודה שבה תמיד מופיעה הספרה 1.\n",
+ "
\n", + " העמודה הזו תקרא כברירת מחדל \"1\", אבל כמו שלמדנו קודם, נוכל לתת לעמודה הזו שם:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT *, 1 AS is_movie\n", + "FROM movies;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " הדוגמה הזו אולי לא מחוכמת במיוחד, אבל היא הקדמה טובה להרבה דברים מעניינים שאפשר לעשות.
\n",
+ " יצירת עמודה חדשה מאפשרת לנו גמישות בהצגת הפלט מהשאילתה שלנו.\n",
+ "
\n", + " ננסה להריץ ביטוי מעט יותר מורכב:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT *, 17 + 5 * 5 AS universal_answer\n", + "FROM movies;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " ונראה שמסד הנתונים יודע להתמודד גם עם ביטויים אריתמטיים.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " עד כה המשחקים הללו נראים נחמדים, אבל לא באמת מועילים.
\n",
+ " הכיף האמיתי מתחיל כשאנחנו גוזרים את העמודה המלאכותית החדשה שלנו מתוך נתונים קיימים.\n",
+ "
\n",
+ " לדוגמה, בטבלה movies קיימת העמודה duration.
\n",
+ " כיצד הייתם יוצרים עמודה נוספת בשם duration_in_hours שבה נראה כמה שעות (ולא דקות) אורך הסרט?
\n",
+ " נסו להשיג את התוצאה בעצמכם, התשובה נמצאת בתא שפה למטה.
\n",
+ " הפתרון לאו דווקא מובן מאליו, ודורש מעט אינטואיציה טכנולוגית.\n",
+ "
\n", + " כך אנחנו עשינו זאת:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT *, duration / 60.0 AS duration_in_hours\n", + "FROM movies;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בתא שלמעלה השתמשנו בעמודה duration.
\n",
+ " חילקנו את ערכה ב־60 וקראנו לערך שנוצר duration_in_hours.
\n",
+ " מסד הנתונים לקח בכל שורה את הערך שבעמודה duration, חילק אותו ב־60 והוסיף אותו כעמודה בשם שבחרנו.\n",
+ "
\n",
+ " כל פעם שאנחנו מכניסים ביטוי כשדה חדש ב־SELECT
,
\n",
+ " נוכל לדמיין שמסד הנתונים עובר שורה שורה ומחשב את הביטוי עבור השורה המסוימת הזו –
\n",
+ " ממש כמו בלולאה בפייתון!\n",
+ "
\n",
+ " עד כה התייחסנו לעמודות חדשות שבחרנו להוסיף בפסוקית ה־SELECT
, בשם \"עמודות מלאכותיות\".
\n",
+ " השם שלהן בעגה המקצועית הוא \"עמודות מחושבות\" (Computed columns).\n",
+ "
WHERE
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ " צורך בסיסי בעבודה עם כמות גדולה של נתונים הוא להתמקד באוסף נתונים שרלוונטי למשימה שלנו.
\n",
+ " לכן, נצטרך הרבה פעמים כלי שעוזר לנו לקבל רק שורות שעונות על תנאי מסוים.\n",
+ "
\n",
+ " במקרים כאלו, מילת המפתח WHERE
יכולה לעזור לנו מאוד.\n",
+ "
\n",
+ " נתחיל בשליפת כל העמודות של טבלת האנשים המפורסמים במסד הנתונים שלנו.
\n",
+ " נסו בעצמכם – הרשומות אודות אותם אנשים נמצאות תחת הטבלה names.\n",
+ "
\n", + " בשלב הזה אתם אמורים להרגיש בנוח עם השליפה הזו:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT *\n", + "FROM names;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " נניח שאנחנו מעוניינים לקבל את כל האושיות שגבוהם גדול מ־2 מטר.
\n",
+ " נוכל להשתמש במילת המפתח WHERE
כדי לבצע את הסינון הזה:\n",
+ "
\n",
+ " בתא האחרון הוספנו את פסוקית ה־WHERE
לסוף השאילתה.
\n",
+ " הפסוקית הזו מאפשרת לנו לסנן את הנתונים לפי תנאי שיכתב בה.
\n",
+ " לדוגמה, אנחנו ביקשנו להחזיר רק שורות שבהן הערך שמופיע בעמודה height יהיה לפחות 200.\n",
+ "
\n",
+ " אפשר לנסח את השאילתה הזו במילים:
\n",
+ " בחר (SELECT
) את כל העמודות, מתוך (FROM
) הטבלה names, שבהן (WHERE
) ערך העמודה height גדול או שווה ל־200.\n",
+ "
\n",
+ " כמו שאתם יכולים לנחש, סימני השיוויון שאתם מכירים מפייתון תופסים כאן: <=
, <
, >
ו־>=
.
\n",
+ " היוצאים מן הכלל הם הסימן =
שמחליף את הסימן ==
, והסימן <>
שלרוב מופיע במקום !=
.
\n",
+ " שימוש בסימן ==
ממש לא חוקי ויזרוק לכם שגיאה, ולעומתו הסימן !=
אומץ ברוב הניבים של SQL ויעבוד לכם כמעט תמיד.\n",
+ "
\n",
+ " SQL, כמו פייתון, רגישה לאותיות גדולות וקטנות כשמבקשים ממנה להשוות בין מחרוזות.
\n",
+ " בניגוד לפייתון, אפשר להשתמש רק בגרש ('
) כדי לבנות מחרוזת. גרשיים (\"
) יקיפו שם של טבלה או עמודה.\n",
+ "
\n",
+ " בשנת 1994 יצא סרט דרמה אפי הונגרי מצליח בשם \"הטנגו של השטן\" (Sátántangó).
\n",
+ " מה אורכו של הסרט?\n",
+ "
\n", + " מעניין לדעת שמנוע ה־SQL לא מריץ את הפסוקיות בסדר שבו הן כתובות בשאילתה.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " ננתח את סדר הרצת הפסוקיות בשאילתה הפשוטה הבאה:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT *\n", + "FROM names\n", + "WHERE height >= 200\n", + "LIMIT 10;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " המנוע קודם יגש לטבלה ממנה הוא רוצה לשלוף (names), אותה ציינו בפסוקית ה־FROM
.
\n",
+ " משם הוא קודם כל יברור את הנתונים לפי התנאי (height >= 200
) שסיפקתם לו בפסוקית ה־WHERE
,
\n",
+ " לאחר מכן הוא יבחר את כל העמודות הרלוונטיות להצגה (*
), שמופיעות בפסוקית ה־SELECT,
\n",
+ " ולבסוף הוא יחזיר רק את כמות התוצאות שכתובה ב־LIMIT
(במקרה שלנו – 10
).\n",
+ "
\n",
+ " חלק ממנועי ה־SQL יתריעו על שגיאה כשתנסו להריץ שאילתה שלא תואמת את הסדר הזה.
\n",
+ " לדוגמה, כמעט כל מנוע שהוא לא SQLite (המנוע בו אנחנו משתמשים כרגע) יתריע על שגיאה בשאילתה הבאה:\n",
+ "
\n",
+ " כשמנוע ה־SQL מגיע לפסוקית ה־WHERE
, הוא טרם הריץ את פסוקית ה־SELECT
.
\n",
+ " לכן הוא לא מכיר את העמודה המחושבת meters, ותיזרק לכם שגיאה.
\n",
+ " SQLite פותר את הבעיה הזו בכך שמאחורי הקלעים הוא מעתיק את הביטויים בעמודות מחושבות לתוך פסוקית ה־WHERE
.\n",
+ "
\n",
+ " ודאי לא יפתיע אתכם שאופרטורים לוגיים קיימים גם ב־SQL –
\n",
+ " מדובר על אותם AND
, OR
ו־NOT
שהתרגלנו לכתוב בביטויים בוליאניים בפייתון.
\n",
+ " נוכל לכתוב אותם בפסוקיות WHERE
כדי ליצור תנאים מורכבים ומעניינים יותר.\n",
+ "
\n", + " כך, לדוגמה, נוכל לבקש סרט אחד שיצא בשנות ה־50:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT *\n", + "FROM movies\n", + "WHERE year >= 1950 AND year < 1960 \n", + "LIMIT 1;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בשלב הזה אנחנו מניחים שהעמקתם ואתם בקיאים בניסוח ביטויים בוליאניים,
\n",
+ " ולכן לא נתעכב על הנושא הזה במחברת הזו.\n",
+ "
\n",
+ " תחביבו של סר. אטזר הוא לראות סרטים בשפת המקור.
\n",
+ " חפשו עבורו סרטים ששמם בשפת המקור שונה מהשם הפופולרי שלהם.\n",
+ "
\n",
+ " חשוב לציין שסר. אטזר הוא בחור עסוק למדי, ולכן יוכל לצפות רק ב־5 סרטים שאורך כל אחד מהם הוא פחות משעה וחצי.
\n",
+ " שלפו את הכמות הנכונה של סרטים שמתאימים לדרישותיו של סר. אטזר.\n",
+ "
LIKE
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ " אופרטור השוויון ב־SQL (=
), כמו בפייתון, מניח שהנתון שמופיע מצידו הימני שווה לחלוטין לנתון שמופיע בצידו השמאלי.
\n",
+ " בשימוש שוטף במסדי נתונים, לא יעבור זמן רב עד שנגלה שהכלי הזה לא מספיק חזק כדי לענות על כל הצרכים שלנו.
\n",
+ "
\n",
+ " מה אם נרצה לחפש בתוך מלל ב\"בערך\", ולא בהכרח לחפש שיוויון מוחלט?
\n",
+ " זה קורה לעיתים כל כך תכופות!\n",
+ "
\n", + " הנה כמה מצבים כאלו שחשבנו עליהם:\n", + "
\n", + "\n", + "\n",
+ " מטרת אופרטור ההשוואה LIKE
היא לפתור עבורנו את הבעיה הזו.
\n",
+ " הוא יכתב כחלק מפסוקית ה־WHERE
, ויאפשר לנו לחפש תבניות.
\n",
+ " נראה דוגמה לשימוש בו, ואז נסקור כיצד להשתמש בו במקרים נוספים:\n",
+ "
\n",
+ " בתא שלמעלה השתמשנו באופרטור ההשוואה LIKE
–
\n",
+ " דרשנו לקבל את כל השורות בהן מתקיים התנאי title LIKE '%Sherlock%'
.\n",
+ "
\n",
+ " כשאנחנו משתמשים באופרטור ההשוואה LIKE
אנחנו מבקשים לבחון האם הביטוי בצד שמאל תואם את התבנית שנמצאת בימין.
\n",
+ " במקרה שלנו, ביקשנו לבדוק בכל שורה אם הערך שב־title תואם את התבנית '%Sherlock%'
. \n",
+ "
\n", + " הכללים לניסוח תבנית פשוטים מאוד (פה קורה הקסם!):\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "%
מחליף 0 או יותר תווים._
מחליף תו אחד בדיוק.\n",
+ " כך שהתבנית שניסחנו אומרת \"כל מחרוזת הכוללת את המילה Sherlock\".
\n",
+ " באותה מידה, יכולנו להשתמש ב־'Sherlock%'
כדי לקבל רק את השורות שמתחילות במילה Sherlock.\n",
+ "
\n", + " תוכלו לשלוף את כל הסרטים שאורך שמם הוא בדיוק 5 תווים?\n", + "
\n", + "LIKE
הוא NOT LIKE
.\n",
+ " \n",
+ " מבחינה טכנית, אפשר להשתמש ב־LIKE
בשביל השוואה פשוטה – אם במקום תבנית נכתוב לימינו מחרוזת רגילה.
\n",
+ " זה נחשב לא מנומס ואין סיבה טובה לעשות את זה.\n",
+ "
NULL
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ " NULL
הוא קבוע מיוחד, והוא קיים כדי לייצג חוסר בערך במסד הנתונים שלנו.
\n",
+ " הוא מזכיר למדי את הערך הפייתוני None
, שגם הוא מייצג ריק, שום דבר.\n",
+ "
\n",
+ " למרבה ההפתעה, המצב של הופעת הערך NULL
הוא נפוץ יחסית.
\n",
+ " חשבו על כל הפעמים שיתכן שסיפקו לנו מידע חסר, או על המצבים שבהם אין מידע למלא בעמודה:\n",
+ "
NULL
.\n",
+ " חשוב לחדד ש־NULL
לא מציין אפס – הוא מציין שהמידע פשוט לא נמצא שם.
\n",
+ " אם אני מריץ שאילתה שמטרתה לבדוק כמה מקררים יש במלאי ומקבל בחזרה את התשובה NULL
,
\n",
+ " אני יודע שלמרות שיש מקררים במסד הנתונים, לא עודכן המלאי עבורם.
\n",
+ " NULL
לא אומר שאין מקררים במלאי – מצב שכזה היה מיוצג על ידי הערך 0.\n",
+ "
\n",
+ " הבעיה מתחילה בכך שהערך NULL
ב־SQL מתנהג קצת מוזר.\n",
+ "
\n", + " ננסה להריץ שאילתה שמחזירה את כל הסרטים שלא ידוע מה היה תקציב ההפקה שלהם:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT *\n", + "FROM movies\n", + "WHERE budget = NULL;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " למרבה ההפתעה, השאילתה הזו לא מחזירה לנו אף סרט.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " טוב, אם כך, כנראה שאנחנו יודעים מה היה תקציבם של כל הסרטים שבמסד הנתונים שלנו. כמה נפלא.
\n",
+ " נשלוף את כל הסרטים שבהכרח ידוע לנו מה היה תקציב ההפקה שלהם:\n",
+ "
\n",
+ " גם הפעם השאילתה לא החזירה לנו שורות כלל! איזה משונה, אנחנו יודעים שבטבלה יש סרטים!
\n",
+ " הסרטים שתקציב ההפקה שלהם = NULL
והסרטים שתקציב ההפקה שלהם <> NULL
, שני ההפכים המוחלטים, לא מחזירים אף שורה.
\n",
+ " מה מתרחש פה?\n",
+ "
\n",
+ " מתברר שב־SQL נוסף לערכים הלוגיים שאנחנו רגילים אליהם (True
, False
), ישנו ערך לוגי שלישי: Unknown
.
\n",
+ " כך הוגדר שתוצאת בדיקת השוויון ל־NULL
היא תמיד Unknown
. הוא הדין גם לגבי בדיקת אי־שוויון.\n",
+ "
\n",
+ " כיוון שמנוע ה־SQL יחזיר רק שורות שעבורן הביטוי הלוגי בפסוקית ה־WHERE
שקול ל־True
,
\n",
+ " בדיקת שוויון רגילה עם NULL
, שתוצאתה Unknown
, תגרום למנוע לא להחזיר את השורות שציפינו לקבל.\n",
+ "
\n",
+ " נראה לדוגמה את המקרה המובהק הבא.
\n",
+ " על פניו, כל ערך שווה לעצמו ולכן אמורות לחזור לנו כל השורות מהטבלה.
\n",
+ " בפועל, כיוון שהשוואה ל־NULL
תמיד תחזיר Unknown
, מנוע ה־SQL לא יחזיר לנו אף שורה מהטבלה:\n",
+ "
\n",
+ " אז מה אפשר לעשות אם נרצה בכל זאת לבצע השוואה ל־NULL
?
\n",
+ " נוכל להשתמש באופרטור ההשוואה IS
ולבדוק האם ערך מסוים IS NULL
או IS NOT NULL
.\n",
+ "
\n", + " נבדוק, אם כן, אילו בעלי תפקידים בכירים שיחקו גם דמויות:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT *\n", + "FROM principals\n", + "WHERE characters IS NOT NULL;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " עכשיו, כשאתם יודעים איך NULL
מתנהג, חשוב להיזהר ממצבים שבהם הוא עלול להציק לנו.
\n",
+ " כשנכתוב שאילתה לאחזור כל האנשים שגובהם הוא לא בדיוק 2 מטר,
\n",
+ " נצטרך לציין במפורש את המקרה בו הגובה שלהם הוא NULL
:\n",
+ "
\n", + " במחברת זו צללנו לראשונה לעולם של SQL ולמדנו את היסודות לכתיבת שאילתות.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " הבנו מה זה מסד נתונים ומתי משתמשים בו, וסקרנו לראשונה את הרעיון של אחזור מידע ממסד הנתונים.
\n",
+ " ראינו כיצד לאחזר מידע די מעניין בעזרת שאילתות פשוטות יחסית, שמורכבות מהפסוקיות SELECT
, FROM
, WHERE
ו־LIMIT
.\n",
+ "
\n",
+ " למדנו גם על אופרטורי השוואה וביניהם LIKE
, ופגשנו שוב את AND
, OR
ו־NOT
– הפעם בהקשרי SQL.
\n",
+ " קינחנו בלהבין מי זה NULL
ועל המוזרויות שבהתעסקות איתו – ושמחייבות אותנו להשתמש ב־IS NULL
ולא לבצע השוואה ישירה מולו.\n",
+ "
\n",
+ " החזירו את כל הסרטים שיש בהם 2 כספרה, ללא ספרות או תווים נוספים שצמודים אליה.
\n",
+ " אל תחזירו סרטים שבהם יש את הספרה 2 כחלק ממספר גדול יותר. \n",
+ "
\n", + " לדוגמה: החזירו את הסרט Shrek 2, אך לא את הסרט 12 Angry Men.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### דירוג משוכלל" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " \"דירוג משוכלל\" הוא הדירוג של סרט, לאחר שמוסיפים לו:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " לדוגמה, סרט שדורג 4.2 על ידי 120,000 מצביעים, ידורג בפועל לפי הנוסחה הבאה:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "$$rating = 4.2 + (0.2 \\cdot 1) + (0.01 \\cdot 2) + (0.0005 \\cdot 0) = 4.420$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " בהינתן שדירוג יכול לנוע מ־0 ועד 10 נקודות, האם שיטת הדירוג הזו יוצרת סרטים שהדירוג שלהם לא תקין?\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### And the science gets done" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " החזירו את שמותיהם של 3 בעלי תפקידים שעדיין בחיים ולהם לפחות תשעה ילדים.\n", + "
" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/content/week13/2_Order_and_functions.ipynb b/content/week13/2_Order_and_functions.ipynb new file mode 100644 index 0000000..9ba8da8 --- /dev/null +++ b/content/week13/2_Order_and_functions.ipynb @@ -0,0 +1,1125 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " עד כה למדנו על הפסוקיות SELECT
, FROM
, WHERE
ו־LIMIT
ועל סדר הרצתם על ידי מנוע ה־SQL.
\n",
+ " דיברנו גם על אופרטורי השוואה, אופרטורים לוגיים ועל NULL
,
\n",
+ " ובעצם למדנו לראשונה מה הם מסדי נתונים ואיך כותבים שאילתות.\n",
+ "
\n", + " במחברת זו נלמד:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " בואו נתחיל!\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##ORDER BY
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ " כיוון שסידור של נתונים היא פעולה שאורכת זמן רב, כברירת מחדל מסדי נתונים לא מחזירים תוצאות לפי סדר מסוים.
\n",
+ " ובכל זאת, לפעמים עולה הצורך לקבל את התוצאות כשהן מסודרות:\n",
+ "
\n",
+ " אז איך נבקש ממסד הנתונים לסדר עבורינו את התוצאות?
\n",
+ " נוסיף פסוקית ORDER BY
לשאילתה, ואחריה נציין את שם העמודה לפיה אנחנו רוצים לסדר:
\n",
+ "
\n",
+ " בשאילתה האחרונה, ביקשנו לסדר את הישויות בטבלת names לפי מספר הילדים שיש ברשותם.
\n",
+ " נוכל גם להוסיף פסוקית LIMIT
כדי להגביל את כמות התוצאות שיחזרו:\n",
+ "
\n",
+ " כך, בשאילתה האחרונה, הוחזרו לנו רק 5 אנשים ממסד הנתונים.
\n",
+ " אלו האנשים שכמות הילדים שהביאו לעולם היא הנמוכה ביותר.
\n",
+ " זה המצב כיוון שמסדי נתונים מסדרים את התוצאות, כברירת מחדל, בסדר עולה – מהנמוך לגבוה.\n",
+ "
\n",
+ " נוסיף גם פסוקית WHERE
, שתחזיר עבורינו רק אנשים שעודם בחיים.\n",
+ "
\n",
+ " עכשיו, כשאנחנו יודעים איך נראית שאילתה שבנויה מכל הפסוקיות שלמדנו,
\n",
+ " נרענן את סדר הרצת הפסוקיות במנוע ה־SQL:\n",
+ "
\n",
+ " מה שמו של בעל התפקיד הנמוך ביותר במסד הנתונים שלנו? מה גובהו?
\n",
+ " לפני שתפתרו, חישבו על המוקש שצריך לנטרל בכתיבת שאילתה שכזו.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n", + " כדי לפתור את השאלה למעלה, נפרק אותה למרכיביה.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "FROM
: מאיפה נשלוף את המידע? – מהטבלה שממנה ניקח את הנתונים, names.SELECT
: אילו נתונים נבחר מהטבלה? – את שם בעל התפקיד ואת גובהו.ORDER BY
: השאלה מבקשת את \"הגובה הנמוך ביותר\", ולכן רומזת לנו לסדר לפי העמודה height.LIMIT
: כמה רשומות נרצה לשלוף? – אחת ויחידה.WHERE
: המוקש שחשוב לנטרל: נצטרך להתעלם מגבהים שהם NULL
ולבקש לא להחזיר אותם.\n", + " ננסח כשאילתה:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT height, name\n", + "FROM names\n", + "WHERE height IS NOT NULL\n", + "ORDER BY height\n", + "LIMIT 1;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " במחברת הקודמת למדנו שרוב המנועים של מסדי הנתונים לא מאפשרים להשתמש בעמודות מחושבות בפסוקית ה־WHERE
.
\n",
+ " האם אפשר להשתמש בעמודות מחושבות בפסוקית ה־ORDER BY
? מדוע? \n",
+ "
\n",
+ " כפי שלמדנו לפני כן, ברירת המחדל של ORDER BY
היא לסדר את העמודה מהתוצאה הקטנה ביותר לגדולה ביותר.
\n",
+ " אבל מה אם אנחנו רוצים לסדר את התוצאות בסדר הפוך?\n",
+ "
\n",
+ " למזלנו יש פתרון פשוט.
\n",
+ " אחרי שם העמודה בפסוקית ORDER BY
, נציין אם היא תסודר בסדר עולה או יורד.
\n",
+ " נבחר במילת המפתח DESC
(מלשון descending, יורד) אם נרצה שהנתונים בעמודה יסודרו מהגדול לקטן,
\n",
+ " או ASC
(מלשון ascending, עולה) אם נרצה להצהיר במפורש שהנתונים בעמודה יסודרו מהקטן לגדול. זו גם ברירת המחדל.\n",
+ "
\n", + " נבקש לסדר את שמות בעלי התפקידים לפי הגובה שלהם – מהגבוה לנמוך:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT name\n", + "FROM names\n", + "WHERE height IS NOT NULL\n", + "ORDER BY height DESC;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אם נחליט שלא לסנן החוצה את ערכי ה־NULL
, נוכל להחליט אם הם יופיעו ראשונים או אחרונים.
\n",
+ " בפסוקית ה־ORDER BY
, נכתוב NULLS FIRST
כדי שיוחזרו ראשונים, או NULLS LAST
כדי שיוחזרו אחרונים.\n",
+ "
\n",
+ " נשפר מעט את השאילתה שכתבנו זה עתה.
\n",
+ " בשביל פישוט הדוגמה הבאה, נסנן החוצה את הרשומות שמכילות גבהים לא הגיוניים, ונבקש לראות רק 100 תוצאות:\n",
+ "
\n",
+ " ואולי, אחרי מחשבה נוספת, יהיה הולם לתת כבוד לבעלי התפקידים המבוגרים יותר.
\n",
+ " מובן שכדי לסדר את האנשים לפי תאריך הלידה שלהם, נצטרך להשתמש ב־ORDER BY
, בצורה הבאה:\n",
+ "
\n",
+ " אבל מה אם נרצה לסדר את בעלי התפקידים לפי גובהם,
\n",
+ " ורק אם גובהם של 2 בעלי תפקידים או יותר זהה לתת עדיפות לזה שנולד קודם?
\n",
+ " מקרה שכזה מכריח אותנו להתייחס ל־2 עמודות בפסוקית ה־ORDER BY
: גם height וגם date_of_birth.\n",
+ "
\n",
+ " נכתוב את העמודות בפסוקית ה־ORDER BY
לפי החשיבות שלהן, ונפריד ביניהן בפסיק:\n",
+ "
\n", + " ובתוך השאילתה:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT * \n", + "FROM names \n", + "WHERE height IS NOT NULL \n", + " AND height < 250\n", + " AND date_of_birth IS NOT NULL \n", + "ORDER BY height DESC, date_of_birth\n", + "LIMIT 100;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " דרך טובה להסתכל על התוצאות שיחזרו היא \"סדר לפי גובה בסדר יורד – ואם הגובה זהה, סדר לפי תאריך הלידה\".\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " שימו לב שבשאילתה הזו בחרנו להשמיט את כל מי שאין לנו ידע לגבי גובהו או לגבי מתי הוא נולד.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### תרגיל ביניים: ועדת המדרוג" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כשבנו את מסד הנתונים עליו אתם עובדים, החליטו להסיר ממנו את כל הסרטים שדורגו מעט מדי פעמים.
\n",
+ " מצאו מה המספר הקטן ביותר של דירוגים שסרט היה צריך לקבל כדי להישאר במסד הנתונים.
\n",
+ " איזה סרט קיבל את הציון הממוצע הגבוה ביותר עם מספר דירוגים שכזה?\n",
+ "
\n", + " לשמחתנו הרבה, כמו שישנן פונקציות מובנות בפייתון – קיימות כאלו גם במסדי נתונים.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " ישנן לא מעט כאלה, וכמובן שלא נסקור את כולן –
\n",
+ " אבל בכל זאת בחרנו להתעכב על כמה שימושיות שיסייעו לנו בדוגמאות בהמשך.\n",
+ "
\n",
+ " רוב תחביר ה־SQL שלמדנו עד עכשיו די דומה בין מנועי ה־SQL השונים.
\n",
+ " בניגוד לתחביר שלמדנו, נדיר למצוא פונקציות ששמותיהם זהים בכל המנועים השונים.
\n",
+ " אנחנו ממליצים מאוד לרפרף בפרק הפונקציות שנמצא בתיעוד של המנוע בו אתם משתמשים.\n",
+ "
substr
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ " מטרת הפונקציה substr
היא להחזיר חלק מהתווים של מחרוזת מסוימת. היא עובדת באופן מעט שונה מחיתוך בפייתון.\n",
+ "
\n",
+ " הפונקציה מקבלת שלושה פרמטרים:
\n",
+ "
\n",
+ " לדוגמה, הביטוי substr(\"Piposh\", 1, 3)
יחזיר את המחרוזת \"Pip\", כיוון שהוא חותך 3 תווים החל מהמקום הראשון במחרוזת.\n",
+ "
\n", + "כמה דברים מעניינים מתוך התיעוד שכדאי לשים לב אליהם:\n", + "
\n", + "\n", + "\n",
+ " נסו לחזות מה יהיו תוצאותיהן של השאילתות הבאות.
\n",
+ " הריצו את השאילתות כדי לבדוק אם צדקתם.\n",
+ "
round
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ " הפונקציה round(X, Y)
מעגלת את המספר X ל־Y ספרות אחרי הנקודה העשרונית.
\n",
+ " אם הארגומנט Y לא מועבר לפונקציה, SQLite לא ישאיר ספרות אחרי הנקודה.\n",
+ "
\n", + "לדוגמה:\n", + "
\n", + "\n", + "round(3.141, 2)
יחזיר 3.14.round(3.1414, 2)
גם יחזיר 3.14.round(3.14, 2)
גם יחזיר 3.14.round(3.141)
יחזיר 3.round(3.848)
יחזיר 4.strftime
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ " מטרת הפונקציה strftime
היא להחזיר תאריך לפי תבנית כלשהי.
\n",
+ " זו פונקציה יחסית מורכבת, לכן מומלץ שלא להסתפק בסיכום שבמחברת, וכן לגשת לקרוא את עמוד התיעוד המלא.\n",
+ "
\n",
+ " הפרמטר הראשון שנעביר לפונקציה יהיה מחרוזת, הבנויה כתבנית שמתארת כיצד יורכב התאריך שנרצה להחזיר כפלט.
\n",
+ " האפשרויות הן:\n",
+ "
המחרוזת | \n", + "החלק שהיא מסמנת בתאריך | \n", + "דוגמה לערך חזרה אפשרי מ־strftime | \n",
+ "
---|---|---|
%Y | \n",
+ " שנה בת 4 ספרות | \n", + "1812 | \n", + "
%m | \n",
+ " חודש ב־2 ספרות | \n", + "12 (דצמבר) | \n", + "
%d | \n",
+ " יום בחודש ב־2 ספרות | \n", + "14 | \n", + "
%H | \n",
+ " שעה ב־2 ספרות, בין 00 ל־24 | \n", + "10 | \n", + "
%M | \n",
+ " דקות | \n", + "50 | \n", + "
%S | \n",
+ " שניות | \n", + "00 | \n", + "
%f | \n",
+ " שניות, כולל מילישניות עם 3 ספרות אחרי הנקודה | \n", + "03.141 | \n", + "
%s | \n",
+ " מספר השניות שעברו מאז 1970-01-01 | \n", + "1234567890 (ב־13 בפברואר 2009, בשעה 23:31:30) | \n", + "
\n",
+ " התבנית יכולה להיות בנויה מכמה חלקי זמן.
\n",
+ " המחרוזת '%Y-%m-%d'
, לדוגמה, מתארת תאריך המורכב משנה, חודש ויום, כשביניהם מפריד הסימן מקף.\n",
+ "
\n",
+ " הפרמטר השני הוא תיאור התאריך שאליו אנחנו רוצים להתייחס.
\n",
+ " לעיתים קרובות נראה שם את המחרוזת 'now' בתור הפרמטר, כדי להתייחס לתאריך הנוכחי.
\n",
+ "
\n", + " נסו בעצמכם:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT strftime('%Y-%m-%d %H:%M:%S', 'now') AS current_date;\n", + "-- Output: 2020-11-05 00:34:10\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " הטריק המגניב בפונקציה הזו הוא שאפשר להעביר ארגומנטים נוספים (ראו modifiers בתיעוד) כדי לזוז קדימה ואחורה בזמן.
\n",
+ " לדוגמה:\n",
+ "
\n",
+ " יחזיר לי את היום אחרון בחודש הנוכחי.
\n",
+ " זה עובד כיוון ש־SQLite עקב אחרי ההוראות שנשלחו כפרמטרים ל־strftime
:
\n",
+ " מצא את התאריך כרגע, זוז ממנו לתחילת החודש, קח חודש אחד קדימה ותפחית מזה יום.\n",
+ "
\n",
+ " הציגו את 10 הסרטים ששמם הוא הארוך ביותר במסד הנתונים.
\n",
+ " ודאו שהסרט הראשון שמוצג הוא זה עם השם הארוך ביותר.
\n",
+ " אם יש שני סרטים שאורך שמם זהה, סדרו אותם לפי שמם בסדר אלפבתי.\n",
+ "
\n", + " רמז: השתמשו בתיעוד הפונקציות באתר של SQLite.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## מילת המפתחDISTINCT
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ " ננסה להציג את השנים שבהן שוחררו הסרטים שנמצאים במסד הנתונים שלנו.
\n",
+ " בעזרת הכלים שרכשנו עד כה, נוכל בקלות לנסח שאילתה כזו:\n",
+ "
\n",
+ " באופן לא מפתיע, שנים מסוימות מופיעות שוב ושוב בתוצאות – כמספר הסרטים ששוחררו באותה שנה.
\n",
+ " שנת 1934 חוזרת על עצמה 289 פעמים, לדוגמה, ושנת 2017 חוזרת על עצמה לא פחות מ־3329 פעמים.\n",
+ "
\n",
+ " כדי להסיר את הכפילויות מתוצאות השאילתה נוכל להשתמש במילת המפתח DISTINCT
.
\n",
+ " נעשה זאת כך:\n",
+ "
\n",
+ " הסרת הכפילויות תתבצע עבור כל העמודות שעליהן אנחנו שולפים בשאילתת ה־SELECT DISTINCT
.
\n",
+ " זאת אומרת שאם נבקש לבצע DISTINCT
על שתי עמודות או יותר,
\n",
+ " כל טור בפני עצמו יוכל להכיל כמה פעמים את אותו ערך – אבל שורה שלמה עם אותם ערכים לעולם לא תחזור על עצמה. \n",
+ "
\n",
+ " מילת המפתח DISTINCT
חייבת להופיע מייד אחרי מילת המפתח SELECT
.
\n",
+ " כרגע אין לנו דרך לבקש ממסד הנתונים להסיר כפילות רק בחלק מהעמודות.\n",
+ "
\n",
+ " כתבו שאילתה שתציג, ללא כפילויות, את כל השנים והחודשים בהם נולדו ידוענים שנמצאים במסד הנתונים שלנו.
\n",
+ " לדוגמה, במסד הנתונים תהיה שורה בה החודש הוא 05 והשנה היא 1899.
\n",
+ " סדרו את התוצאות לפי השנה ואז לפי החודש, בסדר עולה.\n",
+ "
\n", + " נסו לפתור את התרגיל בעצמכם לפני שתביטו בפתרון.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### פתרון" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " השאלה מתייחסת לתאריך הלידה (date_of_birth) של ידוענים (names),
\n",
+ " ומבקשת להשיג את שנת הלידה שלהם ואת חודש הלידה שלהם.\n",
+ "
\n",
+ " נוכל להשיג את הנתונים האלו בעזרת הפונקציה strftime
,
\n",
+ " וכמובן שלא נשכח לוודא שאנחנו משתמשים אך ורק בשורות בהן date_of_birth ידוע לנו.\n",
+ "
\n", + " כך אנחנו פתרנו את השאלה הזו:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT DISTINCT strftime('%m', date_of_birth) AS month_of_birth, \n", + " strftime('%Y', date_of_birth) AS year_of_birth \n", + "FROM names\n", + "WHERE date_of_birth IS NOT NULL\n", + "ORDER BY year_of_birth, month_of_birth;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## סיכום" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " במחברת זו רכשנו כלים נוספים שמאפשרים לנו לכתוב שאילתות SQL עשירות יותר.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " למדנו כיצד לקבל את הנתונים בצורה מסודרת בעזרת ORDER BY
.
\n",
+ " בהמשך, גילינו שקיימות פונקציות גם במסדי נתונים, ואלו מעשירות מאוד את היכולת שלנו לעבד מידע לפני שהוא חוזר אלינו.
\n",
+ " לבסוף, למדנו על מילת המפתח DISTINCT
– שמאפשרת לנו לסנן החוצה תוצאות כפולות על עמודה אחת או יותר.\n",
+ "
\n",
+ " כמה זמן אפשר להקדיש ללמידת פייתון? נמאס!
\n",
+ " וכל הדיבור הזה על סרטים...\n",
+ "
\n",
+ " מפה לשם, החלטתם לפרוש לעסקי הבימוי.
\n",
+ " השתמשו במסד הנתונים כדי להחליט מהו האורך המיטבי עבור הסרט החדש שלכם.
\n",
+ " צרו עמודה בשם rating בה יופיע הציון המעוגל של הסרט, כמספר שלם.
\n",
+ " צרו עמודה נוספת בשם hours שמכילה את אורך הסרט בשעות, גם הפעם מעוגל למספר שלם.
\n",
+ "
\n",
+ " מצאו כמה שעות צריך להיות הסרט שלכם כך שיהיה לו הסיכוי הטוב ביותר לקבל ציון גבוה.
\n",
+ " אל תציגו פעמיים סרטים באותו אורך שקיבלו את אותו ציון.\n",
+ "
\n",
+ " כתבו שאילתה שמחזירה את שמות כל הסרטים שגרפו את הרווח הגדול ביותר במסד הנתונים שלנו.
\n",
+ " הציגו רק סרטים שיש לנו את הנתונים הללו עליהם, ולא יותר מ־1,000 סרטים בסך הכל.
\n",
+ " אם שני סרטים גרפו רווחים זהים, סדרו אותם לפי היחס בין הרווחים שגרפו לבין התקציב שלהם.\n",
+ "
\n",
+ " זו שאלה לא קלה שכוללת ישום אינטנסיבי של מה שלמדנו עד כה, ומעט חשיבה יצירתית.
\n",
+ " ודאו שאתם מתייחסים לטיב הנתונים לפני שאתם משתמשים בהם. אל תתייחסו לסרטים שרווחיהם שמורים במטבע שאינו דולרי.
\n",
+ " השתמשו בתיעוד הפונקציות של SQLite כדי לצלוח את השאלה. במידת הצורך, קראו גם על הפונקציה CAST
.\n",
+ "
\n",
+ " עד כה למדנו על הפסוקיות SELECT
, FROM
, WHERE
, ORDER BY
, ו־LIMIT
, ועל סדר הרצתם על ידי מנוע ה־SQL.
\n",
+ " דיברנו על אופרטורי השוואה, אופרטורים לוגיים, על NULL
ועל טיפול בכפילויות בעזרת מילת המפתח DISTINCT
.
\n",
+ " גילינו גם על פונקציות במסדי נתונים, ועל איך הן מאפשרות לנו לעבד את התוצאות ולהחזיר תוצאות שנוח יותר לעבוד איתן.\n",
+ "
\n", + " במחברת זו נלמד:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " בואו נתחיל!\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## פונקציות אגרגטיביות" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " פונקציות אגרגטיביות (באנגלית: Aggregate functions) הן פונקציות שפועלות על כמה שורות יחד, ומחזירות מהן ערך יחיד.
\n",
+ " הן מוכרות בעברית גם בשמות פונקציות צבירה, פונקציות קבוצה ופונקציות קיבוציות.\n",
+ "
\n", + " הנה כמה דוגמאות למקרים שבהם פונקציות אגרגטיביות יכולות לסייע לנו:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " עד כה עבדנו עם פונקציות סקלריות – פונקציות שמקבלות רשימה של נתונים, ועבור כל איבר ברשימה מחזירות ערך.
\n",
+ " פונקציות אגרגטיביות הן פונקציות שמקבלות נתונים – ומחזירות תוצאה אחת.\n",
+ "
\n",
+ " ניקח לדוגמה את הפונקציה \"קבל מספר, ותחזיר את אותו מספר בחזקת שתיים\".
\n",
+ " זוהי פונקציה סקלרית שבהינתן הנתונים 1, 2 ו־3 תחזיר 1, 4 ו־9 בהתאמה.
\n",
+ " שיעור שעבר למדנו על הפונקציה הסקלרית round
, לדוגמה, שמקבלת מספר ומעגלת אותו.
\n",
+ " עבור הנתונים 3.1, 3.4 ו־3.7 היא תחזיר 3, 3 ו־4 בהתאמה.\n",
+ "
\n",
+ " פונקציות אגרגטיביות, כאמור, מקבלות רשימה של נתונים ומחזירות תוצאה אחת.
\n",
+ " הפונקציה \"קבל מספרים והחזר את סכומם\", לדוגמה, היא פונקציה אגרגטיבית, שבהינתן הנתונים 1, 2 ו־3 תחזיר 6.
\n",
+ "
\n",
+ " ננסה להבין, לדוגמה, מה הדירוג הממוצע של סרט במסד הנתונים שלנו.
\n",
+ "
\n",
+ " נוכל להשתמש בפונקציה האגרגטיבית AVG
, שמקבלת קבוצת נתונים (במקרה שלנו: עמודה).
\n",
+ " הפונקציה תחזיר את ממוצע הערכים שאינם NULL
באותה קבוצת נתונים.\n",
+ "
\n",
+ " נבקש מהפונקציה לפעול על עמודת הציונים, avg_vote, בטבלת movies.
\n",
+ " הפונקציה תחזיר ערך יחיד: מה הציון הממוצע של סרט במסד הנתונים.\n",
+ "
\n",
+ " אם הרצתם את השאילתה על מסד הנתונים שלכם, קיבלתם שורה אחת, בה יש ערך שקרוב ל־5.9.
\n",
+ " זהו הדירוג הממוצע של סרט במסד הנתונים.\n",
+ "
\n",
+ " פונקציות אגרגטיביות זכו לדף משלהם בתיעוד של SQLite.
\n",
+ " אתם עשויים לראות לעיתים קרובות שימוש בפונקציות SUM
, MIN
, MAX
, COUNT
ו־AVG
.\n",
+ "
\n",
+ " נסו לגלות כמה ערכי NULL
נמצאים בעמודה budget שבטבלת movies.
\n",
+ " השתמשו בתיעוד.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " הפונקציה COUNT
, לדוגמה, מאפשרת לנו לספור כמה ישויות קיימות בטבלה מסוימת.
\n",
+ " נבדוק כמה סרטים קיימים לנו במסד הנתונים:\n",
+ "
\n",
+ " שימו לב ש־COUNT
לא סופרת ערכי NULL
,
\n",
+ " כך שאם נעביר לה כארגומנט עמודה מסוימת, נראה כמה ערכים שאינם NULL
קיימים בעמודה.
\n",
+ "
\n",
+ " נראה לדוגמה כמה ערכים של תקציבי סרטים קיימים במסד הנתונים.
\n",
+ " נשתמש בפונקציה COUNT
על העמודה budget בטבלת movies:\n",
+ "
\n",
+ " כדי להבין כמה ערכים הם NULL
בעמודה הזו, נוכל לבצע חיסור פשוט.
\n",
+ " נחסר בין כמות הערכים בטבלה לבין כמות הערכים שאינם NULL
:\n",
+ "
\n",
+ " כמו שראינו, פונקציות אגרגטיביות פועלות על כל השורות הקיימות, ומחזירות לנו תוצאה אחת.
\n",
+ " אפשר לדמיין את הפעולה שלהן כמעין מעיכה של כל השורות כדי להשיג תוצאה יחידה.\n",
+ "
\n",
+ " האם הגבהים במסד הנתונים שלנו אמינים?
\n",
+ " אם כן, הרי שממוצע הגבהים של אנשים עד גיל 18 יהיה נמוך מהממוצע של שאר האנשים.\n",
+ "
\n",
+ " כתבו שאילתה שתציג את ממוצע הגבהים של אנשים עד גיל 18, ושאילתה נוספת שתציג את ממוצע הגבהים של אנשים מגיל 18 ומעלה.
\n",
+ " האם ממוצע הגבהים במסד הנתונים אמין?\n",
+ "
GROUP BY
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n", + " כשנבצע מחקר על הנתונים, פעמים רבות נרצה לשאול שאלות על קבוצות של נתונים.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### דוגמה – משקלי פירות" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " נראה דוגמה חיה על רשימת נתונים קטנה.
\n",
+ " לפנינו טבלה שבה פירות ומשקלם:
\n",
+ "
מספר סידור (id) | \n", + "סוג הפרי (fruit_type) | \n", + "משקל (weight) | \n", + "
---|---|---|
1 | \n", + "תפוח | \n", + "150 | \n", + "
2 | \n", + "מנגו | \n", + "540 | \n", + "
3 | \n", + "תפוח | \n", + "170 | \n", + "
4 | \n", + "תפוח | \n", + "140 | \n", + "
5 | \n", + "מנגו | \n", + "460 | \n", + "
6 | \n", + "תות | \n", + "25 | \n", + "
7 | \n", + "תפוח | \n", + "150 | \n", + "
8 | \n", + "תות | \n", + "15 | \n", + "
\n", + " אם נרצה לקבל מה משקל התותים שקטפנו, נוכל לעשות את זה יחסית בקלות:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT SUM(weight) AS strawberries_weight\n", + "FROM fruits -- שם הטבלה\n", + "WHERE fruit_type = 'תות';\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " התוצאה תהיה עמודה בשם strawberries_weight שבה יש ערך אחד – 40, הרי הוא סכום משקלי התותים בטבלה.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בטבלת הדוגמה למעלה אפשר לראות 3 סוגי פירות: תפוח, מנגו ותות.
\n",
+ " מה אם נרצה לקבל את הסכום של משקלי כל אחד מסוגי הפירות?\n",
+ "
\n",
+ " בכלים שיש לנו כרגע, אין לנו דרך לכתוב שאילתה שכזו.
\n",
+ " נצטרך לעבור על כל אחד מסוגי הפירות וליצור עבורם שאילתה דומה לזו שעשינו עבור התות.\n",
+ "
\n", + " לו היינו רוצים לפתור את הבעיה באופן ידני, היינו ניגשים אליה כך:\n", + "
\n", + "\n", + "\n", + " נקבץ את הפירות בטבלה לפי סוג הפרי:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "מספר סידור (id) | \n", + "סוג הפרי (fruit_type) | \n", + "משקל (weight) | \n", + "
---|---|---|
1 | \n", + "תפוח | \n", + "150 | \n", + "
2 | \n", + "מנגו | \n", + "540 | \n", + "
3 | \n", + "תפוח | \n", + "170 | \n", + "
4 | \n", + "תפוח | \n", + "140 | \n", + "
5 | \n", + "מנגו | \n", + "460 | \n", + "
6 | \n", + "תות | \n", + "25 | \n", + "
7 | \n", + "תפוח | \n", + "150 | \n", + "
8 | \n", + "תות | \n", + "15 | \n", + "
\n",
+ " תוכלו לדמיין ששמנו את כל אחד מהפירות בדלי שמתאים לסוג שלו.
\n",
+ " כעת ברשותינו דלי תותים, דלי מנגו ודלי תפוחים.\n",
+ "
\n",
+ " נחליט שמעניין אותנו לחשב את סכום (SUM
) המשקלים (weight).\n",
+ "
\n",
+ " כדי להגיע לתוצאה, נפעיל את הפונקציה (SUM
) על כל אחת מהקבוצות:\n",
+ "
סוג הפרי (fruit_type) | \n", + "סכום המשקלים (weights_sum) | \n", + "
---|---|
תפוח | \n", + "610 | \n", + "
מנגו | \n", + "1000 | \n", + "
תות | \n", + "40 | \n", + "
\n", + " הכל טוב ויפה, אבל איך ניגש לבעיה ב־SQL?\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " למזלנו, הפסוקית GROUP BY
קיימת בדיוק בשביל המטרה הזו.
\n",
+ " בשאילתה, הפסוקית הזו תאפשר לנו לקבץ ערכים לפי עמודה מסוימת.
\n",
+ " זה יראה כך:\n",
+ "
\n",
+ " זה למעשה החלק בשאילתה ש\"צובע לנו את הטבלה\" בצורה שראינו למעלה.
\n",
+ " ה\"צביעה\" או \"החלוקה לדליים\" שראיתם למעלה – זה בדיוק מה שהפסוקית הזו עושה בפועל.\n",
+ "
\n", + " נעבור לשאילתה המלאה.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " זכרו את השלבים:\n", + "
\n", + "\n", + "SUM
) על המשקלים (weights).\n",
+ " התוצאה תהיה 3 שורות של סכום המשקלים של כל סוג פרי.
\n",
+ " דוגמה לתוצאה כזו היא הטבלה הצבועה האחרונה שראיתם.\n",
+ "
\n",
+ " חשוב להבין שכשכתבנו GROUP BY
בשאילתה שלנו, שינינו לחלוטין את הצורה בה השאילתה עובדת.
\n",
+ " במקום ש־SELECT
תפעל על כל אחת מהשורות,
\n",
+ " היא עוברת למצב שבו היא פועלת על קבוצות – אותן קבוצות צבעוניות שראינו למעלה.\n",
+ "
\n",
+ " פסוקית ה־GROUP BY
יוצרת את אותן קבוצות,
\n",
+ " ואז פסוקית ה־SELECT
עוברת על קבוצה אחת בכל פעם (במקום על שורה אחת בכל פעם), ומחזירה עבור כל קבוצה תוצאה אחת.\n",
+ "
\n",
+ " באותו אופן שבו היה לנו קל לענות על משקל התותים בטבלה שלנו,
\n",
+ " יהיה לנו קל לענות על השאלה \"כמה סרטים יצאו בשנת 1990\":\n",
+ "
\n", + " אבל האם תוכלו לכתוב שאילתה שתחזיר את כמות הסרטים שיצאו בכל שנה שקיימת במסד הנתונים?\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " נסו לכתוב שאילתה שתציג כמה סרטים יצאו בכל שנה.
\n",
+ " בתוצאות השאילתה שתכתבו אמורה להיות רשומה עבור כל אחת מהשנים שקיימות במסד הנתונים.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " נוכל לשאול את אותה שאלה בקלות גם עבור 1991 ו־1992.
\n",
+ " למרות שזה בהחלט אפשרי לעבור שנה שנה, זו עשויה להיות משימה מעט מעייפת.
\n",
+ " אילו רק הייתה דרך לקבץ את הסרטים לפי השנים, ואז לספור כמה סרטים יש בכל קבוצה...\n",
+ "
\n",
+ " נכתוב את השאילתה בעזרת פסוקית ה־GROUP BY
, כמו שלמדנו:\n",
+ "
\n", + " בשאילתה הזו ביקשנו:\n", + "
\n", + "\n", + "\n",
+ " המציאו נתונים משל עצמכם, וחישבו על מקומות שבהם הייתם יכולים לנצל את הכוח של פסוקית GROUP BY
.
\n",
+ " נסו לחשוב לפחות על 4 דוגמאות.\n",
+ "
\n", + " הנה הפתרון שלנו:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "תיאור הנתונים | \n", + "שאלת החקר | \n", + "השדה שלפיו נחלק לקבוצות | \n", + "הפונקציה האגרגטיבית | \n", + "השדה שעליו נפעיל חישוב | \n", + "
---|---|---|---|---|
טבלת פירות בה ישות היא סוג הפרי, משקלו ותמונה שלו. | \n", + "מה המשקל הממוצע של כל סוג פרי שיש לנו במסד הנתונים? | \n", + "סוג הפרי (תפוח, אשכולית, בננה) | \n", + "ממוצע (AVG ) | \n",
+ " משקל | \n", + "
טבלת ספרים בה ישות היא שמם, הסוגה (הז'אנר) שלהם ומספר הדפים בכל ספר. | \n", + "מהו הספר בעל מספר העמודים המירבי מכל סוגה (ז'אנר)? | \n", + "הסוגה של הספר | \n", + "מקסימום (MAX ) | \n",
+ " מספר העמודים | \n", + "
טבלת עובדים בה ישות היא שמם, המשכורת שלהם והמחלקה בה הם עובדים. | \n", + "כמה משכורות אנחנו משלמים עבור מחלקה בעסק שלנו? | \n", + "מחלקת העובד | \n", + "סכום (SUM ) | \n",
+ " שכר | \n", + "
טבלת סרטים בה ישות היא שם הסרט, תאריך שחרורו לאקרנים, ציון ממוצע לסרט ומספר מדרגים. | \n", + "עבור כל שנה, מה הציון הממוצע של הסרטים ששוחררו בה? | \n", + "שנת שחרור | \n", + "ממוצע (AVG ) | \n",
+ " ציון | \n", + "
\n",
+ " נסבך את השאילתה, הפעם רק במעט.
\n",
+ " נבקש לקבל רק שנים שבהן נכנס הפסקול לסרטים (1927 והלאה).
\n",
+ " נסדר אותם לפי כמות הסרטים בסדר יורד, ונבקש לקבל רק עד 5 תוצאות.\n",
+ "
\n",
+ " מהשאילתה למעלה אנחנו למדים שפסוקית GROUP BY
מגיעה אחרי WHERE
ולפני ORDER BY
.
\n",
+ " אבל מהו הסדר לפיו מנוע ה־SQL מריץ את הפסוקיות בשאילתה?\n",
+ "
\n",
+ " עכשיו, כשאנחנו יודעים איך נראית שאילתה שבנויה מכל הפסוקיות שלמדנו,
\n",
+ " נרענן את סדר הרצת הפסוקיות במנוע ה־SQL:\n",
+ "
\n",
+ " כמו שאפשר לראות באיור שלמעלה, פסוקית ה־GROUP BY
נקראת על ידי מסד הנתונים מייד לפני קריאת פסוקית ה־SELECT
.
\n",
+ " הסדר הזה נקבע כדי לאפשר לפסוקית ה־SELECT
להפעיל פונקציות אגרגטיביות על הקבוצות שנוצרו על ידי פסוקית ה־GROUP BY
.\n",
+ "
\n",
+ " רבים טוענים שיש לנו נטייה לייפות את העבר, ולכן דירוגם הממוצע של סרטים ישנים גבוה יותר.
\n",
+ "
\n",
+ " בדקו את הטענה.
\n",
+ " כתבו שאילתה שתוצאתה היא הדירוג הממוצע של סרטים לפי שנה.
\n",
+ " כיוון שיש יחסית מעט דירוגים עבור סרטים ישנים מאוד, כללו רק סרטים שיצאו בשנת 1924 או אחריה.\n",
+ "
\n",
+ " עבור כל שנה, עגלו את הציון הממוצע שמצאתם ל־2 ספרות אחרי הנקודה.
\n",
+ " סדרו את תוצאות השאילתה לפי הדירוג הממוצע באותה שנה כך שהדירוג הגבוה ביותר יופיע ראשון.
\n",
+ " אם יש 2 דירוגים זהים, הציגו קודם את השנה המוקדמת מביניהן. \n",
+ "
\n", + " הציצו על התוצאות, ושערו בעצמכם אם אפשר להגיד שסרטים ישנים יותר מדורגים גבוה יותר.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### המגבלה של פונקציות אגרגטיביות" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " ישנה בעיה נפוצה למדי עם GROUP BY
ועם פונקציות אגרגטיביות, שתקפוץ עליכם די מהר אחרי שתשתמשו בהן מספיק.
\n",
+ " חשוב להבין למה היא מתרחשת וכיצד אפשר להתמודד איתה.
\n",
+ " נסו לחשוב בעצמכם על מה עלול להשתבש בשאילתה הבאה:\n",
+ "
\n", + " השאילתה האחרונה ביקשה לקבץ את הסרטים לפי שנה, ועבור כל שנה – להחזיר:\n", + "
\n", + "\n", + "\n",
+ " במצב האגרגטיבי שבו אנחנו נמצאים, COUNT
הולכת למעוך מספר ישויות לכדי שורה אחת ויחידה.
\n",
+ " במקרה הזה, אין משמעות לאחזור \"כותרת הסרט\" – אנחנו פועלים על קבוצת סרטים, מספר רשומות.
\n",
+ " אין סרט אחד שאפשר להתייחס אליו ולשלוף את הכותרת שלו.\n",
+ "
\n",
+ " SQLite רחמן ומחזיר תשובה חסרת פשר.
\n",
+ " רוב המנועים של מסדי הנתונים האחרים יתריעו על חריגה ויכשילו את השאילתה.\n",
+ "
\n",
+ " לכן, כלל האצבע הוא כזה:
\n",
+ " בשאילתה שבה יש פסוקית GROUP BY
, העמודות בפסוקית ה־SELECT
חייבות:\n",
+ "
GROUP BY
.GROUP BY
על יותר מעמודה אחת"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n", + " הנה תרגיל מעט מאתגר, שתוכלו לנסות לפתור לפני שתמשיכו:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " עבור כל שנה, הציגו את כל הדירוגים שסרטים קיבלו באותה שנה.
\n",
+ " ליד כל דירוג, כמה סרטים דורגו כך באותה שנה.
\n",
+ " כלומר, כל שורה בתוצאת השאילתה צריכה להיות מורכבת מ:\n",
+ "
\n",
+ " אם לא היה אף סרט שקיבל דירוג מסוים בשנה מסוימת, מהשאילתה לא תחזור ישות שמכילה את השנה והדירוג הללו.
\n",
+ " השתמשו ביצירתיות שלכם – הפתרון פשוט, אבל לא למדנו את הטכניקה הזו והיא דורשת מעט אינטואיציה.\n",
+ "
\n", + " כך נראות 21 השורות הראשונות שחזרו מפתרון התרגיל:\n", + "
" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "year|avg_vote|times_rating_appeared|\n", + "----|--------|---------------------|\n", + "1894|5.9 | 1|\n", + "1906|6.1 | 1|\n", + "1911|5.7 | 1|\n", + "1911|5.8 | 1|\n", + "1911|6.0 | 1|\n", + "1911|6.2 | 1|\n", + "1911|7.0 | 1|\n", + "1912|5.2 | 1|\n", + "1912|5.5 | 1|\n", + "1912|5.7 | 1|\n", + "1912|6.7 | 1|\n", + "1912|6.8 | 1|\n", + "1913|5.0 | 1|\n", + "1913|6.0 | 1|\n", + "1913|6.2 | 2|\n", + "1913|6.4 | 1|\n", + "1913|6.5 | 1|\n", + "1913|6.6 | 2|\n", + "1913|7.0 | 3|\n", + "1913|7.1 | 1|\n", + "1913|7.5 | 1|" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " השאלה למעלה מלמדת אותנו על היכולת של GROUP BY
לעבוד על כמה עמודות יחד.
\n",
+ " הפעם, הקבוצות שאנחנו רוצים ליצור הן לא לפי עמודה אחת (רק שנה, או רק דירוג).
\n",
+ " כל קבוצה שניצור תורכב משילוב של כמה עמודות. במקרה שלנו – גם השנה, וגם הדירוג.\n",
+ "
\n",
+ " עם קצת יצירתיות והכלים שלמדתם, נפריד את השדות ב־GROUP BY
בפסיק כדי להשיג את התוצאה:
\n",
+ "
\n",
+ " שימו לב שהשאילתה לא יוצרת קבוצה אחת ל־year וקבוצה אחת ל־avg_vote.
\n",
+ " היא יוצרת קבוצה לכל שילוב קיים בין year לבין avg_vote.
\n",
+ " תוכלו לדמיין שנוצר tuple שמורכב מכל העמודות ששמם צוין אחרי פסוקית ה־GROUP BY
.
\n",
+ " הקבוצות חולקו לפי ה־tuple הזה.\n",
+ "
GROUP BY
לפי ביטוי"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n", + " תעלול נחמד שלא מובן מאליו שיעבוד, הוא חלוקה לקבוצות לפי ביטוי שיצרנו.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " בתור התחלה, נספור כמה סרטים קיימים במסד הנתונים עבור כל דירוג אפשרי:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT avg_vote, COUNT(*)\n", + "FROM movies\n", + "GROUP BY avg_vote;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " השאילתה למעלה החזירה את כל הדירוגים האפשריים, וכמה סרטים תואמים לכל אחד מאותם דירוגים.
\n",
+ " הייתי משתף איתכם את תוצאות השאילתה, אבל חזרו 89 שורות, מ־1.0 ועד 9.9. ארוך ומשעמם!\n",
+ "
\n",
+ " כדי לקבל מידע שיהיה לנו יותר קל לעכל, בואו נעגל את הדירוג של כל סרט למספר שלם.
\n",
+ " כך במקום שהדירוג של כל סרט ינוע בין 1.0 ל־9.9 (89 אפשרויות), הוא יהיה בטווח שבין 1 ל־10 (10 אפשרויות).
\n",
+ "
\n",
+ " החדשות המשמחות הן שאנחנו יכולים לקבץ את התוצאות לפי הערך המעוגל!
\n",
+ " נעשה זאת כך:\n",
+ "
\n",
+ " השאילתה הזו החזירה לנו 10 שורות כשבכל שורה מופיע דירוג מ־1 עד 10.
\n",
+ " לצד הדירוג בכל אחת מהשורות, מופיעה כמות הסרטים שהדירוג המעוגל שלהם תואם לדירוג שמופיע באותה שורה. \n",
+ "
\n",
+ " כדי להגיע לתוצאה הזו, יצרנו סוג של עמודה מחושבת בפסוקית ה־GROUP BY
.
\n",
+ " כמו שאתם רואים, אנחנו יכולים להשתמש בביטוי מפסוקית ה־GROUP BY
, בתוך פסוקית ה־SELECT
. איזו הקלה!\n",
+ "
\n", + " חוק בנפורד, בהגדרה גסה, הוא חוק שמנבא את שכיחות הספרה השמאלית ביותר במספר, בהינתן אוסף נתונים גדול מספיק.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " לפי החוק, ככל שספרה בטווח 1–9 היא נמוכה יותר, יש לה יותר סיכוי להופיע בתור הספרה השמאלית ביותר במספר כלשהו.
\n",
+ " כלומר, הספרה 1 תופיע בתדירות הגבוהה ביותר כספרה השמאלית במספרים.
\n",
+ " לפי אותו חוק, הספרה 2 תופיע כספרה השמאלית ביותר במספרים בתדירות נמוכה משל 1, אך גבוהה משל 3.
\n",
+ "
\n",
+ " בדקו את הטענה.
\n",
+ " כתבו שאילתה שמתייחסת למספר הפעמים שדירגו כל סרט (עמודת votes בטבלת movies).
\n",
+ " הציגו כמה פעמים כמות הדירוגים מתחילה בספרה 1, כמה פעמים כמות הדירוגים מתחילה בספרה 2 וכך הלאה.\n",
+ "
\n",
+ " הספרה השמאלית ביותר במספר 1,234, לדוגמה, היא 1. הספרה השמאלית ביותר במספר 5 היא 5.
\n",
+ " ודאו שהספרה 1 היא הספרה השמאלית ביותר של 33834 שורות של כמויות דירוג.\n",
+ "
HAVING
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ " בשאילתות שבנינו עד כה, ראינו את הכוח האדיר של הפסוקיות השונות – מ־SELECT
ועד GROUP BY
.
\n",
+ " אחת הפסוקיות השימושיות ביותר הייתה WHERE
, שאיפשרה לנו לסנן מהטבלה שורות שלא התאימו לצרכים שלנו.\n",
+ "
\n",
+ " אם נציץ בסדר ההרצה של הפסוקיות באיור הצבעוני שמופיע מעלה,
\n",
+ " נגלה שפסוקית ה־WHERE
מורצת לפני פסוקית ה־GROUP BY
.
\n",
+ " מצליחים לזהות אילו בעיות הסדר הזה עלול להביא איתו?\n",
+ "
\n",
+ " פסוקית ה־WHERE
תעבוד מצוין כשנרצה לסנן שורות לפני שאנחנו מקבצים אותן,
\n",
+ " אבל לא תוכל לעזור לנו בסינון התוצאות שהוחזרו אחרי הקיבוץ והאגרגציה.\n",
+ "
\n", + " נראה דוגמה.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " נניח שנרצה לספור כמה סרטים טובים יצאו בכל שנה.
\n",
+ " נגדיר \"סרט טוב\" כסרט שקיבל ציון ממוצע של יותר מ־8.0.
\n",
+ " סדר הפעולות במקרה הזה הוא:\n",
+ "
FROM movies
.WHERE avg_vote > 8.0
.GROUP BY \"year\"
.SELECT \"year\", COUNT(*)
.\n", + " עד כאן עסקים כרגיל, והכל עובד כפי שציפינו.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אבל מה קורה, נניח, אם נרצה לקבל רק שנים שבהן יש לפחות 10 סרטים טובים?
\n",
+ " אם נתאמץ מאוד, נגיע לשאילתה השגויה הבאה:\n",
+ "
\n",
+ " כמובן שזה לא יעבוד!
\n",
+ " פסוקית ה־WHERE
רצה לפני GROUP BY
ולפני SELECT
,
\n",
+ " כך שכשמנוע ה־SQL מריץ את פסוקית ה־WHERE
הוא כלל לא יודע שקיבצנו את הטבלה לקבוצות.\n",
+ "
\n",
+ " כדי להתגבר על הבעיה הזו, יצרו ב־SQL את פסוקית ה־HAVING
.
\n",
+ " היא פועלת בדיוק כמו פסוקית ה־WHERE
, רק שהיא מסננת תוצאות אחרי ה־GROUP BY
.\n",
+ "
\n",
+ " נתקן את השאילתה התקולה.
\n",
+ " נעביר את תנאי הסינון שנסמך על ה־GROUP BY
לפסוקית ה־HAVING
:\n",
+ "
\n",
+ " אפשר לסכם ולהגיד:
\n",
+ " פסוקית ה־HAVING
מאפשרת לנו להוסיף לשאילתה סינון לפי פונקציות אגרגטיביות.\n",
+ "
\n",
+ " גם מבחינת מיקום בשאילתה וגם מבחינת סדר הרצה,
\n",
+ " פסוקית ה־HAVING
תבוא מייד אחרי פסוקית ה־GROUP BY
:\n",
+ "
\n", + " הוכיחו או הפריכו: קצב הילודה בעולם צונח בקצב מסחרר.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כתבו שאילתה שעבור כל עשור מ־1920, מחשבת את ממוצע הילדים של בעלי התפקידים שנולדו באותו עשור.
\n",
+ " אל תציגו עשורים שבהם יש לנו נתונים על פחות מ־6,000 בעלי תפקידים.
\n",
+ " סדרו את הנתונים בסדר יורד, לפי ממוצע הילודה באותו עשור.\n",
+ "
\n", + " פרס טומי הוא פרס מרגש שהמצאתי ממש הרגע, ומחולק לפי הכללים שמופיעים להלן.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " המועמדים לפרס הם כל הסרטים שדורגו לפחות 30,000 פעמים, והציון הממוצע שלהם הוא לפחות 8.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " בפרס טומי, \"אורך הסרט\" מוגדר לפי אורכו של הסרט בשעות.
\n",
+ " אם אורכו בשעות הוא לא מספר שלם, מעגלים אותו לשלם הקרוב.
\n",
+ " כך סרט שאורך 61 דקות נחשב כסרט של שעה, וסרט של 100 דקות נחשב כסרט של שעתיים.\n",
+ "
\n",
+ " בכל שנה, ישנה קטגוריה עבור כל אורך סרט אפשרי. בכל קטגוריה מחולק פרס אחד בלבד.
\n",
+ " הפרס מחולק עבור הסרט שהדירוג שלו הוא הגבוה ביותר עבור אותה שנה, בקטגוריית האורך שלו.\n",
+ "
\n", + " כך לדוגמה אם יצאו בשנת 2001 ששת הסרטים הבאים:\n", + "
" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + ".-----.--------.--------.-----.\n", + "|movie|duration|avg_vote|votes|\n", + "|-----+--------+--------+-----|\n", + "| A| 1| 8.1|30001|\n", + "| B| 1| 8.9|16432|\n", + "| C| 1| 8.6|66666|\n", + "| D| 2| 8.2| 2|\n", + "| E| 2| 7.9|72345|\n", + "| F| 3| 9|99999|\n", + "'-----------------------------'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " התוצאות יהיו ש־C זכה בשנת 2001 בקטגוריית האורך של שעה.
\n",
+ " ל־B לא היו מספיק הצבעות כדי להיות מועמד, ו־A דורג נמוך מ־C ולכן הפסיד לו.\n",
+ "
\n",
+ " בקטגוריית האורך של שעתיים באותה שנה לא זוכה אף סרט.
\n",
+ " הסרט D נפסל כיוון שאין מספיק אנשים שהצביעו לו, והסרט E נפסל כיוון שהדירוג שלו נמוך מדי.\n",
+ "
\n", + " בקטגוריית האורך של 3 שעות זוכה הסרט F, שהוא גם המתמודד היחיד על הפרס לקטגוריה הזו בשנה הזו.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " הציגו את הזוכים מכל הזמנים, בכל הקטגוריות.\n", + "
" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/content/week13/4_Normalization.ipynb b/content/week13/4_Normalization.ipynb new file mode 100644 index 0000000..92d91f6 --- /dev/null +++ b/content/week13/4_Normalization.ipynb @@ -0,0 +1,2560 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " בשיעורים האחרונים עבדנו עם מסדי נתונים טבלאיים – כאלו שבנויים מטבלאות שבהן כל שורה מייצגת ישות.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אם חקרתם קצת את מסד הנתונים והסתכלתם על הנתונים שנמצאים בטבלאות השונות,
\n",
+ " ייתכן שעלו לכם כמה שאלות מעניינות:\n",
+ "
\n",
+ " כבר בפרק הקרוב נענה על כל השאלות הללו.
\n",
+ " עד סוף הפרק תבינו מדוע מסדי נתונים בנויים מטבלאות רבות, באופן שלפעמים נראה סבוך,
\n",
+ " תדעו לארגן מסד נתונים משל עצמכם,
\n",
+ " ותלמדו מספר מונחים חשובים שישמשו אתכם בהמשך הלמידה על SQL.\n",
+ "
\n", + " לפני שנצא לדרך, נזכיר את הדיאגרמה שהצגנו במחברת הראשונה, כשרק למדנו על השורות והעמודות שבמסד הנתונים.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " במחברת הקרובה אנחנו הולכים לעבור ביחד מסע של יצירת מסד נתונים קטן שכולל סרטים ושחקנים.
\n",
+ " תוך כדי המסע, נבחן החלטות ורעיונות שקשורים בתכנון ובבנייה של מסד הנתונים.\n",
+ "
\n",
+ " לפני שנתחיל, נציב לעצמנו מטרות.
\n",
+ " בבואנו לתכנן מסד נתונים, היינו רוצים לדעת מה עלינו לעשות כדי לענות על הצרכים הבאים:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n", + " יצירה או שינוי של מסד הנתונים כך שיתחשב בנקודות הללו, הוא תהליך שנקרא \"נרמול של מסד הנתונים\" (Database normalization).\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " נתחיל!\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### טבלה אחודה של סרטים" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כל הטבלאות האלו הן בלאגן אחד גדול. אנחנו נלך על משהו פשוט יותר.
\n",
+ " כצעד ראשון, נבנה טבלה פשוטה של סרטים.
\n",
+ " העמודות בה יהיו רק כותרת הסרט והשנה שבה הוא יצא.\n",
+ "
title | \n", + "release_year | \n", + "
---|---|
Monty Python and the Holy Grail | \n", + "1975 | \n", + "
V for Vendetta | \n", + "2005 | \n", + "
The Matrix | \n", + "1999 | \n", + "
\n",
+ " נוסיף את השחקנים שכיכבו בכל סרט ישירות לטבלת הסרטים שלנו.
\n",
+ " כך נוכל לחסוך את הסיבוך שבהוספת טבלת השחקנים.
\n",
+ "
title | \n", + "release_year | \n", + "actor_name | \n", + "
---|---|---|
Monty Python and the Holy Grail | \n", + "1975 | \n", + "Graham Chapman, John Cleese, Eric Idle, Terry Gilliam | \n", + "
V for Vendetta | \n", + "2005 | \n", + "Hugo Weaving, Rupert Graves, Stephen Rea | \n", + "
The Matrix | \n", + "1999 | \n", + "Keanu Reeves, Laurence Fishburne, Hugo Weaving | \n", + "
\n",
+ " במבט ראשון – זה פנטסטי!
\n",
+ " יצרנו טבלה שכוללת את שמות השחקנים בכל סרט, בלי להסתבך עם יצירת טבלה נוספת.
\n",
+ "
\n", + " נצייר ERD של הטבלה החדשה שלנו:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אם נקדיש עוד רגע להתבונן בישויות שיצרנו, נוכל למצוא כמה וכמה בעיות שהסידור הזה יוצר.
\n",
+ " יכולים לחשוב על כמה מהן?\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n", + " הנה כמה בעיות מרכזיות שנוצרות בעקבות הצורה בה בחרנו לבנות את הטבלה:\n", + "
\n", + "\n", + "\n",
+ " לכן, אחת מהפעולות החשובות בנרמול של מסד נתונים הוא לוודא את האי־פריקות (atomicity) של הנתונים בו.
\n",
+ " במילים פשוטות: שבכל תא בכל אחת מהטבלאות יש נתון שאי־אפשר לפרק ליחידות קטנות יותר.
\n",
+ " במקרה שלנו, כל תא בעמודה actor_name בנוי משמותיהם של כמה שחקנים, וזוהי הפרה של רעיון האי־פריקות.\n",
+ "
\n", + " כיצד נתקן את המצב?\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אחת הרעיונות המיידיים שאולי קפצו לכם לראש הוא ליצור עמודה עבור כל אחד מהשחקנים.
\n",
+ " התיקון הזה בעייתי, כיוון שהוא משאיר אותנו עם טבלה שקשה לתשאל.
\n",
+ " נסו לדמיין את הסרבול שבכתיבת שאילתה שבודקת אם שחקן מסוים שיחק במטריקס כשיש לנו 20 עמודות של שחקנים.
\n",
+ " בנוסף, הרעיון הזה עדיין לא מאפשר לנו להוסיף מידע בקלות על כל אחד מהשחקנים.\n",
+ "
\n",
+ " אפשרות אחרת שתפתור עבורנו את כל הבעיות שהצגנו היא ליצור שורה עבור כל שחקן.
\n",
+ " כך המידע יענה על דרישת האי־פריקות, ויאפשר לנו להוסיף בקלות מידע על אודות השחקנים שהשתתפו בסרט.\n",
+ "
\n", + " נוסיף עבור כל שחקן שורה משלו, ועל הדרך ניצור עמודה שבה יופיע תאריך לידתו של כל שחקן.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "title | \n", + "release_year | \n", + "actor_name | \n", + "actor_birth_date | \n", + "
---|---|---|---|
Monty Python and the Holy Grail | \n", + "1975 | \n", + "Graham Chapman | \n", + "1941-01-08 | \n", + "
Monty Python and the Holy Grail | \n", + "1975 | \n", + "John Cleese | \n", + "1939-10-27 | \n", + "
Monty Python and the Holy Grail | \n", + "1975 | \n", + "Eric Idle | \n", + "1943-03-29 | \n", + "
Monty Python and the Holy Grail | \n", + "1975 | \n", + "Terry Gilliam | \n", + "1940-11-22 | \n", + "
V for Vendetta | \n", + "2005 | \n", + "Hugo Weaving | \n", + "1960-04-04 | \n", + "
V for Vendetta | \n", + "2005 | \n", + "Rupert Graves | \n", + "1963-06-30 | \n", + "
V for Vendetta | \n", + "2005 | \n", + "Stephen Rea | \n", + "1946-10-31 | \n", + "
The Matrix | \n", + "1999 | \n", + "Keanu Reeves | \n", + "1964-09-02 | \n", + "
The Matrix | \n", + "1999 | \n", + "Laurence Fishburne | \n", + "1961-07-30 | \n", + "
The Matrix | \n", + "1999 | \n", + "Hugo Weaving | \n", + "1960-04-04 | \n", + "
\n", + " הצלחה!\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " נעדכן את ה־ERD:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אבל הנודניקים שבינינו שוב מעקמים את האף.
\n",
+ " \"גם בצורה הזו יש לא מעט בעיות!\", הם נזעקים.
\n",
+ "
\n", + " קחו רגע ונסו לחשוב מה הבעיות שעולות מהעיצוב החדש שיצרנו עבור מסד הנתונים.\n", + "
\n", + "\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " בנו טבלה שמכילה פרטים על משתמשים בקורס פייתון.
\n",
+ " הטבלה תכלול את שם המשתמש, תאריך יום ההולדת שלו (אם הוא סיפק כזה) ואת התרגילים שפתר.
\n",
+ " עבור כל תרגיל שפתר המשתמש קיימות התכונות הבאות: שם, מספר מחברת ומועד אחרון להגשה.\n",
+ "
\n",
+ " צרו ERD שמתאר את הטבלה שיצרתם.
\n",
+ " תוכלו להשתמש ב־vuerd, או בכל כלי אחר שיהיה לכם נוח עבור המשימה.\n",
+ "
\n",
+ " אנחנו בטוחים שהצלחתם לחשוב על לא מעט בעיות בצורה החדשה בה אירגנו את הטבלה.
\n",
+ " עבורנו, אחת הבעיות הראשונות שזועקות מהטבלה היא כפילות הנתונים.\n",
+ "
\n",
+ " אנחנו לא סתם נטפלים לכפילות הזו – היא ממש מסוכנת.
\n",
+ " נתמקד בכפילות של שחקנים – שהרי אותו שחקן יכול לשחק בכמה סרטים.
\n",
+ " בדוגמה שלנו, השחקן הנפלא הוגו ויבינג משחק גם בסרט The Matrix וגם בסרט V for Vendetta.\n",
+ "
\n",
+ " אם נרצה להוסיף סרט שבו משחק ויבינג, נצטרך לציין שוב את כל פרטי השחקן שלו – שכבר כתובים בשורה אחרת.
\n",
+ " בדוגמה שלנו זה רק תאריך יום ההולדת, אבל לו היו עמודות נוספות היינו צריכים להעתיק שוב את כולן. \n",
+ "
\n",
+ " זה אפילו לא החלק הגרוע ביותר. מה יקרה כשנרצה לעדכן את כתובת מגוריו של ויבינג, לדוגמה?
\n",
+ " נצטרך לכתוב שאילתה שתעבור ותשנה בכל השורות בהן הוא מופיע את כתובת המגורים שלו.
\n",
+ " ומה אם השאילתה תיכשל באמצע, או שהשאילתה שניסחנו לא הצליחה לתפוס את כל השורות בהן ויבינג מופיע?
\n",
+ "
\n",
+ " במקרה שכזה, כשנשאל את מסד הנתונים איפה ויבינג גר יחזרו לנו שתי כתובות שונות.
\n",
+ " זו אי־סדירות בנתונים שלנו – מצב שבו יש לנו נתונים סותרים במסד הנתונים, ממנו אנחנו חייבים להימנע בכל דרך.
\n",
+ " הבעיה הזו, שבה מידע כפול מסכן את עקביות הנתונים, נקראת חֲרִיגוּת עדכון (Update anomaly).\n",
+ "
\n",
+ " בעיה נוספת עלולה להיווצר כשננסה להוסיף סרט חדש.
\n",
+ " בסרט האנימציה המוכר Fantasia 2000, לדוגמה, לא ליהקו שחקנים.
\n",
+ " אם נרצה להוסיף אותו למסד הנתונים, לא ברור מה נכניס בעמודות השמורות למידע על השחקנים.
\n",
+ " הבעיה הזו, שבה אי אפשר להכניס מידע חדש בגלל חוסר במידע אחר, נקראת חֲרִיגוּת הכנסה (Insertion anomaly).\n",
+ "
\n",
+ " ומה באשר למחיקת נתונים?
\n",
+ " אם ישנו שחקן שמשחק רק בסרט אחד, מחיקה של הסרט ממסד הנתונים תמחק לנו את כל המידע שקיים לנו על השחקן.
\n",
+ " זה אומר שכשנרצה להוסיף את השחקן לסרט אחר בעתיד, יהיו חסרים לנו נתונים כמו יום ההולדת שלו.
\n",
+ " הבעיה הזו, שבה מחיקה של מידע לא נחוץ גוררת איבוד של מידע נחוץ, נקראת חֲרִיגוּת מחיקה (Deletion anomaly).\n",
+ "
\n",
+ " ויש עוד!
\n",
+ " אם נרצה להוסיף עבור כל אחד מהשחקנים את שמות הילדים שלו, נצטרך להוסיף שורה עבור כל ילד כדי לשמור על אי־פריקות.
\n",
+ " ואם נרצה לכלול את תחביביו של כל ילד?\n",
+ "
\n",
+ " בצורה הזו כל עמודה שנרצה להוסיף תגדיל לנו את מספר הישויות משמעותית.
\n",
+ " בסרט שבו יש 5 שחקנים, לכל שחקן יש 3 ילדים ולכל ילד 6 תחביבים,
\n",
+ " יוצא שעבור $1 + 5 + 3 + 6 = 15$ ישויות נצטרך ליצור $1 \\cdot 5 \\cdot 3 \\cdot 6 = 90$ ישויות במסד הנתונים!
\n",
+ "
\n",
+ " אם אתם זועקים מבעד למסך המחשב \"בסדר, בסדר, נודניק! נפריד לטבלאות!\" זה רק אומר שאתם נורמליים לחלוטין.
\n",
+ " נפריד לטבלאות.\n",
+ "
title | \n", + "release_year | \n", + "actor_name | \n", + "
---|---|---|
Monty Python and the Holy Grail | \n", + "1975 | \n", + "Graham Chapman | \n", + "
Monty Python and the Holy Grail | \n", + "1975 | \n", + "John Cleese | \n", + "
Monty Python and the Holy Grail | \n", + "1975 | \n", + "Eric Idle | \n", + "
Monty Python and the Holy Grail | \n", + "1975 | \n", + "Terry Gilliam | \n", + "
V for Vendetta | \n", + "2005 | \n", + "Hugo Weaving | \n", + "
V for Vendetta | \n", + "2005 | \n", + "Rupert Graves | \n", + "
V for Vendetta | \n", + "2005 | \n", + "Stephen Rea | \n", + "
The Matrix | \n", + "1999 | \n", + "Keanu Reeves | \n", + "
The Matrix | \n", + "1999 | \n", + "Laurence Fishburne | \n", + "
The Matrix | \n", + "1999 | \n", + "Hugo Weaving | \n", + "
name | \n", + "birth_date | \n", + "
---|---|
Graham Chapman | \n", + "1941-01-08 | \n", + "
John Cleese | \n", + "1939-10-27 | \n", + "
Eric Idle | \n", + "1943-03-29 | \n", + "
Terry Gilliam | \n", + "1940-11-22 | \n", + "
Hugo Weaving | \n", + "1960-04-04 | \n", + "
Rupert Graves | \n", + "1963-06-30 | \n", + "
Stephen Rea | \n", + "1946-10-31 | \n", + "
Keanu Reeves | \n", + "1964-09-02 | \n", + "
Laurence Fishburne | \n", + "1961-07-30 | \n", + "
Hugo Weaving | \n", + "1960-04-04 | \n", + "
\n",
+ " עכשיו נוכל להעשיר את טבלת actors מבלי לדאוג להשפעות שיהיו לעריכות שלנו על טבלת movies.
\n",
+ " נוסיף תאריך פטירה וגובה, ונמחק את הכפילות שיש לנו במסד הנתונים עבור השחקן הוגו ויבינג:\n",
+ "
name | \n", + "height | \n", + "birth_date | \n", + "death_date | \n", + "
---|---|---|---|
Graham Chapman | \n", + "191.0 | \n", + "1941-01-08 | \n", + "1989-10-04 | \n", + "
John Cleese | \n", + "196.0 | \n", + "1939-10-27 | \n", + "NULL | \n",
+ "
Eric Idle | \n", + "186.0 | \n", + "1943-03-29 | \n", + "NULL | \n",
+ "
Terry Gilliam | \n", + "175.0 | \n", + "1940-11-22 | \n", + "NULL | \n",
+ "
Hugo Weaving | \n", + "188.0 | \n", + "1960-04-04 | \n", + "NULL | \n",
+ "
Rupert Graves | \n", + "180.0 | \n", + "1963-06-30 | \n", + "NULL | \n",
+ "
Stephen Rea | \n", + "179.0 | \n", + "1946-10-31 | \n", + "NULL | \n",
+ "
Keanu Reeves | \n", + "186.0 | \n", + "1964-09-02 | \n", + "NULL | \n",
+ "
Laurence Fishburne | \n", + "184.0 | \n", + "1961-07-30 | \n", + "NULL | \n",
+ "
\n", + " מצוין!\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " נעדכן את ה־ERD:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "NULL
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ " נפנה לרגע תשומת לב לעמודת תאריך הפטירה, שמלאה ב־NULL
־ים.
\n",
+ " בעמודות מסוימות, כמו בעמודת תאריכי הפטירה, אנחנו מצפים לראות NULL
בחלק מהתאים.
\n",
+ "
\n",
+ " מתי נראה NULL
במסד הנתונים שלנו בדרך כלל?\n",
+ "
\n",
+ " לעומת עמודות שעשויות לכלול NULL
, יש עמודות שבהן אנחנו ממש בטוחים שלא יופיע NULL
.
\n",
+ " אחת מהן, לדוגמה, היא שם השחקן. אין משמעות להכנסת שחקן למסד הנתונים אם אנחנו לא יודעים את שמו.
\n",
+ " נוכל להגיד שאנחנו משוכנעים שהעמודה name לעולם לא תכיל NULL
.\n",
+ "
\n",
+ " יתרון משמעותי שמסדי נתונים טבלאיים מספקים לנו הוא היכולת לאכוף עקביות בנתונים שאנחנו מאחסנים בו.
\n",
+ " אפשר להגביל את הערכים שיכולים להיכנס לעמודה מסוימת במסד הנתונים בעזרת הגדרת אילוצים (constraints).\n",
+ "
\n",
+ " לדוגמה, על העמודה name בטבלה actors, נרצה להוסיף את האילוץ NOT NULL
– אילוץ שמוודא שהנתון שהוזן לעמודה אינו NULL
.
\n",
+ " לרוב, מי שבונה את מסד הנתונים הוא זה שמחליט על האילוצים בעמודות השונות.\n",
+ "
\n",
+ " אם תחזרו לתרשים שמתאר את מסד הנתונים, זה שמופיע בתחילת הפרק,
\n",
+ " תוכלו לזהות בקלות עבור אילו עמודות הוגדר האילוץ NOT NULL
.
\n",
+ " לצד העמודות שכן מתירות הזנת NULL
, מופיע סימן שאלה, שרומז על כך שלא חייב להיות בעמודה ערך.\n",
+ "
\n",
+ " נוסיף ל־ERD שלנו סימונים שמסמנים באילו עמודות עשוי להיות NULL
:\n",
+ "
NULL
.\n",
+ " בהמשך לתרגיל הקודם, הפרידו את הנתונים לטבלאות וסמנו שדות שניתן להזין בהם NULL
.
\n",
+ " הוסיפו עמודה שמכילה מידע על מצב התרגיל, ובה אחד מהערכים \"לא הוגש\", \"הוגש\" או \"נבדק\".\n",
+ "
\n",
+ " צרו ERD שמתאר את הטבלה שיצרתם.
\n",
+ " תוכלו להשתמש ב־vuerd, או בכל כלי אחר שיהיה לכם נוח עבור המשימה.\n",
+ "
\n",
+ " התגעגעתם למשחק שבו אתם מוצאים את הבעיה במסד הנתונים שיצרנו?
\n",
+ " מצאו את הבעיות שעשויות להיווצר מארגון הנתונים כפי שעשינו ב־ERD האחרון.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " הצלחתם לחשוב על משהו?
\n",
+ " אם תשקיעו מספיק זמן, ודאי תצליחו לחשוב על בעיות רבות.
\n",
+ " נתמקד באחת מרכזית: הייחודיות של כל ישות בטבלאות שיצרנו.
\n",
+ "
\n",
+ " נניח שבטבלת הסרטים מופיעים שני סרטים – Star Wars ו־Dial M for Murder.
\n",
+ " בסרט Star Wars משחק שחקן בשם John Williams.
\n",
+ " תוכלו למצוא אותו במסד הנתונים שמשמש אתכם לתרגול, בטבלת names תחת המזהה nm0002354.
\n",
+ " בסרט Dial M for Murder של היצ'קוק (תורגם ל\"אליבי\", אם תהיתם) משחק שחקן ששמו הוא... John Williams.
\n",
+ " המזהה שלו במסד הנתונים הוא nm0002369.\n",
+ "
\n",
+ " זו לא טעות!
\n",
+ " אמנם שמם של שני השחקנים זהה, אך מדובר בשחקנים שונים.
\n",
+ " כיצד נבדיל ביניהם? הרי בעמודת actor_name שבטבלת movies בשני המקרים יהיה כתוב John Williams.
\n",
+ " איך נדע לאיזה שחקן התא שבטבלת movies מתייחס?\n",
+ "
\n",
+ " הבעיה שלנו היא היכולת לזהות שחקן באופן חד־חד ערכי.
\n",
+ " שם של שחקן או שם של סרט הם נתונים שעשויים להיות בהם כפילויות, ולכן אינם מזהים טובים דיו.
\n",
+ " אם נרצה להתייחס לסרט \"The Lion King\" – איך נדע האם מדובר בקלאסיקה הנפלאה משנת 1994 או בפשע המודרני משנת 2019?
\n",
+ "
\n",
+ " אילו היינו מוסיפים לטבלת actors מזהה כלשהו, כמו nm0002354 עבור ג'ון הראשון ו־nm0002369 עבור ג'ון השני,
\n",
+ " היינו יכולים להתייחס בטבלת movies למזהה הזה במקום לשם השחקן.
\n",
+ " בצורה הזו היה קל לנו יותר להבין על איזה John Williams מדובר בכל אחד מהסרטים.\n",
+ "
\n",
+ " המסקנה המתבקשת היא שעבור כל ישות במסד הנתונים, אנחנו חייבים נתון שיאפשר לזהות אותה באופן ייחודי.
\n",
+ " אם היינו מנהלים מסד נתונים של מרשם האוכלוסין, שבו יש את פרטיהם של כל האזרחים במדינת ישראל.
\n",
+ " איזו שיטה יש לנו לזהות אדם מסוים בישראל באופן ייחודי?
\n",
+ " איך נדע להבדיל בין משה כהן אחד למשנהו?\n",
+ "
\n",
+ " השיטה שמצאו במשרד הפנים היא שכל אחד מהאנשים יקבל מספר ייחודי לו.
\n",
+ " אתם ודאי מכירים את המספר הזה בתור \"תעודת זהות\".
\n",
+ "
\n",
+ " במסדי נתונים נהוג להוסיף לכל טבלה עמודה שמאחסנת נתון ייחודי, כזה שישתנה בין ישות לישות ויזהה אותה באופן חד־חד ערכי.
\n",
+ " לפי הרעיון, לכל שורה מוקצה מזהה שלעולם לא יהיה NULL
ולעולם לא יחזור על עצמו באותה טבלה.\n",
+ "
\n",
+ " דרך פופולרית לממש מזהה ייחודי היא פשוט מספר רץ (autoincrement):
\n",
+ " השורה הראשונה שמתווספת לטבלה מקבלת את המזהה 1, השורה השנייה מקבלת את המזהה 2 וכן הלאה.
\n",
+ " זו דרך מצוינת להימנע ממצב בו אותו מזהה מופיע בשתי ישויות שונות באותה טבלה, ולשמור על דרך לזהות כל ישות.\n",
+ "
\n",
+ " הרעיון הנפלא הזה, של עמודה שמכילה נתון שמאפשר לנו לזהות באופן ייחודי כל ישות, נקרא \"מפתח ראשי\" (primary key).
\n",
+ " תוכלו לראות את המפתח הראשי בכל אחת מהטבלאות ב־ERD שמופיע בתחילת הפרק –
\n",
+ " הוא מסומן בעזרת סמליל של מפתח ליד שם העמודה.\n",
+ "
\n",
+ " ניצור מפתחות ראשיים עבור השחקנים והסרטים.
\n",
+ " במקום לציין את שם השחקן בטבלת הסרטים, נציין את המספר הייחודי לו:\n",
+ "
movie_id | \n", + "title | \n", + "release_year | \n", + "actor_id | \n", + "
---|---|---|---|
1 | \n", + "Monty Python and the Holy Grail | \n", + "1975 | \n", + "|
2 | \n", + "Monty Python and the Holy Grail | \n", + "1975 | \n", + "|
3 | \n", + "Monty Python and the Holy Grail | \n", + "1975 | \n", + "|
4 | \n", + "Monty Python and the Holy Grail | \n", + "1975 | \n", + "|
5 | \n", + "V for Vendetta | \n", + "2005 | \n", + "|
6 | \n", + "V for Vendetta | \n", + "2005 | \n", + "|
7 | \n", + "V for Vendetta | \n", + "2005 | \n", + "|
8 | \n", + "The Matrix | \n", + "1999 | \n", + "|
9 | \n", + "The Matrix | \n", + "1999 | \n", + "|
10 | \n", + "The Matrix | \n", + "1999 | \n", + "
actor_id | \n", + "name | \n", + "height | \n", + "birth_date | \n", + "death_date | \n", + "
---|---|---|---|---|
1 | \n", + "Graham Chapman | \n", + "191.0 | \n", + "1941-01-08 | \n", + "1989-10-04 | \n", + "
2 | \n", + "John Cleese | \n", + "196.0 | \n", + "1939-10-27 | \n", + "NULL | \n",
+ "
3 | \n", + "Eric Idle | \n", + "186.0 | \n", + "1943-03-29 | \n", + "NULL | \n",
+ "
4 | \n", + "Terry Gilliam | \n", + "175.0 | \n", + "1940-11-22 | \n", + "NULL | \n",
+ "
5 | \n", + "Hugo Weaving | \n", + "188.0 | \n", + "1960-04-04 | \n", + "NULL | \n",
+ "
6 | \n", + "Rupert Graves | \n", + "180.0 | \n", + "1963-06-30 | \n", + "NULL | \n",
+ "
7 | \n", + "Stephen Rea | \n", + "179.0 | \n", + "1946-10-31 | \n", + "NULL | \n",
+ "
8 | \n", + "Keanu Reeves | \n", + "186.0 | \n", + "1964-09-02 | \n", + "NULL | \n",
+ "
9 | \n", + "Laurence Fishburne | \n", + "184.0 | \n", + "1961-07-30 | \n", + "NULL | \n",
+ "
\n",
+ " נעדכן את ה־ERD כך שישקף את השינויים שביצענו בטבלאות.
\n",
+ " נוסיף עמודות של מפתחות ראשיים לכל אחת מהטבלאות, ונסמן אותן בהתאם.
\n",
+ " נשנה את העמודה actor_name בטבלת movies כך שתצביע על actor_id שבטבלת actors.
\n",
+ " נשנה את שם העמודה ל־actor_id, ונגדיר את טיפוס הנתונים שהיא מכילה כ־int
במקום כ־text
.
\n",
+ " ברוב ה־ERD־ים אתם תראו מפתח קטן ליד השורה שמייצגת את המפתח הראשי של הטבלה, או את ראשי התיבות \"PK\".\n",
+ "
\n",
+ " עבור כל ישות בטבלת movies, עמודת actor_id מצביעה על שחקן שבטבלת actors.
\n",
+ " לצורך כך היא משתמשת במפתח הראשי שבטבלת actors, עמודה ששמה actor_id, שמקצה מזהה ייחודי לכל שחקן.\n",
+ "
\n",
+ " הרעיון הזה מאפשר לנו להתייחס לשחקן מטבלה אחרת מבלי לחשוש מבעיה של רב־משמעות לגבי זהות השחקן.
\n",
+ " עמודה כמו actor_id בתוך טבלת movies נקראת \"מפתח זר\" (foreign key).
\n",
+ " אנחנו קוראים לעמודה \"מפתח זר\" כשהערכים שבה מצביעים על מפתח ראשי של טבלה אחרת.\n",
+ "
\n",
+ " אם נגדיר במסד הנתונים שעמודת actor_id מטבלת movies היא מפתח זר שמצביע לנתונים מעמודת actor_id שבטבלת actors,
\n",
+ "שימוש במספר שחקן לא קיים (נניח, 10) בעמודת actor_id שבטבלת movies יגרום להתרעה על חריגה.\n",
+ "
\n",
+ " בצורה הנוכחית של הטבלה, אנחנו יכולים להגיד שמתקיים בין טבלת movies לבין טבלת actors קשר שנקרא \"יחיד לרבים\".
\n",
+ " זהו מונח שנמצא בשימוש נפוץ. פירושו שבטבלת movies יכולות להיות שורות רבות שיצביעו על שחקן יחיד מטבלת actors.\n",
+ "
\n",
+ " נחדד: לפי העיצוב הנוכחי, כל שורה בטבלת השחקנים, יכולה להופיע בכמה שורות בטבלת הסרטים.
\n",
+ " באופן רשמי, נוכל להגיד ש\"כל ישות ב־actors יכולה להופיע בישויות רבות ב־movies\".
\n",
+ " קשר כזה בין טבלאות נקרא \"קשר יחיד לרבים\" (one-to-many relationship).\n",
+ "
\n",
+ " יש שתי דרכים לסמן ב־ERD קשר של יחיד לרבים.
\n",
+ " בשתיהן, נחבר את שתי העמודות הקשורות בקו.
\n",
+ " בדרך הראשונה, נכתוב ליד העמודה של ה\"רבים\" את האות N, וליד העמודה של ה\"יחיד\" את הספרה 1.
\n",
+ " בצורה השנייה, נסמן את הצד של ה\"יחיד\" בשני קווים או בקו שאחריו עיגול. את הצד של ה\"רבים\" נסמן כמשולש פתוח עם קו אופקי שיוצא מקודקודו.
\n",
+ " כל עוד לא העמקתם בקריאה נוספת באינטרנט על פשר הסימונים, העדיפו לסמן קשרים בדרך הראשונה.\n",
+ "
\n", + " בשביל העניין, נניח שאנחנו רוצים שטבלת הסרטים תכלול עמודה נוספת – השפה העיקרית שבה מדברים בסרט המקורי.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "movie_id | \n", + "title | \n", + "release_year | \n", + "actor_id | \n", + "original_language | \n", + "
---|---|---|---|---|
1 | \n", + "Monty Python and the Holy Grail | \n", + "1975 | \n", + "1 | \n", + "English | \n", + "
2 | \n", + "Monty Python and the Holy Grail | \n", + "1975 | \n", + "2 | \n", + "English | \n", + "
3 | \n", + "Monty Python and the Holy Grail | \n", + "1975 | \n", + "3 | \n", + "English | \n", + "
4 | \n", + "Monty Python and the Holy Grail | \n", + "1975 | \n", + "4 | \n", + "English | \n", + "
5 | \n", + "V for Vendetta | \n", + "2005 | \n", + "5 | \n", + "English | \n", + "
6 | \n", + "V for Vendetta | \n", + "2005 | \n", + "6 | \n", + "English | \n", + "
7 | \n", + "V for Vendetta | \n", + "2005 | \n", + "7 | \n", + "English | \n", + "
8 | \n", + "The Matrix | \n", + "1999 | \n", + "8 | \n", + "English | \n", + "
9 | \n", + "The Matrix | \n", + "1999 | \n", + "9 | \n", + "English | \n", + "
10 | \n", + "The Matrix | \n", + "1999 | \n", + "5 | \n", + "English | \n", + "
11 | \n", + "Cidade de Deus | \n", + "2002 | \n", + "10 | \n", + "Portuguese | \n", + "
12 | \n", + "Cidade de Deus | \n", + "2002 | \n", + "11 | \n", + "Portuguese | \n", + "
13 | \n", + "Cidade de Deus | \n", + "2002 | \n", + "12 | \n", + "Portuguese | \n", + "
14 | \n", + "Cidade de Deus | \n", + "2002 | \n", + "13 | \n", + "Portuguese | \n", + "
\n",
+ " שימו לב לכמות החזרות של English ו־Portuguese בעמודת original_language.
\n",
+ " נוכל למנוע את החזרה על המחרוזת אם נוציא את הערכים ב־original_language לטבלה נפרדת,
\n",
+ " וניצור קשר של יחיד לרבים בין טבלת השפות החדשה לטבלת הסרטים.
\n",
+ "
\n", + " ניצור את טבלת השפות:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "language_id | \n", + "name | \n", + "
---|---|
1 | \n", + "English | \n", + "
2 | \n", + "Portuguese | \n", + "
\n", + " ונעדכן את הערכים בעמודה החדשה שיצרנו ב־movies כך שיהיו מפתח זר למפתח הראשי language_id שבטבלת languages:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "movie_id | \n", + "title | \n", + "release_year | \n", + "actor_id | \n", + "language_id | \n", + "
---|---|---|---|---|
1 | \n", + "Monty Python and the Holy Grail | \n", + "1975 | \n", + "1 | \n", + "1 | \n", + "
2 | \n", + "Monty Python and the Holy Grail | \n", + "1975 | \n", + "2 | \n", + "1 | \n", + "
3 | \n", + "Monty Python and the Holy Grail | \n", + "1975 | \n", + "3 | \n", + "1 | \n", + "
4 | \n", + "Monty Python and the Holy Grail | \n", + "1975 | \n", + "4 | \n", + "1 | \n", + "
5 | \n", + "V for Vendetta | \n", + "2005 | \n", + "5 | \n", + "1 | \n", + "
6 | \n", + "V for Vendetta | \n", + "2005 | \n", + "6 | \n", + "1 | \n", + "
7 | \n", + "V for Vendetta | \n", + "2005 | \n", + "7 | \n", + "1 | \n", + "
8 | \n", + "The Matrix | \n", + "1999 | \n", + "8 | \n", + "1 | \n", + "
9 | \n", + "The Matrix | \n", + "1999 | \n", + "9 | \n", + "1 | \n", + "
10 | \n", + "The Matrix | \n", + "1999 | \n", + "5 | \n", + "1 | \n", + "
11 | \n", + "Cidade de Deus | \n", + "2002 | \n", + "10 | \n", + "2 | \n", + "
12 | \n", + "Cidade de Deus | \n", + "2002 | \n", + "11 | \n", + "2 | \n", + "
13 | \n", + "Cidade de Deus | \n", + "2002 | \n", + "12 | \n", + "2 | \n", + "
14 | \n", + "Cidade de Deus | \n", + "2002 | \n", + "13 | \n", + "2 | \n", + "
\n",
+ " הקשר הוא יחיד לרבים כיוון שכל שפה מטבלת השפות יכולה להופיע פעמים רבות בעמודה language_id שבטבלת movies.
\n",
+ " נבנה את ה־ERD המתאים:\n",
+ "
\n",
+ " בהמשך המחברת נזנח את התוספת של השפות לטבלת הסרטים כדי לפשט את הדוגמאות.
\n",
+ " אם תרצו, תוכלו לדמיין שהשינויים שעשינו קיימים גם בדוגמאות הבאות.\n",
+ "
\n",
+ " תיאורטית, היינו רוצים שכל שחקן יוכל לשחק בכמה סרטים.
\n",
+ " הצורה הנוכחית שבה סידרנו את הטבלאות מאפשרת לנו לעשות זאת באלגנטיות, באמצעות הקישור שעשינו בין טבלת actors לטבלת movies.\n",
+ "
\n",
+ " באותה צורה, נרצה שבכל סרט נוכל להצביע על כמה וכמה שחקנים.
\n",
+ " כרגע, כדי להשיג את המטרה הזו, אנחנו משכפלים את שורת הסרט, ובכל שורה שכזו מזינים מצביע לאחד השחקנים בסרט.
\n",
+ " כבר דנו לעיל בחסרונות המובהקים של שכפול נתונים, ובחריגויות שהחסרונות הללו מביאים איתם.
\n",
+ " ניגש לעיקר: איך מונעים את הכפילות בטבלת הסרטים, שומרים על האי־פריקות ועדיין מאפשרים כמה שחקנים באותו סרט?\n",
+ "
\n",
+ " המחשבה הראשונה שעשויה לעבור לנו בראש היא לאחסן מצביע לסרטים בטבלת השחקנים.
\n",
+ " הפתרון הזה ייצור לנו בעיה דומה לבעיה שיש לנו כרגע –
\n",
+ " הוא יכריח אותנו לשכפל את שורת השחקן שבטבלת actors כדי לגרום לו להצביע לכמה סרטים, ומהשכפול הזה אנחנו מנסים להימנע.\n",
+ "
\n", + " אז מה בכל זאת הפתרון?\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " הדרך הקלאסית לפתור את הבעיה הזו היא ליצור טבלה נוספת, שמייצגת את הקשרים שבין סרטים לבין שחקנים.
\n",
+ " כל שורה תכיל מספר מזהה של סרט, ומספר מזהה של שחקן.\n",
+ "
\n",
+ " נסו לדמיין את זה ויזואלית:
\n",
+ " טבלת movies וטבלת actors נמצאות זו לצד זו,
\n",
+ " והטבלה החדשה שניצור אומרת מאיזו שורה בטבלת movies למתוח קו, ולאיזו שורה בטבלת actors הקו אמור להגיע.\n",
+ "
movie_id | \n", + "title | \n", + "release_year | \n", + "
---|---|---|
1 | \n", + "Monty Python and the Holy Grail | \n", + "1975 | \n", + "
2 | \n", + "V for Vendetta | \n", + "2005 | \n", + "
3 | \n", + "The Matrix | \n", + "1999 | \n", + "
movie_id | \n", + "actor_id | \n", + "
---|---|
1 | \n", + "1 | \n", + "
1 | \n", + "2 | \n", + "
1 | \n", + "3 | \n", + "
1 | \n", + "4 | \n", + "
2 | \n", + "5 | \n", + "
2 | \n", + "6 | \n", + "
2 | \n", + "7 | \n", + "
3 | \n", + "5 | \n", + "
3 | \n", + "8 | \n", + "
3 | \n", + "9 | \n", + "
actor_id | \n", + "name | \n", + "height | \n", + "birth_date | \n", + "death_date | \n", + "
---|---|---|---|---|
1 | \n", + "Graham Chapman | \n", + "191.0 | \n", + "1941-01-08 | \n", + "1989-10-04 | \n", + "
2 | \n", + "John Cleese | \n", + "196.0 | \n", + "1939-10-27 | \n", + "NULL | \n",
+ "
3 | \n", + "Eric Idle | \n", + "186.0 | \n", + "1943-03-29 | \n", + "NULL | \n",
+ "
4 | \n", + "Terry Gilliam | \n", + "175.0 | \n", + "1940-11-22 | \n", + "NULL | \n",
+ "
5 | \n", + "Hugo Weaving | \n", + "188.0 | \n", + "1960-04-04 | \n", + "NULL | \n",
+ "
6 | \n", + "Rupert Graves | \n", + "180.0 | \n", + "1963-06-30 | \n", + "NULL | \n",
+ "
7 | \n", + "Stephen Rea | \n", + "179.0 | \n", + "1946-10-31 | \n", + "NULL | \n",
+ "
8 | \n", + "Keanu Reeves | \n", + "186.0 | \n", + "1964-09-02 | \n", + "NULL | \n",
+ "
9 | \n", + "Laurence Fishburne | \n", + "184.0 | \n", + "1961-07-30 | \n", + "NULL | \n",
+ "
\n",
+ " טבלת movie_actors היא טבלה שמכילה שני מפתחות זרים:
\n",
+ " אחד שמצביע על המפתח הראשי של טבלת movies, ואחד שמצביע על המפתח הראשי של טבלת actors.
\n",
+ " הטבלה הזו, שנקראת בעגה המקצועית טבלה מקשרת (junction table), היא מעין טבלת עזר.
\n",
+ " קיומה מאפשר לנו להגדיר קשר מורכב, בו שחקן יכול להופיע בכמה סרטים, ובכל סרט יכולים לשחק כמה שחקנים.
\n",
+ " הקשר הזה, שמוגדר בין movies לבין actors נקרא קשר רבים לרבים (many-to-many relationship).\n",
+ "
\n", + " בהמשך לתרגיל הקודם, הוסיפו לטבלאות שיצרתם מפתחות ראשיים וצרו ביניהם קשרי גומלין.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " צרו ERD שמתאר את הטבלה שיצרתם.
\n",
+ " תוכלו להשתמש ב־vuerd, או בכל כלי אחר שיהיה לכם נוח עבור המשימה.\n",
+ "
\n", + " במחברת זו ראינו את התהליך המחשבתי ואת השיקולים הבסיסיים שמלווים מתכנת בבניית מסד נתונים.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " צעד אחר צעד ביצענו תהליך שנקרא נרמול של מסד נתונים.
\n",
+ " התהליך אמור לוודא שמבנה מסד הנתונים יהיה בנוי באופן מיטבי:\n",
+ "
\n",
+ " למרות שביצענו את הנרמול בעזרת היגיון בריא וקצת משחקים עם טבלאות,
\n",
+ " הנושא של נרמול טבלאות נחקר לעומקו ונדון שוב ושוב בספרות אקדמית משמימה.
\n",
+ " לאורך השנים נוצרו \"חוקי נרמול\", שמטרתם לתת שיטה טכנית ומדורגת לנרמול של מסדי נתונים.\n",
+ "
\n", + " נסקור את חלקם בקצרה:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### צורה נורמלית ראשונה (1NF)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " טבלה היא מצורה נורמלית ראשונה אם אין בה נתונים מיותרים או כפולים.
\n",
+ " צורת נרמול זו מכריחה את הטבלה לעמוד בדרישות הבאות:\n",
+ "
\n", + " טבלה היא מצורה נורמלית שנייה אם היא עומדת בצורת הנורמליות הראשונה, וגם כל עמודה בטבלה תלויה בכל עמודות המפתח.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " הכלל הזה בעיקרון מתייחס למצבים מורכבים, בהם יש מפתחות ראשיים שמורכבים מ־2 עמודות או יותר (כן, זה אפשרי).
\n",
+ " תוכלו לראות מצב כזה בטבלת principals ב־ERD.
\n",
+ " אם המפתח הראשי שלכם מורכב מעמודה אחת בלבד – מזל טוב, הטבלה שלכם היא מצורה נורמלית שנייה.
\n",
+ "
\n",
+ " אם בטבלה שלכם יש מפתח ראשי שמורכב מכמה עמודות,
\n",
+ " הצורה הנורמלית השנייה דורשת שכל אחת מהעמודות שאינן עמודות המפתח, יהיו תלויות בכל עמודות המפתח.\n",
+ "
\n",
+ " ניקח מצב לדוגמה בו בנינו טבלה שבה יש עמודה למספר הסרט, ועמודה למספר השחקן.
\n",
+ " הגדרנו שהמפתח הראשי הוא שילוב של שתי העמודות הללו.
\n",
+ " טבלה שכזו היא ללא כפילויות, כך שטכנית היא עומדת בצורה הנורמלית הראשונה.\n",
+ "
\n",
+ " אבל אז נוסיף עמודות נוספות לטבלה, כמו שם הסרט, אורך הסרט ושם השחקן.
\n",
+ " אף אחת מהעמודות הללו לא מקיימת את התנאים של הצורה הנורמלית השנייה:
\n",
+ " העמודות של אורך הסרט ושל שם הסרט אינן תלויות בעמודה של מספר השחקן.
\n",
+ " בדיוק באותה מידה, העמודה של שם השחקן לא תלויה בעמודה של מספר הסרט.\n",
+ "
\n",
+ " כיוון שישנן עמודות שלא מתייחסות לכל המפתח הראשי, הטבלה אינה נחשבת מצורה נורמלית שנייה.
\n",
+ " כדי לתקן את המצב, נצטרך להפריד את הטבלאות ל־2 – לטבלת סרטים ולטבלת שחקנים.\n",
+ "
\n",
+ " טבלה היא מצורה נורמלית שלישית אם היא עומדת בצורת הנורמליות השנייה, וגם כל עמודה בטבלה לא תלויה בעמודות שאינן עמודות המפתח.
\n",
+ " במילים אחרות: אם ארצה לשנות עמודה מסוימת בטבלה, אסור שזה ישפיע על התוכן של עמודה אחרת.\n",
+ "
\n",
+ " לדוגמה, יצרתי טבלת סרטים שמורכבת ממספר סרט ושמו.
\n",
+ " הוספתי לטבלה שתי עמודות: שחקן וגובה השחקן.
\n",
+ " לאחר שהכנסנו קצת נתונים לטבלה הגיעו לאוזנינו שמועות שאחד השחקנים התפטר, ובמקומו הגיע שחקן חדש.
\n",
+ " שינינו את שמו של השחקן הישן לשחקן החדש, הפלנו את העט וטסנו הביתה לאכול סושי.\n",
+ "
\n",
+ " מה ששכחנו זה שיש לשחקן גם גובה, ואותו לא עדכנו.
\n",
+ " למעשה בניסיון לעדכון השחקן שברנו את אמינות הנתונים במסד הנתונים שלנו.
\n",
+ " כל זה היה יכול להימנע אם הייתה טבלה נפרדת של שחקנים, ורק הייתי צריך לעדכן בעמודת המפתח הזר את ה־id של השחקן הנכון.\n",
+ "
\n", + " הצורה הנורמלית השלישית, אם כך, דורשת שכל עמודה בטבלה תהיה תלויה במפתח הראשי, ולא באף עמודה אחרת.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ועוד" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " יש צורות נורמליות נוספות (BCNF, 4NF, 5NF, DKNF, 6NF) עליהן אפשר לקרוא כאן.
\n",
+ " לדעתנו, הדבר החשוב ביותר זה להבין את העקרונות הכלליים, ולא לעקוב שורה שורה אחרי חוקי הנרמול.
\n",
+ " הפרידו סוגים שונים של ישויות לטבלאות, ותמיד חשבו על המשתמש במסד הנתונים.
\n",
+ " כמה קל יהיה לו לתשאל את מסד הנתונים? כמה קל יהיה לו לשנות מידע? האם בעקבות פעולות עדכון המידע עלול להיהרס? \n",
+ "
\n",
+ " במחברת זו למדנו להכיר מונחים חשובים בעולם של מסדי נתונים רלציוניים.
\n",
+ " לצד המונחים הבסיסיים שהכרנו, כמו מפתח ראשי, מפתח זר וקשרי גומלין, למדנו ליצור מסד נתונים מנורמל.\n",
+ "
\n",
+ " ישנה חשיבות רבה ליצירה של מסד נתונים מנורמל היטב.
\n",
+ " מסד נתונים שכזה יהיה גמיש להרחבות עתידיות, יאפשר אחסון ושליפה בצורה יעילה ונוחה ויחסוך תקלות רבות בתפעולו.\n",
+ "
\n",
+ " זכרו שלא צריך לזכור כללי נרמול אקדמיים ומסובכים. מספיק להכיר את הרעיונות הבסיסיים ולהשתמש בהיגיון.
\n",
+ " אם היינו צריכים לבחור כלל אחד לזכור בקשר לנרמול, הוא היה זה:\n",
+ "
\n", + " הקפידו שכל טבלה במסד הנתונים שלכם (חוץ מהטבלאות המקשרות) תאגד נתונים שקשורים לנושא אחד בלבד," + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### This Is How We Do" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + " ושכל הנתונים בטבלה יתייחסו אך ורק לנושא הזה.
\n", + "
\n", + " בתכל'ס , כשאנחנו באים לבנות מסד נתונים, אלו השלבים שאנחנו עוברים:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "NULL
, לדוגמה.UNIQUE
ועל CHECK
.UNIQUE
, לדוגמה, אוסר על ערך להופיע פעמיים בעמודה. NOT NULL
לא מאפשר לערך שמוזן לעמודה להיות NULL
.\n",
+ " \n", + " צרו ERD מלא למערכת הגשת התרגילים.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " ה־ERD צריך להתחשב בכך שמסד הנתונים אמור להכיל את הנתונים הבאים:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " לצורך יצירת ה־ERD תוכלו להשתמש ב־vuerd, או בכל כלי אחר שתמצאו שנוח לכם.\n", + "
" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/content/week13/5_Joins.ipynb b/content/week13/5_Joins.ipynb new file mode 100644 index 0000000..03e8124 --- /dev/null +++ b/content/week13/5_Joins.ipynb @@ -0,0 +1,2903 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " עד כה, הסקנו תובנות ממסד הנתונים שלנו בעזרת שאילתות שהשתמשו כל פעם בנתונים מטבלה אחת.
\n",
+ " השאילתות שבנינו תשאלו טבלה מסוימת, והשתמשו במידע הקיים בה כדי לענות על שאלות מעניינות.\n",
+ "
\n",
+ " בשיעור הקודם למדנו על הצורך בביצוע נרמול למסד הנתונים, שמפזר מידע על פני כמה טבלאות.
\n",
+ " מצד אחד, הנרמול מסייע לנו לשמור מידע בצורה חסכונית ויעילה,
\n",
+ " ומצד שני – בשיטה שלמדנו עד כה אי אפשר לתשאל מידע ששמור ביותר מטבלה אחת.\n",
+ "
\n",
+ " כיוון שבחיים האמיתיים לרוב מסדי הנתונים שנתשאל יהיו (בתקווה) מנורמלים,
\n",
+ " נצטרך למצוא דרך להתייחס למידע שפרוש על פני כמה טבלאות במסד הנתונים שלנו.
\n",
+ " זה מה שנלמד במחברת הקרובה.\n",
+ "
\n", + " לפני שנתחיל, ניזכר כיצד נראה מסד הנתונים שעליו אנחנו עובדים:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " הצורך שעליו נרצה לענות במחברת הקרובה, כמו שעשינו עד עתה, הוא מציאת תשובות לשאלות.
\n",
+ " במחברת הזו, בניגוד למחברות הקודמות, כל שאלה תתייחס לנתונים שפזורים על פני כמה טבלאות.
\n",
+ " באיזה סרט שיחקו הכי הרבה שחקנים, ואיזה סרט תורגם להכי הרבה שפות?
\n",
+ " אילו שחקנים שיחקו במטריקס, ומה היה הסרט המצליח ביותר בו שיחק ג'ון טרבולטה?\n",
+ "
\n",
+ " כדי לענות על השאלות שהוצגו מעלה, נצטרך לתשאל יותר מטבלה אחת בכל פעם.
\n",
+ " דרך מקובלת לעשות זאת היא לצרף כמה טבלאות כך שיוצגו כטבלה אחת עם כל הנתונים שלנו, ולתשאל אותה.\n",
+ "
\n",
+ " בשיעור הקרוב נשחק עם הרעיון של צירוף טבלאות.
\n",
+ " בעזרת צירוף טבלאות, נוכל ליצור טבלה אחת שאותה יהיה קל לתשאל.\n",
+ "
\n",
+ " כדי להתחיל, נדמיין שתי טבלאות שישמשו לנו לדוגמה.
\n",
+ " אלו יהיו טבלאות פשוטות יחסית: טבלת \"צורות\" וטבלת \"בניינים\". \n",
+ "
\n",
+ " המשמעות של הביטוי \"צירוף בין הטבלאות\" עשויה להיות מעורפלת בשלב הזה.
\n",
+ " הכוונה, במקרה שלנו, היא ליצור טבלה זמנית שבנויה גם מהעמודות של טבלת צורות, וגם מהעמודות של טבלת בניינים.
\n",
+ "
\n",
+ " איך זה ייראה?
\n",
+ " ובכן, גם ההגדרה הזו קצת מעורפלת, ובפסקאות הבאות נסקור יותר מדרך אחת לצמד בין הטבלאות.
\n",
+ " לפני שניגש לעשות זאת, נסו לחשוב בעצמכם על כמה דרכים מועילות ליצור טבלה אחת משתי הטבלאות שמופיעות מעלה.\n",
+ "
\n",
+ " לפני שנתחיל ללמוד על הדרכים המועילות לצמד בין הטבלאות, נלמד על רעיון תיאורטי חשוב.
\n",
+ " הרעיון נקרא \"מכפלה קרטזית\", ובהשאלה לטבלאות שלנו – זו דרך מעניינת לצמד בין הישויות בטבלאות.\n",
+ "
\n", + " כדי לבצע מכפלה קרטזית, אנחנו פועלים לפי האלגוריתם הבא:\n", + "
\n", + "\n", + "\n",
+ " כך נבצע את התהליך עבור הטבלאות בניינים וצורות:
\n",
+ " לשורה הראשונה, בה כתוב \"עיגול\", נצמיד את כל הבניינים – עזריאלי 1, עזריאלי 2 והפנטגון, וניצור עבור כל בניין שורה חדשה.
\n",
+ " כך גם עבור המשולש (3 שורות, עבור 3 בניינים), עבור המעוין ועבור הריבוע.
\n",
+ " עבור כל איבר בשמאל, נצמד את כל האיברים בימין. \n",
+ "
\n", + " התוצאות יראו כך:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " לפניכם מוגדרות הטבלאות שהובאו כדוגמה.
\n",
+ " נסו לכתוב תוכנית פייתון שמבצעת מכפלה קרטזית בין הטבלאות.\n",
+ "
\n",
+ " ב־SQL, נוכל להשיג תוצאה של מכפלה קרטזית בין שתי טבלאות בעזרת CROSS JOIN
.
\n",
+ " זה יראה כך (השוו לתוצאות האיור שלמעלה):\n",
+ "
\n",
+ " מתי השימוש ב־CROSS JOIN
יכול להועיל?
\n",
+ " בעיקר כשאנחנו מעוניינים לקבל תובנה על כל השילובים האפשריים של שורות בין שתי טבלאות.\n",
+ "
\n",
+ " אם, לדוגמה, אנחנו מנהלים מסעדה, ואנחנו רוצים להציע לאורחים שלנו ארוחת בוקר עסקית.
\n",
+ " תמחור הארוחה ללקוח יהיה 100 ש\"ח, כשהארוחה כוללת שתייה, מנה עיקרית ועיתון.
\n",
+ " חשוב לנו לעשות רווח של לפחות 40 שקלים על כל ארוחה כזו שנמכרת.\n",
+ "
\n",
+ " בתפריט יש לנו 5 אפשרויות לשתייה, 6 אפשרויות למנה עיקרית ו־3 אפשרויות לעיתונים.
\n",
+ " העלות של המוצרים והמנות עבור הבעלים של המסעדה לא אחידה.
\n",
+ " איך נמצא שילובים בעייתיים שלא יניבו רווח של 40 שקלים?\n",
+ "
\n",
+ " הנתונים מסודרים בטבלאות במסד הנתונים שקיים בקובץ resources/restaurant.db.
\n",
+ " נראה את האפשרויות השונות, והעלות של כל אחת מהן לבעלי המסעדה:\n",
+ "
id | \n", + "name | \n", + "cost | \n", + "
---|---|---|
1 | \n", + "Coffee | \n", + "10 | \n", + "
2 | \n", + "Orange Juice | \n", + "9 | \n", + "
3 | \n", + "Nuka-Cola | \n", + "14 | \n", + "
4 | \n", + "Janx Shot | \n", + "4.2 | \n", + "
5 | \n", + "Milk of the Poppy | \n", + "11 | \n", + "
id | \n", + "name | \n", + "cost | \n", + "
---|---|---|
1 | \n", + "Caesar Salad | \n", + "36 | \n", + "
2 | \n", + "Xiaolongbao | \n", + "43 | \n", + "
3 | \n", + "Krabby Patties | \n", + "39 | \n", + "
4 | \n", + "Butterscotch Cinnamon Pie | \n", + "40 | \n", + "
5 | \n", + "Lava Soup | \n", + "34 | \n", + "
6 | \n", + "Lembas | \n", + "43 | \n", + "
id | \n", + "name | \n", + "cost | \n", + "
---|---|---|
1 | \n", + "The Washington Herald | \n", + "12 | \n", + "
2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "
3 | \n", + "Daily Bugle | \n", + "9 | \n", + "
\n",
+ " אם נצטרף את הטבלאות באמצעות CROSS JOIN
, נקבל את כל השילובים שהלקוח יכול לבחור מהתפריט.
\n",
+ " נכתוב זאת כך:\n",
+ "
\n", + " התוצאות יראו כך:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "id | \n", + "name | \n", + "cost | \n", + "id | \n", + "name | \n", + "cost | \n", + "id | \n", + "name | \n", + "cost | \n", + "total_cost | \n", + "
---|---|---|---|---|---|---|---|---|---|
1 | \n", + "Coffee | \n", + "10 | \n", + "1 | \n", + "Caesar Salad | \n", + "36 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "58 | \n", + "
1 | \n", + "Coffee | \n", + "10 | \n", + "1 | \n", + "Caesar Salad | \n", + "36 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "54 | \n", + "
1 | \n", + "Coffee | \n", + "10 | \n", + "1 | \n", + "Caesar Salad | \n", + "36 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "55 | \n", + "
1 | \n", + "Coffee | \n", + "10 | \n", + "2 | \n", + "Xiaolongbao | \n", + "43 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "65 | \n", + "
1 | \n", + "Coffee | \n", + "10 | \n", + "2 | \n", + "Xiaolongbao | \n", + "43 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "61 | \n", + "
1 | \n", + "Coffee | \n", + "10 | \n", + "2 | \n", + "Xiaolongbao | \n", + "43 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "62 | \n", + "
1 | \n", + "Coffee | \n", + "10 | \n", + "3 | \n", + "Krabby Patties | \n", + "39 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "61 | \n", + "
1 | \n", + "Coffee | \n", + "10 | \n", + "3 | \n", + "Krabby Patties | \n", + "39 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "57 | \n", + "
1 | \n", + "Coffee | \n", + "10 | \n", + "3 | \n", + "Krabby Patties | \n", + "39 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "58 | \n", + "
1 | \n", + "Coffee | \n", + "10 | \n", + "4 | \n", + "Butterscotch Cinnamon Pie | \n", + "40 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "62 | \n", + "
1 | \n", + "Coffee | \n", + "10 | \n", + "4 | \n", + "Butterscotch Cinnamon Pie | \n", + "40 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "58 | \n", + "
1 | \n", + "Coffee | \n", + "10 | \n", + "4 | \n", + "Butterscotch Cinnamon Pie | \n", + "40 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "59 | \n", + "
1 | \n", + "Coffee | \n", + "10 | \n", + "5 | \n", + "Lava Soup | \n", + "34 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "56 | \n", + "
1 | \n", + "Coffee | \n", + "10 | \n", + "5 | \n", + "Lava Soup | \n", + "34 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "52 | \n", + "
1 | \n", + "Coffee | \n", + "10 | \n", + "5 | \n", + "Lava Soup | \n", + "34 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "53 | \n", + "
1 | \n", + "Coffee | \n", + "10 | \n", + "6 | \n", + "Lembas | \n", + "43 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "65 | \n", + "
1 | \n", + "Coffee | \n", + "10 | \n", + "6 | \n", + "Lembas | \n", + "43 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "61 | \n", + "
1 | \n", + "Coffee | \n", + "10 | \n", + "6 | \n", + "Lembas | \n", + "43 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "62 | \n", + "
2 | \n", + "Orange Juice | \n", + "9 | \n", + "1 | \n", + "Caesar Salad | \n", + "36 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "57 | \n", + "
2 | \n", + "Orange Juice | \n", + "9 | \n", + "1 | \n", + "Caesar Salad | \n", + "36 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "53 | \n", + "
2 | \n", + "Orange Juice | \n", + "9 | \n", + "1 | \n", + "Caesar Salad | \n", + "36 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "54 | \n", + "
2 | \n", + "Orange Juice | \n", + "9 | \n", + "2 | \n", + "Xiaolongbao | \n", + "43 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "64 | \n", + "
2 | \n", + "Orange Juice | \n", + "9 | \n", + "2 | \n", + "Xiaolongbao | \n", + "43 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "60 | \n", + "
2 | \n", + "Orange Juice | \n", + "9 | \n", + "2 | \n", + "Xiaolongbao | \n", + "43 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "61 | \n", + "
2 | \n", + "Orange Juice | \n", + "9 | \n", + "3 | \n", + "Krabby Patties | \n", + "39 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "60 | \n", + "
2 | \n", + "Orange Juice | \n", + "9 | \n", + "3 | \n", + "Krabby Patties | \n", + "39 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "56 | \n", + "
2 | \n", + "Orange Juice | \n", + "9 | \n", + "3 | \n", + "Krabby Patties | \n", + "39 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "57 | \n", + "
2 | \n", + "Orange Juice | \n", + "9 | \n", + "4 | \n", + "Butterscotch Cinnamon Pie | \n", + "40 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "61 | \n", + "
2 | \n", + "Orange Juice | \n", + "9 | \n", + "4 | \n", + "Butterscotch Cinnamon Pie | \n", + "40 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "57 | \n", + "
2 | \n", + "Orange Juice | \n", + "9 | \n", + "4 | \n", + "Butterscotch Cinnamon Pie | \n", + "40 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "58 | \n", + "
2 | \n", + "Orange Juice | \n", + "9 | \n", + "5 | \n", + "Lava Soup | \n", + "34 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "55 | \n", + "
2 | \n", + "Orange Juice | \n", + "9 | \n", + "5 | \n", + "Lava Soup | \n", + "34 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "51 | \n", + "
2 | \n", + "Orange Juice | \n", + "9 | \n", + "5 | \n", + "Lava Soup | \n", + "34 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "52 | \n", + "
2 | \n", + "Orange Juice | \n", + "9 | \n", + "6 | \n", + "Lembas | \n", + "43 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "64 | \n", + "
2 | \n", + "Orange Juice | \n", + "9 | \n", + "6 | \n", + "Lembas | \n", + "43 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "60 | \n", + "
2 | \n", + "Orange Juice | \n", + "9 | \n", + "6 | \n", + "Lembas | \n", + "43 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "61 | \n", + "
3 | \n", + "Nuka-Cola | \n", + "14 | \n", + "1 | \n", + "Caesar Salad | \n", + "36 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "62 | \n", + "
3 | \n", + "Nuka-Cola | \n", + "14 | \n", + "1 | \n", + "Caesar Salad | \n", + "36 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "58 | \n", + "
3 | \n", + "Nuka-Cola | \n", + "14 | \n", + "1 | \n", + "Caesar Salad | \n", + "36 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "59 | \n", + "
3 | \n", + "Nuka-Cola | \n", + "14 | \n", + "2 | \n", + "Xiaolongbao | \n", + "43 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "69 | \n", + "
3 | \n", + "Nuka-Cola | \n", + "14 | \n", + "2 | \n", + "Xiaolongbao | \n", + "43 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "65 | \n", + "
3 | \n", + "Nuka-Cola | \n", + "14 | \n", + "2 | \n", + "Xiaolongbao | \n", + "43 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "66 | \n", + "
3 | \n", + "Nuka-Cola | \n", + "14 | \n", + "3 | \n", + "Krabby Patties | \n", + "39 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "65 | \n", + "
3 | \n", + "Nuka-Cola | \n", + "14 | \n", + "3 | \n", + "Krabby Patties | \n", + "39 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "61 | \n", + "
3 | \n", + "Nuka-Cola | \n", + "14 | \n", + "3 | \n", + "Krabby Patties | \n", + "39 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "62 | \n", + "
3 | \n", + "Nuka-Cola | \n", + "14 | \n", + "4 | \n", + "Butterscotch Cinnamon Pie | \n", + "40 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "66 | \n", + "
3 | \n", + "Nuka-Cola | \n", + "14 | \n", + "4 | \n", + "Butterscotch Cinnamon Pie | \n", + "40 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "62 | \n", + "
3 | \n", + "Nuka-Cola | \n", + "14 | \n", + "4 | \n", + "Butterscotch Cinnamon Pie | \n", + "40 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "63 | \n", + "
3 | \n", + "Nuka-Cola | \n", + "14 | \n", + "5 | \n", + "Lava Soup | \n", + "34 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "60 | \n", + "
3 | \n", + "Nuka-Cola | \n", + "14 | \n", + "5 | \n", + "Lava Soup | \n", + "34 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "56 | \n", + "
3 | \n", + "Nuka-Cola | \n", + "14 | \n", + "5 | \n", + "Lava Soup | \n", + "34 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "57 | \n", + "
3 | \n", + "Nuka-Cola | \n", + "14 | \n", + "6 | \n", + "Lembas | \n", + "43 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "69 | \n", + "
3 | \n", + "Nuka-Cola | \n", + "14 | \n", + "6 | \n", + "Lembas | \n", + "43 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "65 | \n", + "
3 | \n", + "Nuka-Cola | \n", + "14 | \n", + "6 | \n", + "Lembas | \n", + "43 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "66 | \n", + "
4 | \n", + "Janx Shot | \n", + "4.2 | \n", + "1 | \n", + "Caesar Salad | \n", + "36 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "52.2 | \n", + "
4 | \n", + "Janx Shot | \n", + "4.2 | \n", + "1 | \n", + "Caesar Salad | \n", + "36 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "48.2 | \n", + "
4 | \n", + "Janx Shot | \n", + "4.2 | \n", + "1 | \n", + "Caesar Salad | \n", + "36 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "49.2 | \n", + "
4 | \n", + "Janx Shot | \n", + "4.2 | \n", + "2 | \n", + "Xiaolongbao | \n", + "43 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "59.2 | \n", + "
4 | \n", + "Janx Shot | \n", + "4.2 | \n", + "2 | \n", + "Xiaolongbao | \n", + "43 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "55.2 | \n", + "
4 | \n", + "Janx Shot | \n", + "4.2 | \n", + "2 | \n", + "Xiaolongbao | \n", + "43 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "56.2 | \n", + "
4 | \n", + "Janx Shot | \n", + "4.2 | \n", + "3 | \n", + "Krabby Patties | \n", + "39 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "55.2 | \n", + "
4 | \n", + "Janx Shot | \n", + "4.2 | \n", + "3 | \n", + "Krabby Patties | \n", + "39 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "51.2 | \n", + "
4 | \n", + "Janx Shot | \n", + "4.2 | \n", + "3 | \n", + "Krabby Patties | \n", + "39 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "52.2 | \n", + "
4 | \n", + "Janx Shot | \n", + "4.2 | \n", + "4 | \n", + "Butterscotch Cinnamon Pie | \n", + "40 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "56.2 | \n", + "
4 | \n", + "Janx Shot | \n", + "4.2 | \n", + "4 | \n", + "Butterscotch Cinnamon Pie | \n", + "40 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "52.2 | \n", + "
4 | \n", + "Janx Shot | \n", + "4.2 | \n", + "4 | \n", + "Butterscotch Cinnamon Pie | \n", + "40 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "53.2 | \n", + "
4 | \n", + "Janx Shot | \n", + "4.2 | \n", + "5 | \n", + "Lava Soup | \n", + "34 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "50.2 | \n", + "
4 | \n", + "Janx Shot | \n", + "4.2 | \n", + "5 | \n", + "Lava Soup | \n", + "34 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "46.2 | \n", + "
4 | \n", + "Janx Shot | \n", + "4.2 | \n", + "5 | \n", + "Lava Soup | \n", + "34 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "47.2 | \n", + "
4 | \n", + "Janx Shot | \n", + "4.2 | \n", + "6 | \n", + "Lembas | \n", + "43 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "59.2 | \n", + "
4 | \n", + "Janx Shot | \n", + "4.2 | \n", + "6 | \n", + "Lembas | \n", + "43 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "55.2 | \n", + "
4 | \n", + "Janx Shot | \n", + "4.2 | \n", + "6 | \n", + "Lembas | \n", + "43 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "56.2 | \n", + "
5 | \n", + "Milk of the Poppy | \n", + "11 | \n", + "1 | \n", + "Caesar Salad | \n", + "36 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "59 | \n", + "
5 | \n", + "Milk of the Poppy | \n", + "11 | \n", + "1 | \n", + "Caesar Salad | \n", + "36 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "55 | \n", + "
5 | \n", + "Milk of the Poppy | \n", + "11 | \n", + "1 | \n", + "Caesar Salad | \n", + "36 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "56 | \n", + "
5 | \n", + "Milk of the Poppy | \n", + "11 | \n", + "2 | \n", + "Xiaolongbao | \n", + "43 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "66 | \n", + "
5 | \n", + "Milk of the Poppy | \n", + "11 | \n", + "2 | \n", + "Xiaolongbao | \n", + "43 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "62 | \n", + "
5 | \n", + "Milk of the Poppy | \n", + "11 | \n", + "2 | \n", + "Xiaolongbao | \n", + "43 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "63 | \n", + "
5 | \n", + "Milk of the Poppy | \n", + "11 | \n", + "3 | \n", + "Krabby Patties | \n", + "39 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "62 | \n", + "
5 | \n", + "Milk of the Poppy | \n", + "11 | \n", + "3 | \n", + "Krabby Patties | \n", + "39 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "58 | \n", + "
5 | \n", + "Milk of the Poppy | \n", + "11 | \n", + "3 | \n", + "Krabby Patties | \n", + "39 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "59 | \n", + "
5 | \n", + "Milk of the Poppy | \n", + "11 | \n", + "4 | \n", + "Butterscotch Cinnamon Pie | \n", + "40 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "63 | \n", + "
5 | \n", + "Milk of the Poppy | \n", + "11 | \n", + "4 | \n", + "Butterscotch Cinnamon Pie | \n", + "40 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "59 | \n", + "
5 | \n", + "Milk of the Poppy | \n", + "11 | \n", + "4 | \n", + "Butterscotch Cinnamon Pie | \n", + "40 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "60 | \n", + "
5 | \n", + "Milk of the Poppy | \n", + "11 | \n", + "5 | \n", + "Lava Soup | \n", + "34 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "57 | \n", + "
5 | \n", + "Milk of the Poppy | \n", + "11 | \n", + "5 | \n", + "Lava Soup | \n", + "34 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "53 | \n", + "
5 | \n", + "Milk of the Poppy | \n", + "11 | \n", + "5 | \n", + "Lava Soup | \n", + "34 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "54 | \n", + "
5 | \n", + "Milk of the Poppy | \n", + "11 | \n", + "6 | \n", + "Lembas | \n", + "43 | \n", + "1 | \n", + "The Washington Herald | \n", + "12 | \n", + "66 | \n", + "
5 | \n", + "Milk of the Poppy | \n", + "11 | \n", + "6 | \n", + "Lembas | \n", + "43 | \n", + "2 | \n", + "The Springfield Shopper | \n", + "8 | \n", + "62 | \n", + "
5 | \n", + "Milk of the Poppy | \n", + "11 | \n", + "6 | \n", + "Lembas | \n", + "43 | \n", + "3 | \n", + "Daily Bugle | \n", + "9 | \n", + "63 | \n", + "
\n",
+ " חדי העין שמו לב שהוספנו לתוצאות עמודה נוספת בשם total_cost – עלות הארוחה לבעלי המסעדה.
\n",
+ " זו השאילתה שבה השתמשנו כדי להשיג את העלות הזו:\n",
+ "
\n",
+ " שימו לב לצורת ההתייחסות לעמודות בעזרת הנקודה.
\n",
+ " מסד הנתונים לא יכול לדעת לאיזה עמודת cost אנחנו מתייחסים בכל פעם.
\n",
+ " לכן ציינו במפורש לפני כל שם עמודה את הטבלה ממנה היא מגיעה, בצורה table_name.column_name
.\n",
+ "
\n",
+ " נראה, אם כך, שיש לא מעט מנות שהרווח עליהן הוא פחות מ־40 שקלים.
\n",
+ " נוכל לראות את כמות השילובים ההפסדיים בעזרת השאילתה הבאה:\n",
+ "
\n",
+ " מה הרווח הנמוך ביותר שבעל המסעדה יעשה על ארוחה עסקית, אם הלקוח יבחר את המוצרים היקרים ביותר?
\n",
+ " כתבו שאילתה אחת שעונה על השאלה.\n",
+ "
\n",
+ " אחרי שהשתעשענו, נספר בלחישה של־CROSS JOIN
יש מעט מאוד שימושים בעולם האמיתי.
\n",
+ " השימוש העיקרי בו, עבורנו לפחות, הוא ללמד אתכם על INNER JOIN
בקלות.\n",
+ "
\n",
+ " השימוש ב־CROSS JOIN
יצר כמות עצומה של תוצאות.
\n",
+ " עבור 4 טבלאות שבכל אחת מהן יש 100 רשומות, CROSS JOIN
יחזיר 100,000,000 רשומות.\n",
+ "
\n",
+ " רוב הפעמים, אנחנו לא צריכים את כל השילובים בין עמודות – אלא רק שילובים שיועילו לנו.
\n",
+ " אם נביט שוב בטבלאות הדוגמה שלנו, נגלה עמודה שמשותפת לשתי הטבלאות:\n",
+ "
\n",
+ " נרצה ליצור טבלה חדשה – צירוף של הטבלה צורות עם הטבלה בניינים.
\n",
+ " נתחיל בלבצע מכפלה קרטזית בין הטבלאות,
\n",
+ " ואז נסיר את כל השורות שבהן העמודה שם שבטבלת צורות והעמודה צורה שבטבלת בניינים לא תואמות.\n",
+ "
\n",
+ " כלומר, מתוצאות המכפלה הקרטזית נבחר רק את השורות שבהן הצורה בשתי הטבלאות זהה.
\n",
+ " נראה תוצאה לדוגמה:\n",
+ "
\n",
+ " בתוצאות של הטבלה החדשה שנוצרה הושמטו שורות רבות:
\n",
+ " תחילה, הושמטו כל השורות מטבלת צורות שאין להן שורות עם צורה תואמת בטבלת בניינים.
\n",
+ " באותו אופן, הושמטו גם השורות מטבלת בניינים עבורן אין צורה עם שם תואם בטבלת צורות.\n",
+ "
\n",
+ " פרט חשוב במיוחד הוא שעבור כל שורה יכולה להיות יותר מהתאמה אחת.
\n",
+ " לדוגמה, אם נוסיף לטבלת \"בניינים\" עוד בניין שצורתו ריבוע,
\n",
+ " התוצאות יכללו שני בניינים שצורתם \"ריבוע\" בטבלת התוצאות – גם את הבניין שהוספנו, וגם את הבניין שהיה שם:\n",
+ "
\n",
+ " אם היינו מוסיפים לטבלת צורות, סתם בשביל הניסוי, שורה נוספת ששמה \"ריבוע\" עם תמונה של קרוקודיל,
\n",
+ " היינו מקבלים שתי שורות נוספות עם תמונת קרוקודיל בטבלת התוצאות – אחת עבור עזריאלי 2 ואחת עבור הבית של שרלוק.\n",
+ "
\n",
+ " נסכם:
\n",
+ " בסוג החדש הזה של צירוף בין טבלאות, לקחנו את המכפלה הקרטזית בין שתי הטבלאות.
\n",
+ " לפני שהחזרנו את התוצאות, הסתכלנו על עמודה מסוימת מכל אחת מהטבלאות, והגדרנו את הקשר שאנחנו מחפשים שיהיה ביניהן.
\n",
+ " רשומות שלא קיימו את הקשר שהגדרנו לא הופיעו כתוצאה.\n",
+ "
\n",
+ " הצורה הזו לחיבור טבלאות, בה אנחנו מסננים את כל השורות שלא מתאימות לתנאי שלנו, נקראת INNER JOIN
.
\n",
+ " היא ככל הנראה צורת ה־JOIN
השימושית ביותר שנלמד.
\n",
+ " מבחינה תחבירית היא נראית כך:\n",
+ "
\n",
+ " בניגוד ל־CROSS JOIN
, בה פשוט ביקשנו להצליב בין שתי טבלאות,
\n",
+ " כאן אנחנו מצרפים את מילת המפתח ON
, שעובדת פחות או יותר כמו פסוקית WHERE
.\n",
+ "
\n", + " אפשר לדמיין את מה שקורה מאחורי הקלעים בצורה הבאה:\n", + "
\n", + "\n", + "CROSS JOIN
) בין שתי הטבלאות.\n",
+ " הביטו שוב באיור שלמעלה וודאו שאתם מבינים איך INNER JOIN
עובד.\n",
+ "
\n",
+ " כדי להבין את הפוטנציאל המשוגע שגלום ב־INNER JOIN
,
\n",
+ " ננסה להבין אילו בעלי תפקידים השתתפו בסרט \"The Lego Movie\".
\n",
+ " לצורך כך נצרף את טבלת principals לטבלת movies:\n",
+ "
\n",
+ " מה עשינו כאן?
\n",
+ " ביקשנו לצרף ליד כל שורה בטבלת movies שורה מטבלת principals,
\n",
+ " בכל מקום שבו ה־movie_id שלהם תואם.
\n",
+ " מתוך התוצאות, שלפנו רק את השורות שבהן שם הסרט הוא \"The Lego Movie\".\n",
+ "
\n", + " לפניכם תוצאות השאילתה:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "id | \n", + "title | \n", + "original_title | \n", + "year | \n", + "avg_vote | \n", + "votes | \n", + "duration | \n", + "budget | \n", + "gross_income | \n", + "movie_id | \n", + "ordering | \n", + "name_id | \n", + "job_id | \n", + "characters | \n", + "
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
tt1490017 | \n", + "The Lego Movie | \n", + "The Lego Movie | \n", + "2,014 | \n", + "7.7 | \n", + "319,327 | \n", + "100 | \n", + "\\$ 60000000 | \n", + "\\$ 468060692 | \n", + "tt1490017 | \n", + "1 | \n", + "nm0695435 | \n", + "2 | \n", + "Emmet Brickowski | \n", + "
tt1490017 | The Lego Movie | The Lego Movie | 2,014 | 7.7 | 319,327 | 100 | \\$ 60000000 | \\$ 468060692 | tt1490017 | 10 | nm9204084 | 7 | |
tt1490017 | The Lego Movie | The Lego Movie | 2,014 | 7.7 | 319,327 | 100 | \\$ 60000000 | \\$ 468060692 | tt1490017 | 2 | nm0002071 | 2 | Lord Business,President Business,The Man Upstairs |
tt1490017 | The Lego Movie | The Lego Movie | 2,014 | 7.7 | 319,327 | 100 | \\$ 60000000 | \\$ 468060692 | tt1490017 | 3 | nm0006969 | 1 | Wyldstyle,Lucy |
tt1490017 | The Lego Movie | The Lego Movie | 2,014 | 7.7 | 319,327 | 100 | \\$ 60000000 | \\$ 468060692 | tt1490017 | 4 | nm0004715 | 2 | Batman,Bruce Wayne |
tt1490017 | The Lego Movie | The Lego Movie | 2,014 | 7.7 | 319,327 | 100 | \\$ 60000000 | \\$ 468060692 | tt1490017 | 5 | nm0588087 | 3 | |
tt1490017 | The Lego Movie | The Lego Movie | 2,014 | 7.7 | 319,327 | 100 | \\$ 60000000 | \\$ 468060692 | tt1490017 | 6 | nm0520488 | 3 | |
tt1490017 | The Lego Movie | The Lego Movie | 2,014 | 7.7 | 319,327 | 100 | \\$ 60000000 | \\$ 468060692 | tt1490017 | 7 | nm1087952 | 7 | |
tt1490017 | The Lego Movie | The Lego Movie | 2,014 | 7.7 | 319,327 | 100 | \\$ 60000000 | \\$ 468060692 | tt1490017 | 8 | nm1156984 | 7 | |
tt1490017 | The Lego Movie | The Lego Movie | 2,014 | 7.7 | 319,327 | 100 | \\$ 60000000 | \\$ 468060692 | tt1490017 | 9 | nm9204083 | 7 |
\n",
+ " פסוקית ה־JOIN
תתבצע תמיד ישירות אחרי פסוקית ה־FROM
,
\n",
+ " כך שמדובר בשאילתה רגילה לכל דבר, רק שהרחבנו קצת את הטבלה שעליה אנחנו שולפים.
\n",
+ " עכשיו נוכל לספור כמה בעלי תפקידים היו בסרט באמצעות COUNT
, כרגיל:\n",
+ "
\n",
+ " זה הזמן ללמוד תעלול נחמד.
\n",
+ " בשיעורים הקודמים השתמשנו במילת המפתח AS
כדי לערוך את שמן של עמודות.
\n",
+ " רבים משתמשים באותה שיטה כדי לקצר את שמות הטבלאות שלהם, ולהפוך את השאילתה לנוחה יותר לעין.
\n",
+ " אפשר לנקוט באותה צורה גם פה.\n",
+ "
\n",
+ " נדגים דרך קצרה אפילו יותר – פשוט נרשום את השם המקוצר אחרי שם הטבלה.
\n",
+ " זה נראה כך:\n",
+ "
\n",
+ " יש לא מעט אנשים שמעדיפים להשאיר את מילת המפתח AS
בקוד.
\n",
+ " לשיטתם, צורת הכתיבה הזו נקייה יותר ומאפשרת להפריד חזותית בין שם הטבלה לבין הקיצור שמייצג אותה.
\n",
+ " אנחנו בחרנו להשאיר את מילת המפתח AS
בהמשך ההסברים.\n",
+ "
\n", + " ננסה להציג את שמות כל בעלי התפקידים ב־The Lego Movie.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " כשחושבים על זה? אתם מסוגלים לעשות את זה לבד.
\n",
+ " (אהמ... רמז... כותרת...)\n",
+ "
\n",
+ " הציגו את שמות כל בעלי התפקידים ב־The Lego Movie.
\n",
+ " ליד שמם, הציגו מה היה התפקיד שלהם.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " קודם כול, ננקה את השאילתה ונצמצם שדות לא מועילים.
\n",
+ " נציין בפסוקית ה־SELECT
רק את העמודות שעשויות להועיל לנו, בינתיים:\n",
+ "
\n", + " ונקבל את התוצאה הסתומה הבאה:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "id | original_title | name_id | job_id | characters |
---|---|---|---|---|
tt1490017 | The Lego Movie | nm0695435 | 2 | Emmet Brickowski |
tt1490017 | The Lego Movie | nm9204084 | 7 | |
tt1490017 | The Lego Movie | nm0002071 | 2 | Lord Business,President Business,The Man Upstairs |
tt1490017 | The Lego Movie | nm0006969 | 1 | Wyldstyle,Lucy |
tt1490017 | The Lego Movie | nm0004715 | 2 | Batman,Bruce Wayne |
tt1490017 | The Lego Movie | nm0588087 | 3 | |
tt1490017 | The Lego Movie | nm0520488 | 3 | |
tt1490017 | The Lego Movie | nm1087952 | 7 | |
tt1490017 | The Lego Movie | nm1156984 | 7 | |
tt1490017 | The Lego Movie | nm9204083 | 7 |
\n",
+ " בתוצאה אנחנו רואים את כל השחקנים ששיחקו ב־The Lego Movie ואת תפקידיהם.
\n",
+ " העניין הוא שהטבלה נורמלה, כך שהשם האמיתי והשם המילולי של העבודה מופיעים בטבלאות אחרות. \n",
+ "
\n",
+ " בתוצאה הזו נראה שיש לנו את המספרים הסידוריים של שם בעל התפקיד וסוג העבודה שהוא מבצע.
\n",
+ " ניגש למלאכה של חילוץ \"הערכים האמיתיים\" של כל אחד מהם.\n",
+ "
\n",
+ " ראשית נטפל בעמודה job_id.
\n",
+ " לפי ה־ERD, נוכל לראות שהיא מפתח זר למפתח הראשי id בטבלת jobs.
\n",
+ " נצרף את הטבלאות אחת לשנייה בעזרת INNER JOIN
, ונוסיף ל־SELECT
את השדות שצורפו מטבלת jobs.\n",
+ "
\n",
+ " שימו לב שלא חידשנו פה שום דבר הנוגע ל־JOIN
– פשוט צירפנו עוד טבלה לחגיגה.
\n",
+ " אחרי שהשגנו את job_id מטבלת principals, התבקש ממש שנשתמש בו אל מול טבלת jobs.\n",
+ "
\n",
+ " שימו לב שבפסוקית ה־SELECT ביקשנו להציג את כל השדות של jobs.
\n",
+ " ביטוי מקוצר נחמד שישיג תוצאה זהה הוא j.*
, שמסמל את כל העמודות הקיימות בטבלה j
.
\n",
+ " נדגים:\n",
+ "
\n", + " התוצאות שחזרו הן:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "id | original_title | name_id | job_id | characters | id | name |
---|---|---|---|---|---|---|
tt1490017 | The Lego Movie | nm0695435 | 2 | Emmet Brickowski | 2 | actor |
tt1490017 | The Lego Movie | nm0002071 | 2 | Lord Business,President Business,The Man Upstairs | 2 | actor |
tt1490017 | The Lego Movie | nm0006969 | 1 | Wyldstyle,Lucy | 1 | actress |
tt1490017 | The Lego Movie | nm0004715 | 2 | Batman,Bruce Wayne | 2 | actor |
tt1490017 | The Lego Movie | nm0588087 | 3 | 3 | director | |
tt1490017 | The Lego Movie | nm0520488 | 3 | 3 | director | |
tt1490017 | The Lego Movie | nm1087952 | 7 | 7 | writer | |
tt1490017 | The Lego Movie | nm1156984 | 7 | 7 | writer | |
tt1490017 | The Lego Movie | nm9204083 | 7 | 7 | writer | |
tt1490017 | The Lego Movie | nm9204084 | 7 | 7 | writer |
\n",
+ " מצוין!
\n",
+ " בטבלה שלמעלה אנחנו רואים מה היה תפקידם של אותם בעלי תפקידים מפורסמים שעזרו ביצירה של The Lego Movie.
\n",
+ " עכשיו נחשוף שמותיהם של אותם בעלי תפקידים מ־names, באותה צורה שבה עשינו זאת עם jobs.\n",
+ "
\n", + " נסו לצרף לשאילתה הנוכחית את שמות בעלי התפקידים שסייעו בהפקת The Lego Movie.\n", + "
\n", + "\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n",
+ " בצירוף של טבלת names אין שום חידוש.
\n",
+ " נעשה בדיוק מה שעשינו עם טבלת jobs, רק שהפעם נשתמש בפרטים של טבלת names.
\n",
+ " נחבר את הטבלאות לפי העמודות principals.name_id ו־names.id:\n",
+ "
\n", + " והנה התוצאות:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "id | original_title | name_id | job_id | characters | id | name | id | name | height | date_of_birth | date_of_death | children |
---|---|---|---|---|---|---|---|---|---|---|---|---|
tt1490017 | The Lego Movie | nm0695435 | 2 | Emmet Brickowski | 2 | actor | nm0695435 | Chris Pratt | 188 | 1979-06-21 00:00:00.000000 | 2 | |
tt1490017 | The Lego Movie | nm0002071 | 2 | Lord Business,President Business,The Man Upstairs | 2 | actor | nm0002071 | Will Ferrell | 191 | 1967-07-16 00:00:00.000000 | 3 | |
tt1490017 | The Lego Movie | nm0006969 | 1 | Wyldstyle,Lucy | 1 | actress | nm0006969 | Elizabeth Banks | 164 | 1974-02-10 00:00:00.000000 | 2 | |
tt1490017 | The Lego Movie | nm0004715 | 2 | Batman,Bruce Wayne | 2 | actor | nm0004715 | Will Arnett | 189 | 1970-05-04 00:00:00.000000 | 2 | |
tt1490017 | The Lego Movie | nm0588087 | 3 | 3 | director | nm0588087 | Christopher Miller | 180 | 1975-09-23 00:00:00.000000 | 1 | ||
tt1490017 | The Lego Movie | nm0520488 | 3 | 3 | director | nm0520488 | Phil Lord | 177 | 1975-07-12 00:00:00.000000 | 0 | ||
tt1490017 | The Lego Movie | nm1087952 | 7 | 7 | writer | nm1087952 | Dan Hageman | 0 | ||||
tt1490017 | The Lego Movie | nm1156984 | 7 | 7 | writer | nm1156984 | Kevin Hageman | 1974-04-21 00:00:00.000000 | 0 | |||
tt1490017 | The Lego Movie | nm9204083 | 7 | 7 | writer | nm9204083 | Ole Kirk Christiansen | 1891-04-01 00:00:00.000000 | 1958-03-11 00:00:00.000000 | 0 | ||
tt1490017 | The Lego Movie | nm9204084 | 7 | 7 | writer | nm9204084 | Godtfred Kirk Christiansen | 1920-07-08 00:00:00.000000 | 1995-07-13 00:00:00.000000 | 0 |
\n", + " ננקה שדות לא נחוצים כדי לקבל בדיוק את מה שרצינו, בלי נתונים שמפריעים בעין:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT m.original_title as movie_name, \n", + "\t n.name as principal_name,\n", + "\t j.name as job,\n", + "\t p.\"characters\"\n", + "FROM movies AS m\n", + " INNER JOIN principals AS p\n", + " ON m.id = p.movie_id\n", + " INNER JOIN jobs AS j\n", + " ON p.job_id = j.id\n", + " INNER JOIN names AS n\n", + " ON p.name_id = n.id\n", + "WHERE m.original_title = 'The Lego Movie';\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " והנה התוצאות במלוא תפארתן:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "movie_name | principal_name | job | characters |
---|---|---|---|
The Lego Movie | Chris Pratt | actor | Emmet Brickowski |
The Lego Movie | Will Ferrell | actor | Lord Business,President Business,The Man Upstairs |
The Lego Movie | Elizabeth Banks | actress | Wyldstyle,Lucy |
The Lego Movie | Will Arnett | actor | Batman,Bruce Wayne |
The Lego Movie | Christopher Miller | director | |
The Lego Movie | Phil Lord | director | |
The Lego Movie | Dan Hageman | writer | |
The Lego Movie | Kevin Hageman | writer | |
The Lego Movie | Ole Kirk Christiansen | writer | |
The Lego Movie | Godtfred Kirk Christiansen | writer |
\n",
+ " כיוון ש־INNER JOIN
היא צורת JOIN
כה שימושית,
\n",
+ " ברירת המחדל במסדי נתונים היא שאם כותבים פשוט JOIN
הכוונה היא ל־INNER JOIN
.
\n",
+ " לכן, אפשר להשמיט את המילה INNER
מהשאילתה האחרונה, ולקבל שאילתה ששקולה לה:\n",
+ "
\n",
+ " בדומה למילת המפתח AS
, רבים מחליטים דווקא להשאיר את המילה INNER
לשם הַקְּרִיאוּת של השיאלתה.
\n",
+ "
\n",
+ " הציגו את 3 המדינות שבהן הופקו הכי הרבה סרטים מאז שנת 2000.
\n",
+ " לצד כל מדינה, הציגו כמה סרטים הופקו בה.\n",
+ "
\n",
+ " כפי שלמדנו בתחילת ההדגמה על INNER JOIN
,
\n",
+ " בתוצאות מוצגות רק שורות שהתנאי שכתוב ב־ON
מתקיים עבורן.\n",
+ "
\n", + " כלומר, בשאילתה:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT *\n", + "FROM צורות\n", + " INNER JOIN בניינים\n", + " ON בניינים.צורה = צורות.שם;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + " התוצאות יכללו רק ישויות שהערך שלהם נמצא גם בערכי הטבלה השמאלית, וגם בערכי הטבלה הימנית.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " שימו לב איך בתמונה הבאה הפנטגון לא נכנס לתוצאות.
\n",
+ " הוא נמצא בטבלה הימנית, אך הערך בעמודה שעליה עשינו ON
(\"מחומש\") לא נמצא בערכי הטבלה השמאלית.\n",
+ "
\n",
+ " באותה מידה, מעוין ועיגול, שהם ערכים מהטבלה השמאלית, לא נכנסו לתוצאות.
\n",
+ " זה המצב כיוון שאין לישויות הללו ערך תואם בטבלה הימנית.\n",
+ "
INNER JOIN
אנחנו מוותרים על כל הישויות שלא מקיימות את השוויון שכתוב ב־ON
.\n",
+ " \n",
+ " לפעמים נוצרים מצבים שבהם אנחנו רוצים לבצע JOIN
בין טבלאות,
\n",
+ " אבל לא להחסיר מהתוצאות ישות שקיימת בטבלה א אם אין לה ישות תואמת בטבלה ב.\n",
+ "
\n",
+ " נניח שחשבנו על העניין לעומק, והגענו למסקנה שאנחנו מספיק כשרוניים כדי להחליט לבד כמה צלעות יש במחומש.
\n",
+ " כלומר, זה יהיה נחמד מאוד אם המידע מטבלת צורות יוצג לנו,
\n",
+ " אבל אם המידע בטבלת צורות חסר – נסתפק במידע מטבלת בניינים ללא המידע בטבלת צורות.
\n",
+ " במילים אחרות: גם אם חסר מידע משלים בטבלת צורות, נרצה בכל זאת לראות את העמודות מטבלת בניינים.\n",
+ "
\n",
+ " במקרה כזה, אנחנו רוצים לעשות JOIN
בין טבלת בניינים לבין טבלת צורות,
\n",
+ " אבל לקחת את כל הישויות מטבלת בניינים, אפילו אם לבניין מסוים אין ישות תואמת בטבלת צורות.
\n",
+ " זה ייראה כך:\n",
+ "
\n", + " סדר הפעולות התיאורטי יראה כך:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "CROSS JOIN
בין הטבלאות.ON
.NULL
.\n",
+ " הרעיון של צירוף טבלאות בהן כל הישויות מטבלה אחת נמצאות בהכרח בתוצאות, נקרא OUTER JOIN
.
\n",
+ " כשאנחנו רוצים שהטבלה השמאלית תהיה זו שממנה ילקחו הישויות בהכרח, נשתמש בפסוקית LEFT OUTER JOIN
.\n",
+ "
\n",
+ " נדגים את התחביר של שאילתת LEFT OUTER JOIN
:\n",
+ "
\n",
+ " שימו לב שהטבלה שמופיעה ראשונה בשאילתה נחשבת ל\"טבלה שמאלית\" – זו שהישויות בה תמיד יופיעו בתוצאות.
\n",
+ " לכן, בניגוד לשאילתות שביצענו בעבר, שינינו את פסוקית ה־FROM
כך שתשלוף מטבלת בניינים במקום מטבלת צורות.
\n",
+ " למרות שבמבט ראשון ההבדל עלול להיראות סמנטי בלבד, בפועל השינוי הזה חשוב מאוד.\n",
+ "
\n", + " לפי מסד הנתונים שלנו, בשנת 2016 יצא הסרט בעל השם הקליט \"Bugs Bunny: Looney Tunes Cartoons 1942-1943 - Golden-Era Collection\".\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " למרבה הצער, בצעד שמזכיר את חטיף הקוקוס המפוקפק במכולת השכונתית, אף אחד לא נטל אחריות על הסרט.
\n",
+ " בטבלה principals לא מופיע אף לא אדם אחד שמקושר לסרט הזה.\n",
+ "
\n", + " כלומר, השאילתה הבאה מחזירה לנו 0 שורות, למרות שהסרט שריר וקיים!\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT *\n", + "FROM movies AS m\n", + " INNER JOIN principals AS p\n", + " ON p.movie_id = m.id\n", + "WHERE m.original_title = 'Bugs Bunny: Looney Tunes Cartoons 1942-1943 - Golden-Era Collection';\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " נסו לפתור את הבעיה בעזרת OUTER JOIN
.
\n",
+ " כתבו שאילתה שתציג את כל הסרטים, גם כאלו שלא היו בהם בעלי תפקידים.\n",
+ "
\n",
+ " חשוב!
\n",
+ " פתרו לפני שתמשיכו!\n",
+ "
\n", + " נכתוב שאילתה שבאיחוד הסרטים ובעלי התפקידים, תחזיר לנו גם סרטים בהם לא היו בעלי תפקידים:\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```sql\n", + "SELECT *\n", + "FROM movies AS m\n", + " LEFT OUTER JOIN principals AS p\n", + " ON p.movie_id = m.id;\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " אם נוסיף את ה־WHERE
, נראה שהפעם דווקא כן מופיע בתוצאות אותו סרט עלום מ־2016.\n",
+ "
OUTER
עבור LEFT JOIN
.\n",
+ " \n", + " במחברת זו למדנו כמה דרכים לצרף טבלאות אחת לשנייה.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n",
+ " הצורה הראשונה שלמדנו הייתה CROSS JOIN
, שמחזירה מכפלה קרטזית בין הטבלאות.
\n",
+ " כלומר – עבור כל ישות בטבלה השמאלית יוצמדו כל הישויות בטבלה הימנית.
\n",
+ " אם הטבלה השמאלית מכילה $n$ ישויות והטבלה הימנית מכילה $m$ ישויות, בתוצאות יופיעו $n \\cdot m$ ישויות.\n",
+ "
\n",
+ " הצורה השנייה שלמדנו הייתה INNER JOIN
, שמטרתה להחזיר ישויות תואמות בין שתי טבלאות.
\n",
+ " כלומר – אחרי ביצוע מכפלה קרטזית בין שתי הטבלאות, מוחזרות רק השורות שעונות על התנאי שנמצא אחרי מילת המפתח ON
.\n",
+ "
\n",
+ " הצורה האחרונה שלמדנו הייתה OUTER JOIN
, שמטרתה להחזיר את כל הישויות מטבלה מסוימת, ולצרף לכל אחת מהן מידע מטבלה נוספת – אם יש כזה.
\n",
+ " אפשר לדמיין את התהליך כך: אחרי ביצוע INNER JOIN
בין שתי הטבלאות, \"דוחפים\" שוב לתוצאות את הישויות מהטבלה השמאלית שלא מופיעות בתוצאות.
\n",
+ " בעמודות בהן חסרים נתונים בעקבות החזרת הישויות מהטבלה השמאלית מציבים NULL
.\n",
+ "
\n",
+ " כתבו שאילתה שמציגה את הסרט המפורסם ביותר בו שיחק שחקן מסוים.
\n",
+ " הניחו שהפופולריות של סרט נקבעת לפי כמות ההצבעות שקיבל.\n",
+ "
\n",
+ " הציגו את 4 השחקנים שכִכְּבו בכמות הסרטים הגדולה ביותר.
\n",
+ " בכמה סרטים כיכב כל אחד מהם?\n",
+ "
\n",
+ " חשבו על שאלה שמעניינת אתכם, ושניתן לענות עליה בעזרת שאילתות JOIN
על הנתונים שבמסד הנתונים.
\n",
+ " צרו שאילתה שתעזור לכם לענות על השאלה הזו.\n",
+ "
SELECT *
FROM names
WHERE date_of_death IS NULL
ORDER BY children
LIMIT 5;
SELECT "year", COUNT(*) AS movie_count
FROM movies
WHERE "year" >= 1927
GROUP BY "year"
ORDER BY movie_count DESC
LIMIT 5;
SELECT "year", COUNT(*) AS good_movies_count
FROM movies
WHERE avg_vote > 8.0
GROUP BY "year"
HAVING COUNT(*) >= 10
ORDER BY good_movies_count DESC
LIMIT 5;
SELECT *
FROM names
WHERE height >= 200
LIMIT 10;