{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Comprehensions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## הקדמה"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"
\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 }