top of page

תוצאות החיפוש

66 items found for ""

  • מודולים – ספריות תוכניות מדף מן המוכן

    מודולים – ספריות תוכניות מדף מן המוכן בואו נחבר כמה עובדות יחד, ונראה מה המסקנה, המתקבלת מהן. פייתון היא תוכנה בקוד פתוח, חינמית, כולם יכולים להשתמש, כולם יכולים לכתוב עבורה ספריות (מודולים), והיא אף מגיעה כבר עם ההתקנה, כסטנדרט, עם רשימה מכובדת של מודולים שימושיים. מודולים דומים למחלקות שנכתבו מראש, וניתן לייבא אותם לתוכנית שלנו ולעשות בהן שימוש, בלי לכתוב יותר מידי קוד. זו עוצמה אדירה שיש לפייתון כקוד פתוח שכולם יכולים לכתוב עבורו ספריות שכאלה בתחומים מתחומים שונים. נגיד שלצרכים מסוימים אנו צריכים לדעת את הערך של p המפורסם משיעור מתמטיקה כדי לחשב שטח מעגל או את היקפו. אפשרות אחת היא להסתפק ב – 3.14 אבל יש להניח שלצרכים רבים זה לא יספיק, כמובן שידוע שזה מספר אי רציונאלי שאין גבול ידוע למספר הספרות שיש לו אחרי הנקודה. לכן צריך להחליט כמה דיוק אנו צריכים. אחד המודולים נקרא math והקבוע p מופיע בו, בילת אין, עם יותר מעשר ספרות אחרי הנקודה. אם כך , נראה איך משתמשים במודול math - import math def circleArea(r): """ takes a radios and returns the area of a circle """ return (r**2)*math.pi print(circleArea(10)) >>> 314.1592653589793 לפני הכל, כדאי לשים לב לשורת התיעוד הנכתבת בין שלושה גרשיים מלמעלה ומלמטה, שהתוכנה לא קוראת אותם והיא מיועדת רק לתת הסברים על הקוד. ניתן לקבל את התיעוד הזה באמצעות (help(circleArea כמובן לבקש ((print(help(circleArea אם רוצים גם לראות משהו. המילה המשמשת ליבוא מודולים היא import ואחריה רושמים את שם המודול, במקרה שלנו math, וזהו, כעת כל מה שצריך לעשות כדי להשתמש במתודות שיש במודול הוא לדעת על קיומן, לרשום את המילה math, אחריה נקודה, ואחריה שם המתודה, במקרה שלנו pi ויש לנו את זה. אפשר להשתמש ב pi עם רמת דיוק של שלוש עשרה ספרות אחרי הנקודה. כך גם לגבי הקבוע המתמטי e (שערכו בערך 2.71828). פעולת השורש (math.sqrt(x תחזיר את השורש של x שיוזן לתוך הפונקציה. ויש שם המון, cos, sin, log (באיזה בסיס שאנחנו חולמים), לעגל מספרים, להפוך רדיאנים למעלות (טריגו זוכרים?) ולהיפך ועוד. כיף גדול. כדי להקטין את העומס על התוכנית שלנו, אנו יכולים ליבא רק חלק מהספריה ולא את כולה, למה אני צריך בקוד כל כך הרבה כשאני צריך רק לחשב דברים עם pi – נראה איך עושים זאת – from math import pi def circleArea(r): """ takes a radios and returns the area of a circle """ return (r**2)*pi print(circleArea(10)) >>> 314.1592653589793 אנו רואים תחביר (syntax) מעט אחר שמתחיל במילה from ולאחר מכן שם הספריה ולאחר מכן import ולאחר מכן שם המתודה הספציפית שאנו רוצים לייבא. במקרה שלנו רק pi. אבל צריך לשים לב שבמקרה כזה, כאשר אנו רוצים להשתמש באותה מתודה, אנו לא צריכים את שם הספריה והנקודה לפני (גם אי אפשר), מספיק שרושמים pi וכשנכפול אותו בריבוע הרדיוס נקבל את שטח המעגל המשוייך לאותו רדיוס r שמופיע בפונקציה. לעיתים נחמד למתכנתים להשתמש בכתיב המקוצר שבספריה בלי שם הספריה, והם מוכנים בשל כך לייבא את כל המתודות שבספריה, באמצעות הביטוי *from math import למשל עבור המודול math (הכוכבית * זה כל המתודות כולן), ברם, זה דבר איום ונורא ומכביד על הקוד ועושה בהמשך עוד דברים רעים, מתודות שיכולות להתערבב כאשר יש הרבה מודולים וחלקם עם מתודות בעלות אותו שם, בשלבים מתקדמים, לא כדאי. ספריה נוספת בתחום המתמטיקה והסטטיסטיקה שאי אפשר לשרוד בלעדיה היא numPy חביבת הסטודנטים בתחום של data science. אם נצטרך בהמשך אספר אודותיה. לא כל הספריות נמצאות כסטנדרט בפייתון ולעיתים צריך להוריד ספריה מהרשת באופן מיוחד. מי שיגיע לרמות האלה ילמד על כל הנושא שאינו מסובך כלל, מהרשת. כולל הביטוי המגניב pip install. יש מעט פעמים שאנו צריכים ללבוש את סרבל הטכנאי ולמצוא היכן המחשב שלנו מניח כל דבר ואיך ניגשים ואיך מתקינים. שום דבר שלא עובר אחרי הרבה סרטונים ברשת וסבלנות. ספריית collections המודול/ספריה collections משך את תשומת ליבי בשל המגניבות (והשימושיות) של חלק מהמתודות שהוא כולל. אחד מהם, מאפשר לקבל מעין tuple (מעין מחלקה מהירה וקלה) יותר מגניב ויותר קריא, כאשר כל אחד מהפריטים ב- tuple מקבל שם המתאר אותו, וניתן לגשת אליו לא רק באמצעות אינדקס מספרי אלא באמצעות השם המתאר אותו. כמו כן ניתן לשכפל את הייצור בדומה למה שניתן לעשות עם class (זה למעשה מחלקה שמתנהגת כמו tuple delux) רק פשוט יותר ובלי הרבה קוד. למתודה קוראים ()namedtuple ונדגים את דרך פעולתה לפני שנמשיך להסביר - import collections Person = collections.namedtuple('Person','name age gender height') dan=Person("Dan",25,"male",182) batia=Person("Batia",45,"female",176) print(batia) יצרנו מעין מחלקה הקרויה Person זה יהיה מפעל ה- tuple היוקרתי שלנו, לאחר שאנו כותבים את הפקודה collections.namedtuple אנו מתחילים לתת שמות לנתונים שנזין בעתיד, לפני כן, אנו נותנים שם ל"מחלקה" שלנו ואת זאת עושים בתוך מחרוזת 'Person' מוסיפים פסיק (,) ולאחר מכן מחרוזת אחת הכוללת את שמות כל הפרמטרים שנרצה להזין, בסדר שאנו רוצים אותם, כשהם מופרדים ברווח בלבד ! זהו ! מכאן אנו עוברים לייצר מופעים של המחלקה Person ויש לנו שניים: dan ו-batia , ובתוך הסוגריים מזינים לפי הסדר שהופיע במחרוזת שלנו, את הפרמטרים השונים. כאשר נבקש להדפיס את batia, נקבל ייצוג מאוד יפה, קריא ומסודר: התוצאה Person(name='Batia', age=45, gender='female', height=176) אנו מיד יודעים שהיא Person ולא למשל חתול, וש- 176 מתייחס לגובה ולא למשקל, ו- 45 מתייחס לגיל. אבל זה לא הכל – זה גם מתנהג כמו tuple רגיל ואפשר לבקש להדפיס את [dan[3 ולקבל 182, ואפשר להדפיס רק את batia.gender (כמו מתודה במחלקה) ונקבל למי שהיה לו ספק: female. אני לא יכול להתאפק – רק עוד מתודה אחת ב- collections המתודה deque שמאפשרת לנו במקום להסתפק ברשימה (list) רגילה, ברשימה משודרגת, שאפשר להוסיף בקלות איברים משני צדדיה (נזכיר ש- append מוסיף לנו ברשימה איבר בצד ימין בלבד וזה מעצבן), נראה איך זה עובד – import collections deq = collections.deque(["b","c","d"]) deq.appendleft("a") print(deq) >>> deque(['a', 'b', 'c', 'd']) print(list(deq)) >>> ['a', 'b', 'c', 'd'] יצרנו מעין רשימה של האותיות "b","c","d" , התחביר הוא שה- deque שיצרנו נמצא בתוך סוגריים מרובעים שבתוך סוגריים עגולים. כעת נוסיף לצד השמאלי של הרשימה את האות "a" באמצעות המתודה appendleft וזה עובד קיבלנו את האות "a" בצד שמאל, אנו גם יכולים לבקש לראות את זה כרשימה רגילה, באמצעות הפקודה ()list. באותו אופן ניתן להוציא החוצה באמצעות הפקודה ()popleft איברים מהצד השמאלי של הרשימה. נזכור שהפקודה pop (גם ברשימות) עושה שתי פעולות במחיר שורת קוד אחת – היא מחלצת איבר ושומרת אותו עבורנו, אם נרצה, בתוך משתנה לבחירתנו. וזה לא הכל, ניתן לייצר deque מסתם מחרוזת באופן מהיר (בדוגמא הבא המחרוזת "bcde" הופכת לרשימה של מחרוזות שכל אחת מהן היא אות אחת, נדגים את שני הנושאים יחד – import collections deq = collections.deque("bcde") print(deq) >>> deque(['b', 'c', 'd', 'e']) f=deq.popleft() print(f) >>> b print(deq) >>> deque(['c', 'd', 'e']) למעלה, כאמור, יצרנו רשימה של אותיות ממחרוזת בודדת "bcde". לאחר מכן בשורת פקודה אחת עשינו שתי פעולות, השמה לתוך המשתנה f של האיבר השמאלי ב-deque שלנו (האות b), אגב הוצאתו של האיבר השמאלי ביותר מה-deque שלנו. כעת אנו יכולים להדפיס את f ולראות שהוא מכיל את האות b שהוצאנו, ואפשר גם להדפיס את ה- deque ולראות שהוא חסר את האות b שהיתה ראשונה משמאל. אפשר לעשות עוד המון דברים בדומה לרשימות ויותר מזה, הדבר מדגים את הכוח העצום של ספריות והשימוש בהן, כהרחבה למה שהמפתחים של פייתון עשו במקור.

  • איטראטור - iterator

    איטראטור - iterator כדי להבין מהו איטרטור אנו צריכים להבין איזה מין אובייקט הוא iterable – טוב בפייתון יש הרבה אובייקטים המוגדרים iterables , ביניהם רשימות, טופלים, מילונים, סטים ומחרוזות המשמעות של המילה היא שניתן לעבור דרך אותו אובייקט, על כל איבריו, אחד אחד, לפי הסדר, ולעשות עם כל איבר של האובייקט משהוא בנפרד. לפעמים באמצעות הפקודה for - for i in "abcd": print(i) >>> a b c d המחרוזת "abcd" מאפשר ריצה בתוכה על כל אחד מהאיברים שלה והדפסתו למשל. האובייקטים הללו יכולים לקבל את המתודה ()iter באופן שבאמצעות הפקודה next ניתן לעבור אחד אחד – להלן ניצור איטרטור בעל השם המקורי iterator, אשר יצירתו מתאפשרת אודות לפקודה iter() שהפעלנו על הרשימה שלנו lista (שהיא כאמור iterable).לנשום לשקית נייר חומה ואז לקרוא שוב במידת הצורך. lista=[2,4,6,8,10] iterator=iter(lista) print(next(iterator)) print(next(iterator)) print(next(iterator)) print(next(iterator)) print(next(iterator)) >>> 2 4 6 8 10 האיטרטור מאפשר לנו לעבור על איברי הרשימה, אולם מה קורה כאשר סיימנו ? את האיברים ברשימה? האם הוא מתחיל מחדש ? לא. הוא מוציא הודעת שגיאה ובעצם יוצא מכלל פעולה. על זה אנו אומרים שהאיטרטור נוצל או באנגלית (consumed) . מה שעושה לולאת for עבורנו, עכשיו מותר לגלות, זה בעצם לייצר איטרטור ולהפעיל אותו על איברי האובייקט האיטרבילי (רשימה, טופל, מחרוזת וכו') שלנו. בואו נייצר איטרטור נחמד שיכול להמשיך לנצח, האיבר הראשון שהוא מדפיס הוא 1 האיבר אחריו כפול ממנו, וכך הלאה עד אין קץ (או שהזיכרון נגמר או משהו) – class Multi2: def __iter__(self): self.num = 1 return self def __next__(self): mul = self.num self.num *= 2 return mul multiclass = Multi2() doubleIterator = iter(multiclass) print(next(doubleIterator)) print(next(doubleIterator)) print(next(doubleIterator)) print(next(doubleIterator)) print(next(doubleIterator)) print(next(doubleIterator)) >>> 1 2 4 8 16 32 הסבר- בנינו מחלקה שנקראת Multi2 שיש בה שתי פונקציות/מתודות מיוחדות __iter__ ו- __next__ שיהפכו את המופע שלנו (multiclass) לאיטרטור אינסופי שיפלוט מספרים כל פעם בגודל כפול מהפעם הקודמת. זאת באמצעות השמה במשתנה בשם doubleIterator (האיטרטור שלנו) את המופע (multiclass) כשהוא עטוף בפקודת ()iter. ניתן לייצר מכל אובייקט איטרבילי כמה איטרטורים שאנו רוצים וכל אחד מהם יפעל באופן עצמאי - lista=[1,2,3,4,5] it1=iter(lista) it2=iter(lista) print(next(it1)) print(next(it1)) print(next(it2)) >>> 1 2 1 כאמור, כל אחד מהאיטרטורים רץ בנפרד עד שהוא מנוצל במלואו (consumed) ואז הוא מוציא הודעת שגיאה אם מנסים להוציא ממנו יותר ממה שיש בו. הפקודה ()range – מזכירה בהתנהגותה איטרטור, אם כי יש לה מאפיינים מעט שונים, ויש המכנים אותה איטרטור עצל (למה להעליב)- נושא לויכוח בקרב יודעי דבר ברשת.

  • גנרטור generator

    גנרטור generator גנרטור הוא סוג מסוים של איטרטור – מאפשר יצירה מהירה ונוחה של איטרטורים - (אבל לא כל איטרטור הוא גנרטור) – הדבר נעשה באמצעות הביטוי ,yield המניב בכל פעם איבר נוסף בסדרה ועושה זאת לפי דרישה בלבד (וזוכר רק את האיבר האחרון שהוא הניב ולא שומר את כל הסדרה) – הגנרטור שנראה בדוגמא הבאה יודע, לפני שהוא מנוצל ונשרף (consumed), לייצר לנו למשל את ריבועי המספרים, מנקודת התחלה שנבחר ועד לנקודת סיום שנבחר (לא כולל את נקודת הסיום). למטה אנו בונים את הפונקציה squarit שמחזירה ערכים לפי דרישה (ולא מייצרת מראש רשימה שלהם) באמצעות הפקודה yield (להניב)- def squarit(start, stop): for i in range(start, stop): yield i * i generator = squarit(1, 6) print(next(generator)) print(next(generator)) print(next(generator)) print(next(generator)) >>> 1 4 9 16 המשתנה generator מימוש של הפונקציה בתחום הערכים 1 עד 6 (לא כולל 6) ובכל פעם יניב את הערך הריבועי הבא בתור. כשיסיים את עבודתו, אם נמשיך לבקש את האיבר הבא, נקבל הודעת שגיאה. אנו יכולים לקבל את כל האיברים שמייצר הגנרטור באמצעות לולאה for x in generator ולערוך איתם חישובים או חיפושים כפי רצוננו, או סתם להדפיס את כולם. ניתן גם לייצר טיפוס מסוג גנרטור באופן דומה ליצירת list comprehension רק עם סוגריים עגולים – נראה איך זה עובד – gen=(x**2 for x in range(5)) print(type(gen)) >>> print(next(gen)) >>> 0 print(next(gen)) >>> 1 print(next(gen)) >>> 4 print(next(gen)) >>> 9 למעלה יצרנו גנרטור, כאשר אנו מבקשים להדפיס את סוג הטיפוס, אנו מקבלים את התשובה generator מה שמחזק את התחושה הפנימית שזה באמת גנרטור. הגנרטור חסכוני בזכרון, ומניב רק את האיבר שצריך כל פעם, בלי לשמור רשימות ארוכות בזכרון. זה גם מאיץ את מהירות עבודת התוכנה, ויש שלבים, אפילו אצל מתחילים, שהחישובים המרובים מתחילים להיות ארוכים בזמן, לפעמים רק בבוקר למחרת אפשר לראות אם התוכנית פתרה חידה מורכבת מסוימת. לכן, שימוש באיטרטורים בכלל וגנרטורים בפרט הוא קריטי. כדי להשוות זמני עבודה של איטרטורים אל מול list comprehension אפשר להשתמש במודול time המסוגל למדוד זמנים בחלקיקי שנייה בין תהליך אחד לאחר – נערוך השוואה כזאת, וגם נסגור את הפינה לגבי אחד השימושים הנחמדים במודל time - import time time0=time.time() lista=[x for x in range(10000000)] for i in lista: pass time1=time.time() gen=(x for x in range(10000000)) for i in gen: pass time2=time.time() print(time2-time1,time1-time0) >>> 0.799553394317627 1.0324063301086426 למעלה אנו רואים איך אפשר למדוד זמנים בין תהליך לתהליך, בכל פעם מוסיפים משתנה עם הזמן הנוכחי ()time.time ולבסוף מודדים את ההפרשים ביניהם. אנו רואים שגנרטורים מהירים ביותר מ- 20% 0.8 שניות במקום 1 שנייה שלימה לתהליך שהרצנו למעלה. אבל בממוצע, גנרטורים יהיו הרבה יותר מהירים משום שאם אנו מחפשים איבר מסוים בתוך סדרה, ובממוצע הוא ימצא במקום טוב באמצע, לגנרטור אין צורך לייצר את כל איברי הסדרה ולאחסן אותם בזיכרון ורק אז להתחיל לחפש, בעוד שברשימה, לא משנה היכן ממוקם האיבר שאנו מחפשים, פייתון מייצרת רשימה המלאה בכל האיברים לפני שהיא מתחילה לחפש, ובכך אף מעמיסה על הזיכרון מעבר לזמן ריצה של התוכנית שיכול להיות משמעותית קצר יותר בגנרטור. אם הזכרנו את המודול time, נזכיר גם את אח שלו, המודול datetime שמחזיר תאריכים ומועדים בפורמט יותר קריא, מאשר המודול time המחזיר מספר עשרוני המייצג שניות משנת 1970 באופן רציף עד היום. להלן סרטון הדרכה שלנו בנושא גנרטורים המדגים גם את יעילות הגנרטור מול רשימה:

  • טיפול בשגיאות – exeptions

    טיפול בשגיאות – exeptions עבודה עם תוכנה ללא טיפול בשגיאות משולה לניווט בסהרה, בלי מצפן, בלי מפה, בלי טלפון חכם, בלי מים, בלי אוכל, בלי רכב, נראה לי שהנקודה הובנה כבר במפה. בחיים זה נראה טריוויאלי אבל מה תוכנה צריכה לעשות כאשר אומרים לה לחסר 8 מתפוח, בואו ננסה את זה - a="apple" b=8 print(a-b) >>> TypeError: unsupported operand type(s) for -: 'str' and 'int' פייתון שולחת לנו הודעת שגיאה מסוג TypeError (ויש עוד סוגים רבים) וגם הסבר שהאופרטור, במקרה שלנו פעולת החיסור, אינה תומכת בחיסור מספר ממחרוזת. כדאי לשים לב שפעולה כפל של מחרוזת במספר מתפרשת כשרשור של המחרוזת מספר פעמים ולא זורקת הודעת שגיאה. ואיך התוכנה מגיבה אם מסנים למחוק איבר מטופל ? tupla=(1,2,3,4) del tupla[1] >>> TypeError: 'tuple' object doesn't support item deletion שוב TypeError לא ניתן למחוק איברים מטופל. עכשיו בואו נעשה מה שתמיד חלמנו – נחלק באפס - תאכלי את זה המורה שרה! x=3/0 print(x) >>> ZeroDivisionError: division by zero נו. יש לו שגיאה משל עצמו. למעשה הטקסט המלא של השגיאה הולך ככה בצנזור קל של שמות הקבצים בממחשב שלי – Traceback (most recent call last): File "C:…..", line 1, in x=3/0 ZeroDivisionError: division by zero כלומר, פייתון אומרת לנו באיזו שורת קוד התרחשה השגיאה, מה סוג השגיאה, והטקסט המסביר את השגיאה הספציפית. עכשיו נדמיין שאנו צריכים לדעת את הגיל של מישהו על מנת לבדוק האם הוא יכול לעשות פעולה מסוימת התלויה בגיל, אנו מבקשים ממנו שיקליד את גילו והוא מקליד בטעות את שמו, מה קורה עם התוכנית שלנו ? כיצד נתקן את השגיאה ? x=int(input("what is your age?")) if x>18: print("Enjoy") if x<=18: print("you are too young") >>> what is your age? הבחור מקליד Shalom >>> ValueError: invalid literal for int() with base 10: 'Shalom' אנו מקבלים הודעה מסוג ValueError והתוכנה מפסיקה לעבוד – הבה נראה את הדרך של פייתון להתמודד עם המשבר – try except while True: try: x=int(input("what is your age?")) except ValueError: print("please enter a number") continue if x>18: print("Enjoy") break if x<=18: print("you are too young") break what is your age? שלום מקליד shalom >>> please enter a number what is your age? שלום הבין את הטעות ומקליד 21 >>> Enjoy ראשית בנינו לולאת while כדי שתחזיר אותנו להתחלה במידה והבחור מתעקש שהגיל שלו צריך להיות באותיות, הלולאה הזו ממשיכה לנצח עד שהבחור מכניס מספר, ואז התוכנה מדפיסה את מה שהיא צריכה להדפיס ושוברת את הלולאה באמצעות פקודת break. מה שמעניין יותר לעניינינו הוא המבנה של מנגנון לכידת הטעויות. במקרה הזה אנו צופים שהמתחכם עלול לעשות טעות מסוג ValueError לכן אנו בעצם אומרים לתוכנה לנסות (try) לקבל ממנו את התשובה הרצויה (מספר) ובמידה שלא קיבלה ויש לנו ValueError , ולמעשה ה- except שלנו מתממש, אנו מבקשים להדפיס הנחיה מתקנת למסך ולחזור לתחילת הלולאה באמצעות הפקודה continue . חשוב לזכור – אנו לא מוגבלים ל- except אחד ולמעשה אנו יכולים אחרי ה- try להציב שורה של exceptions כל אחת מתייחסת לסוג אחר של טעות עם הנחיות מתאימות. יותר מזה, אנו יכולים לנסח הודעות שגיאה משל עצמנו. בואו נניח שבארץ הקדושה שלנו אסור להשתמש במספר 6 בשל קדושתו המיוחדת. הבה נבנה class של טעות בשם ErrorNumberSix וכמו כל הודעות השגיאה בפייתון גם הוא ירש תכונות מספריית בסיס המצויה בפייתון ונקראת Exception כך שהוא יפעל כמו הודעות שגיאה מובנות של פייתון – הפקודה לזריקת הודעת שגיאה היא raise בצירוף שם השגיאה – נראה איך זה עובד – class ErrorNumberSix(Exception): pass x=int(input("choose your number in life")) if x==6: raise ErrorNumberSix("you can't use the holy number 6") >>> choose your number in life ברגע שהיא מקלידה את המספר 6 מתקבלת הודעת השגיאה (אחרי מיקום השגיאה ובלה בלה)– ErrorNumberSix: you can't use the holy number 6 כמה דברים שאת צריכה לדעת – את התוכן הספציפי של הודעת השגיאה אנו יכולים לבחור מיד לאחר שאנו מבקשים לזרוק הודעת שגיאה באמצעות הפקודה raise, בתוך הסוגריים שאחרי שם השגיאה. שימי לב שאנו משתמשים בירושה וספריית הבסיס Exception מוכנסת כפרמטר במחלקה החדשה שבנינו לשגיאה שלנו. שנית למה התוכנה משתמשת במספר 6 אם הוא קדוש ? אולי היתה מסתפקת ב- holy number J למה המחבר עבר לדבר בלשון נקבה? עזבי את זה. השימוש בהודעת שגיאה באופן הזה, כשאנו יורשים את התכונות של ספריית הבסיס Exception, מאפשר למשל להודעה לפרט באיזו שורה התרחשה השגיאה. finally כפי שאמרנו, אפשר להכניס שורת try מתחתיה הקוד שאנו רוצים לבדוק. לאחר מכן except אחד או יותר ולבסוף, בין אם נלכד exception ובין אם לאו, אני יכולים לבקש, להריץ שורת קוד, שתרוץ בכל מקרה, תחת הביטוי finally – נראה דוגמא - while True: try: x=int("abc") except ValueError: print("please enter a number") continue except SyntaxError: print("yalla gam ata") continue except TypeError: print("try again") finally: print("this line will always be here") break >>> please enter a number this line willalways be here יש לנו את הפקודה try שלתוכה אנו מכניסים את מה שאנחנו רוצים לבחון – במקרה למעלה אנו מנסים להפוך למספר את המחרוזת וזו שגיאה מסוג ValueError, לאחר מכן יש שרשרת של except תחת כל אחד הוראה מה לעשות אם השגיאה הנוצרת היא מהסוג שהזכרנו. לבסוף, תחת finally ישנה הוראה שתמיד יוצאת לפועל, הראיה לכך היא שיש בסופה פקודת break ששוברת את הלולאה while, שאם לא היתה יוצאת לפועל בכל מקרה, היינו ממשיכים לקבל בלי סוף את השורה please enter a number.

  • פרויקט- אבני דומינו

    פרויקט- אבני דומינו הגיע הזמן לעשות חזרה, במסגרת מיני פרויקט, המיישם חלק גדול מהחומר שנלמד כאן. וגם להבין את השלבים של התהליך המחשבתי בזמן בניית תוכנית מורכבת. כבר אמרנו שתוכנית היא מודל של המציאות ובפרויקט הזה אנו רוצים לערוך מעין מודל ראשוני של משחק הדומינו הידוע. משחק הדומינו כולל 28 אבנים, לכל אבן שני צדדים, לכל צד יש שבע אפשרויות – הוא יכול להיות חלק או בעל 1-6 נקודות. הדבר הראשון שנרצה לעשות הוא לייצר מודל של אבני דומינו. אנו יודעים שכל אבן כוללת שני מספרים, שכל אחד מהם יכול להיות בין 0 ל-6, לכן כל מופע (שזה בעצם אבן דומינו שנבנה) יכלול את שני המספרים האלה. בתוכנית למטה המספרים מיוצגים על ידי x ו- y - class Domino: def __init__(self,x,y): self.domino=[x,y] def __repr__(self): return f"{self.domino}" החלטתי לייצור אבני דומינו באמצעות מחלקה שקראתי לה Domino (מחלקות אני מזכיר מתחילות על פי המוסכמה באות גדולה) הכוללת כרגע שתי פונקציות מיוחדות (special methods) הראשונה היא __init__ המגדירה את המופע שלנו (והיא הפונקציה/מתודה הראשונה שהמחשב קורא לה כאשר יוצרים מופע חדש באמצעות המחלקה), בהמשך, ייצוג אבן הדומינו יהיה באמצעות רשימה המכילה את שני המספרים. למשל משהוא כזה [0,0] ישמש כדי לתאר אבן עם שני צדדים חלקים. על מנת שאוכל להדפיס משהו עם משמעות, כאשר אבקש להדפיס את המופע, הגדרתי במחלקה גם פונקציה מיוחדת נוספת בשם __repr__ שתדפיס לי מחרוזת שאני יכול להבין שנראית כמו [3,2] . עכשיו ננסה לייצר דומינו ונדגים את תהליך הניסוי והטעיה, תחיל מהקוד ואחר כך ההסבר - tooManyDominos=[[x,y] for x in range(7) for y in range(7)] dominos=[] for item in tooManyDominos: if sorted(item) not in dominos: dominos.append(item) allDominos=[Domino(i[0],i[1]) for i in dominos] בהתחלה ניסיתי ליצור, בטכניקה של list comprehension, אבן דומינו מהסוג [x,y] באמצעות ה"איטרטור העצל" range שבעצם עובר על המספרים מאפס עד שש ולכל תחנה כזאת הוא עובר שוב מאפס עד שש עבור הצד השני של האבן, כך שיש לנו אפס עם כל המספרים, אחד עם כל המספרים וכו'. אבל מיד התברר לי שהתהליך מייצר יותר מידי אבני דומינו, 49 במקום 28, הוא מייצר גם את [6,1] וגם את [1,6] מה שמופיע רק פעם אחת בלבד בדומינו. לכן הרשימה הזאת קיבלה בדיעבד את השם tooManyDominos. אז איך מצמצמים את הרשימה? יצרתי רשימה ריקה בשם dominos ובאמצעות לולאת for עברתי על כל פרטי הרשימה tooManyDominos וביקשתי לקחת כל אבן ולעשות עליה את הפעולה ()sorted שבעצם מסדרת את האבן מהמספר הקטן לגדול כך שלא יהיה יותר [6,2] אלא רק [2,6]. אבל זה לא מספיק, כי עכשיו צפויות להיות לי שתי אבנים שנראות כמו [2,6] ולכן ביקשתי מפייתון, שאם האבן (בתכנית למעלה נקראת item) כבר נמצאת ברשימה dominos שלא תכנס פעם שנייה. טוב, עכשיו יש לי רשימה שנקראת dominos הכוללת בדיוק 28 רשימות קטנות (אבני הדומינו) שכל אחת מהן נראה כך [2,5] בשינוי של מספר או שניהם. יאללה, עכשיו משתמשים במחלקה Domino שבנינו מבעוד מועד, וגם ב- list comprehension כדי לייצר סט מושלם של אבני דומינו, שהם אובייקטים מסוג class, שיאופסנו בתוך רשימה שנקראת allDominos. היות שאתx ו- y הדרושים למימוש מופע (instance) של המחלקה אני לוקח מתוך הרשימה dominosהכוללת מיני רשימות שכל אחת מהן מכילה שני מספרים, אני צריך לפרק כל מיני רשימה כזאת לשני מרכיביה (כי על מנת לייצר מופע אני צריך מספר ולא רשימה שלימה) [i[0 זה המספר השמאלי ו- [i[1 הוא המספר הימיני, וכך עוברים אבן אבן ברשימה dominos והופכים אותה למופע (אובייקט של אבן דומינו) במחלקה Domino. השלב הבא – לערבב את האבנים באופן רנדומלי Import random ... ... random.Random(4).shuffle(allDominos) כאן אני מדגים שימוש במודול (ספריה) שנקראת random, אותה מייבאים לתוכנית באמצעות פקודת import, והיא כוללת את הפונקציה השימושית random.suffle(x) שלוקחת אובייקט כמו רשימה ומחזירה אותו מעורבב. מגניב. אבל הדבר יצר לי בעיה, כי כל פעם שהתחלתי לשחק עם האבנים, מצאתי את עצמי עם ערבוב חדש. אני צריך ערבוב פעם אחת ושלא ישגע אותי ויערבב כל פעם שאני מניח אבן על השולחן. יש לזה כמה פתרונות, אני בחרתי בפתרון שיש במודל random והוא נותן ערבוב קבוע לבחירתך וזה נראה כמו בתוכנית למעלה. עכשיו allDominos מעורבבת. השלב הבא – לחלק לכל שחקן שבע אבני דומינו המשתנה myHand יהפוך עד מהרה לרשימה עם שבע אבנים, באמצעות מתודה נחמדה שנלמדה קודם לכן ומבוצעת גם עם רשימות, היא נקראת ()pop והיא עושה שתי פעולות, גם מוציאה אבן מהרשימה allDominos וגם מעבירה אותה ליד שלי myHand. בשלב הזה ברור לנו שבסופו של דבר אחרי שנחלק אבנים לכל השחקנים, allDominos תהיה הקופה, ממנה מושכים אבן כשלמישהו נגמרות האפשרויות, (אלא אם יש ארבעה שחקנים ואז אין קופה). myHand=[allDominos.pop() for i in range(7)] computerHand=[allDominos.pop() for n in range(7)] התפקיד של i in range (7) הוא פשוט, לספור כמה פעמים תתבצע המתודה pop() – שבע פעמים (זו למעשה ספירה מאפס עד 6 כולל 6 ולא כולל 7- סה"כ 7 פעמים) השלב הבא- לייצר שולחן עליו מניחים את אבני הדומינו – לשני השחקנים שלנו יש 7 אבנים שונות, עכשיו אנו רוצים ייצוג לשולחן דומינו, כלומר, רשימה, שבתוכה נסדר את האבנים. היות שלפעמים רוצים להניח אבן בצד הימיני של הרשימה ולפעמים בצד השמאלי שלה, נוח יותר להשתמש במתודה מתוך ספריית collections שנקראת: ()collections.deque והיא נותנת לנו מעין רשימה משוכללת שאפשר לצרף אליה בקלות איברים משני הצדדים. לשולחן שלנו אנו קוראים dominoTable וזה נראה בתוכנית כך - import collections dominoTable =collections.deque() השלב הבא – לאפשר לשחקן להוציא אבן דומינו מהיד שלו ולהניח על השולחן בצד הנכון בשביל זה אנו חוזרים למחלקה שלנו Domino ומוסיפים לה פונקציונאליות play ומקשרים את הפעולות למשתנים מחוץ למחלקה באמצעות הביטוי global (כך שגם השולחן, גם היד של המחשב וגם היד שלי מושפעים כאשר אני מוציא אבן מהיד של שחקן כלשהו). לשם כך בנינו את המתודה play הכוללת ארבעה מצבים שמחייבים הסבר – נניח שאחת מאבני הדומינו ביד שלי נראית כך – [1,2] ונניח שי אבן אחת על השולחן שנראית כך – [6,2] (זה אפשרי למרות שבהתחלה עשינו sorted והיא אמורה להיות מהמספר הקטן בשמאל לגדול בימין), זה מעולה כי יש לי את המספר 2 אבל אם אני אניח את האבן בצד ימין בלי לסובב אותה, השולחן יראה כך [6,2],[1,2] שזה לא טוב, כל הרעיון של אבן דומינו הוא שאפשר לסובב אותה. מכאן חילקתי את האפשרויות שעומדות בפני השחקן לארבעה מצבים כלהלן – 1 – מניחים אבן בצד ימין בלי לסובב אותה (פקודת append פשוטה, מצרפת איבר לצד ימין של הרשימה או ה- deque במקרה שלנו) 2- מניחים אבן בצד ימין עם לסובב (מצרפים רשימה הפוכה - [self.domino[1], self.domino[0] היא הפוכה כי 0 ברשימה הוא השמאלי ביותר וכאן אנו מבקשים ש-1 יהיה בשמאל. כלומר במקום [1,2] נצרף [2,1] וזה מדמה מצב של סיבוב אבן הדומינו לפני שמניחים אותה. 3- מניחים בצד שמאל בלי לסובב - דבר שמתאפשר ב deque באמצעות appendleft. 4- מניחים בצד שמאל עם לסובב. class Domino: def __init__(self,x,y): global dominoTable ,myHand,computerHand self.domino=[x,y] def __repr__(self): return f"{self.domino}" def play(self,x): if x==1: dominoTable.append(self.domino) if x==2: dominoTable.append([self.domino[1],self.domino[0]]) if x==3: dominoTable.appendleft(self.domino) if x==4: dominoTable.appendleft([self.domino[1], self.domino[0]]) if self in myHand: myHand.remove(self) if self in computerHand: computerHand.remove(self) דאגנו שהמתודה play מגיעה עם פרמטר מספרי 1-4 שיקבע לנו איזה אפשרות תבחר. אם למשל נבחר (play(3 האבן תונח בצד השמאלי בלי סיבוב. כמובן שאחרי משחק צריך להוסיף את האבן לשולחן ולגרוע אותה מהיד של מי ששיחק הרגע. לשם כך השתמשנו במתודה remove במקרה הזה של self האובייקט (אבן הדומינו שלנו) מתוך אחת הידיים שהוא היה בה קודם לכן. לשים לב שבפקודה remove אם מנסים להסיר משהו שאינו קיים ברשימה, מקבלים הודעת שגיאה. השלב הבא – לבדוק מה קורה לשולחן בזמן שמשחקים כך נראים מהלכי המשחק – המספר השמאלי, למשל ב- [myHand[1, מייצג את הבחירה שלנו באבן הדומינו מתוך רשימת האבנים שביד שלנו – 0 היא האבן השמאלית, 1 אחריה וכך הלאה כמו ב- list כי myHand היא באמת list. והמספר שאחרי המתודה play בסוגריים עגולים, (play(4 למשל, מייצג באיזה אופן אנו רוצים לשחק עם האבן, לפי ארבעת המצבים, שמאל ימין עם או בלי סיבוב. myHand[1].play(1) computerHand[1].play(1) myHand[0].play(3) computerHand[5].play(1) myHand[2].play(4) computerHand[2].play(3) print("my hand:" ,myHand) print("dominoTable:",list(dominoTable)) print("computerHand:",computerHand) כמובן שניתן לערוך את הדברים בקלות באופן אוטומטי, כלומר, לבחור רק אבן מתוך היד שלי וצד בשולחן, והתוכנית תבצע במידת הצורך סיבוב של האבן. איך עושים ? כותבים תנאי פשוט שלגבי הצד שבחרנו בשולחן, אם המספר האחרון בקצה שווה למספר באבן שלנו כאשר מניחים אותה בלי סיבוב, אזי לאפשר צירוף בלי סיבוב, ואם זה שווה למספר בצד השני של האבן, לבצע סיבוב ואז לצרף. לא מסובך, אבל לצורך הלימוד מאריך מידי את הדברים. לפני שנראה איך הדברים נראים אחרי כל המהלכים שעשינו, כדאי לראות איך נראה כל הקוד (עד לשלב הזה) ביחד – import random import collections dominoTable =collections.deque() class Domino: def __init__(self,x,y): global dominoTable ,myHand,computerHand self.domino=[x,y] def __repr__(self): return f"{self.domino}" def play(self,x): if x==1: dominoTable.append(self.domino) if x==2: dominoTable.append([self.domino[1],self.domino[0]]) if x==3: dominoTable.appendleft(self.domino) if x==4: dominoTable.appendleft([self.domino[1], self.domino[0]]) if self in myHand: myHand.remove(self) if self in computerHand: computerHand.remove(self) tooManyDominos=[[x,y] for x in range(7) for y in range(7)] dominos=[] for item in tooManyDominos: if sorted(item) not in dominos: dominos.append(item) allDominos=[Domino(i[0],i[1]) for i in dominos] random.Random(4).shuffle(allDominos) myHand=[allDominos.pop() for i in range(7)] computerHand=[allDominos.pop() for n in range(7)] myHand[1].play(1) computerHand[1].play(1) myHand[0].play(3) computerHand[5].play(1) myHand[2].play(4) computerHand[2].play(3) print("my hand:" ,myHand) print("dominoTable:",list(dominoTable)) print("computerHand:",computerHand) תמיד יש אפשרות לעשות את הכל בארבע שורות אבל קשה לעקוב אחרי הלוגיקה בקודים מקוצרים מידי. כך נראה השולחן והידיים של השחקנים אחרי סיבובי המשחק שלמעלה, בהם כל שחקן שיחק שלוש פעמים ונותר עם ארבע אבנים – my hand: [[0, 3], [4, 5], [2, 4], [0, 4]] dominoTable: [[4, 6], [6, 1], [1, 1], [1, 3], [3, 5], [5, 5]] computerHand: [[0, 2], [0, 0], [5, 6], [0, 1]] ביד שלי וביד של המחשב נותרו ארבע אבנים, האבנים בשולחן מסודרים בשרשרת נחמדה כמו שאבני דומינו צריכים להיות מונחים. בתור הבא יהיו לי כמה אפשרויות טובות, משום שהאבנים בשולחן מסודרות כך ש- 4 ו- 5 בקצוות ויש לי שלוש רביעיות וחמש אחד. השלבים הבאים בפרויקט – (אותם תוכלו לעשות בעצמכם) לאפשר משיכה של אבן מהקופה ליד של השחקן שאין לו אפשרות במסגרת האבנים שבידיו להגדיר ניצחון להפוך את המשחק של המחשב לאוטומטי להפוך את המשחק של המחשב לחכם (אם יש לו כמה אפשרויות משחק אולי כדאי להיפתר קודם ממספרים שיש לו כמותם בשפע באבנים אחרות) לשפר את הנראות של המשחק לתפוס שגיאות של שחקנים שמנסים לשחק עם אבן שלא ברשימה, למשל, לשחק עם אבן מספר 5 כאשר נותרו להם רק 3 אבנים ביד. או לתקן שגיאה של מהלך שהוא לא חוקי, הצמדת המספר 2 באבן אחת למספר 6 באבן אחרת. אולי לאפשר לשחקן להתחרט ולהחזיר את המהלך לאחור (אני בעד נגעת נסעת) – להלביש על התוכנה ממשק גרפי צבעוני שהשולחן יראה כמו שולחן והאבן כמו אבן דומינו (טוב זה עדיין רחוק – אבל מי שרוצה...)

  • חלק שלישי – מתקדמים יותר

    חלק שלישי – מתקדמים יותר - הקדמה טוב, מי שמסתובב, עדיין, באזור הזה, באמת התאהב בפייתון. אולי הפך לאיש מקצוע. זה אומר הרבה תרגול, ובנייה של פרויקטים קטנים, אחרת אי אפשר באמת להבין את החומר. מכאן, ממראים. מי שמתקשה בהבנה, מוזמן לשאול שאלות בפורום, ננסה לעזור ולקדם את ההבנה שלו ברבדים העמוקים שהתוכנה יודעת להגיע אליהם. ולא, לא היה לי שם יותר מקורי מאשר "מתקדמים יותר" אדי ארבילי

  • מחלקה ראשונה – עוד מתודות מיוחדות

    מחלקה ראשונה – עוד מתודות מיוחדות __setitem__ ו- __getitem__ מחלקות הן דרך מעולה ליצור מודלים של פיסות מציאות קטנטנות, באמצעותן אנו יוצרים אובייקטים בעלי תכונות ויכולות כאילו היו..אובייקטים במציאות. כשאנו יוצרים אובייקט של רשימה בפייתון, נניח באופן הבא [x=[1,2, פייתון מפנקת אותנו בשלל אפשרויות שמובנות בתוכנה, למשל אנו יכולים לבצע (x.append(3 ולקבל רשימה מעודכנת [1,2,3]. כשאנו בונים אובייקט משל עצמנו, מחלקה פרטית, אנו צריכים לאפשר את כל הפעולות הללו באופן ידני. אם האובייקט שלנו לובש אופי של רשימה, ואנו רוצים שהוא יתנהג באופן דומה לדרך שבה אובייקטים מובנים בפייתון מתנהגים, אנו צריכים לשלב במחלקה מתודות מיוחדות (special methods) מתאימות. __getitem__ מאפשרת לנו להגיע לאיברים ספציפיים בתוך האובייקט שלנו (שהוא כאמור מיכל בדומה לרשימה. class SomeContainer: def __init__(self): self.contain=[1,2] def __getitem__(self, item): return self.contain[item] def __repr__(self): return f"{self.contain}" c=SomeContainer() for i in c: print(i) >>> 1 2 למעלה, בנינו מחלקה בשם SomeContainer שבמהותה היא רשימה, המכילה מראש שני איברים בלבד המספרים 1 ו- 2. היא מכילה מתודה שכבר הכרנו בשם __repr__ כדי שנראה מה אנחנו מקבלים בתוך האובייקט. כתבנו מתודה מיוחדת בשם __getitem__ , וכעת אנו מייצרים אובייקט בשם c שיקבל את כל האפשרויות המטורפות שבנינו במחלקה, כמו למשל להדפיס את האיבר [c[0 שהוא המספר 1 או כמו שמודגם בתוכנית למעלה, לרוץ עם לולאת for ולהדפיס כל מה שיש ברשימה. בלי get item לא היינו מסוגלים להגיע לרגע המכונן הזה. אבל זה לא מספיק. כדי שהאובייקט יתנהג כמו list בפייתון, דרושות לנו תכונות נוספות. __setitem__ יאפשר לנו להחליף איברים ברשימה (רק במיקום קיים, לא ניתן עדיין להוסיף איברים) – ננצל את האפשרות כדי להחליף את המספר 2 במספר שהוא התשובה לשאלת השאלות... – 42 (מי שלא הבין הוא לא מספיק זקן כדי להתקל בדוגלס אדמס) class SomeContainer: def __init__(self): self.contain=[1,2] def __getitem__(self, item): return self.contain[item] def __repr__(self): return f"{self.contain}" def __setitem__(self,key, value): self.contain[key]=value c=SomeContainer() c[1]=42 print(c) >>> [1, 42] טוב זה לא מספיק, בואו נייצר שתי פונקציות שיתמכו גם ב- insert (מאפשרת שתילה של איבר במיקום מסוים ברשימה) וגם ב- append – שמוסיפה איבר לסוף הרשימה בצד ימין. שימו לב שהן לא פונקציות מיוחדות - class SomeContainer: def __init__(self): self.contain=[1,2] def __getitem__(self, item): return self.contain[item] def __repr__(self): return f"{self.contain}" def __setitem__(self,key, value): self.contain[key]=value def insert(self, location, value): self.contain.insert(location, value) def append(self, value): self.contain.append(value) c=SomeContainer() c.append(3) print(c) >>> [1, 2, 3] c.insert(2,42) print(c) >>> [1, 2, 42, 3] למעלה, הוספנו את המספר 3 לרשימה, ושתלנו את המספר 42 במיקום השני ברשימה. __delitem__ ו- __len__ לסיום האפיזודה הזאת נציין את __delitem__ המאפשרת באופן מפתיע...למחוק איברים ואת __len__ המאפשרת לנו למדוד את אורך הרשימה . class SomeContainer: def __init__(self): self.contain=[1,2] def __getitem__(self, item): return self.contain[item] def __repr__(self): return f"{self.contain}" def __setitem__(self,key, value): self.contain[key]=value def insert(self, location, value): self.contain.insert(location, value) def append(self, value): self.contain.append(value) def __delitem__(self, key): del self.contain[key] def __len__(self): return len(self.contain) c=SomeContainer() c.append(3) print(c) >>> [1, 2, 3] del c[0] print(c) >>> [2, 3] print(len(c)) >>> 2 כל הטרחה הזאת בשביל שאובייקט יתנהג כמו רשימה פושטית בפייתון, וזה עוד לא הכל, בכל פעם שמשהו לא עובד צריך לבדוק איזו מתודה מיוחדת חסרה לנו, אולי __iter__ כדי להפוך את האובייקט לאיטרבלי, אולי __del__ שמוחקת את כל האובייקט ולא רק item בתוכו וכו'. פעמים רבות מתכנתים יזנחו את הטרחה ויישארו בתכנות הפונקציונלי שהוא כמו לטוס במחלקת תיירים לעומת טיסה במחלקה ראשונה. אני סבור שהמאמץ משתלם.

  • טיפול ב-attributes

    טיפול ב-attributes __getattr__, __setattr__ , __delattr__ לאובייקט שאנו מייצרים במחלקה ישנן attribuetes או אם תרצו, תכונות. לעיתים נרצה לטפל בתכונות הללו או ליתר דיוק באפשרות לעשות בהן שימוש או למחוק אותן מהאובייקט. class MyAttributes: def __init__(self): self.data = {'a': 'alfa', 'b': 'beta'} def __getattr__(self, attr): return self.data[attr] obj = MyAttributes() d=obj.a print(d) >>> alfa בדוגמא למעלה, אנו מעבירים במסגרת המתודה המיוחדת __init__ שורה של תכונות (attributes) בתוך מילון. תכונה a היא alfa תכונה b היא beta. כדי להגיע לתכונה מסוימת דווקא ברשימת התכונות, אנו נזקקים ל- __getattr__ ולאחר ששיבצנו את המתודה במחלקה שלנו אנו יכולים לגשת לתכונה מסוימת ולראות אותה. כאשר אנו רוצים להוסיף באותו אופן תכונה נוספת לרשימה שלנו שתגיב ל- obj.c שעדיין לא קיימת, הדברים מעט מסתבכים, קחו אוויר, וננסה להסביר מה עושה המתודה המיוחדת __setattr__ ולמה זה יותר מסובך. פייתון משתמשת באותה שיטה כדי לבצע השמה של attribute ולכן שימוש אינטואיטיבי בפונקציה מהצורה self.data[key]=value כאשר מתודת __init__ נותרת כפי שהיא - יגרום לרקורסיה אינסופית - ()__setattr__ תקרא לעצמה.... לבעיה הזאת יש כמה פתרונות – שהבא מביניהם נראה לי יותר אלגנטי והוא גם קרוי בפייתון new style כך שאין מצב למשהו ב- old style. class MyAttributes: def __init__(self): object.__setattr__(self,"data", {'a': 'alfa', 'b': 'beta'}) def __getattr__(self, key): return self.data[key] def __setattr__(self, key, value): self.data[key]=value def __repr__(self): return f"{self.data}" obj = MyAttributes() obj.c="cool" print(obj) אנו קוראים למחלקה הבסיסית באמצעות ()object.__setattr__ כדי לחמוק מ- ()__setattr__ והרקורסיה הכרוכה בו. התוצאה {'a': 'alfa', 'b': 'beta', 'c': 'cool'} הצלחנו לשבץ תכונה חדשה תחת השם c, זה cool. בלי לגרום לרקורסיה אינסופית. באותו אופן גם הטיפול ב- __delattr__ עובר באופן חלק – ואנו מצליחים למחוק תכונה מהמחלקה שלנו. class MyAttributes: def __init__(self): object.__setattr__(self,"data", {'a': 'alfa', 'b': 'beta'}) def __getattr__(self, key): return self.data[key] def __setattr__(self, key, value): self.data[key]=value def __delattr__(self, key): del self.data[key] def __repr__(self): return f"{self.data}" obj = MyAttributes() obj.c="cool" del obj.a print(obj) הנה, מחקנו attribute התוצאה {'b': 'beta', 'c': 'cool'}

  • Hashable objects -אובייקטים ניתנים לגיבוב

    Hashable objects -אובייקטים ניתנים לגיבוב נתחיל ראשית בהבנת המושג hash table שהיא בעברית טבלת גיבוב או טבלת ערבול, שהיא מבנה נתונים מילוני שנותן ערך לפי מפתח. זה נעשה באמצעות פונקציית גיבוב שלוקחת את המפתח והופכת אותו למספר המהווה אינדקס במערך נתונים, וככה שולפים את הערך המתאים לאותו מפתח, רק כאשר צריך אותו בלי לשמור בזיכרון כתובות רבות לכל מפתח. משהוא כזה. פונקציית הגיבוב מתאימה את המספר 3 למפתח "a" אח"כ לפי האינדקס אנו יודעים ש- 3 מקבל את הערך "alfa" . כמובן שהיא צריכה להיות חד ערכית, כלומר שלכל מספר יהיה רק ערך אחד, אחרת מה עשינו. פייתון משתמשת בגיבוב כברירת מחדל בכל ה- immutable objects – אובייקטים שלא ניתנים לשינוי מרגע יצירתם, כמו integer, bool True או False, tuple או string. כדי לדעת את המספר המתקבל מהגיבוב, נשתמש במתודה ()__hash__ stringa="abc" tuplea=(1,2) n=None t=True f=False print(stringa.__hash__()) >>> 9075183606561537427 print(tuplea.__hash__()) >>> 3713081631934410656 print(n.__hash__()) >>> 109031757 print(t.__hash__()) >>> 1 print(f.__hash__()) >>> 0 אנו רואים שכל אובייקט מקבל מספר hash גם אובייקט מסוג None ומה שמעניין לשים לב אליו הוא שהאובייקט True מקבל את המספר 1 והאובייקט False מקבל את המספר 0. לרשימות, מילונים וסטים למשל לא יהיה hash משום שהם mutable ניתנים לשינוי (אפשר להוסיף ולגרוע איברים בלי לשנות את האובייקט לאחד חדש). איך אנו יודעים שמדובר באותו אובייקט ? פשוט. בפייתון נתנו לו מספר זהות- ()id. שווה לראות דוגמא של העניין הזה – lista=[1,2,3] print(id(lista)) >>> 2120179395784 lista.remove(1) print(lista) >>> [2, 3] print(id(lista)) >>> 2120179395784 לאחר שיצרנו רשימה lista היא מקבלת מספר זהות שאינו משתנה גם לאחר שגרענו את המספר 1 מהרשימה. זה אובייקט שניתן לשינוי mutable. היות שלמילון ולרשימה אין מספר גיבוב, לא ניתן להשתמש ברשימה כמפתח במילון של פייתון. מפתח יכול להיות רק אובייקט שיש לו מתודת hash. אם כך, פייתון לוקחת אובייקט והופכת אותו למספר, הדבר יכול ליצור בעיה באם התוכנה תהפוך שני אובייקטים בעלי תוכן שונה לאותו מספר. זה יכול לקרות ולהוביל להתנגשות (hash collision). לכן, פייתון אינה נסמכת רק על מספר הגיבוב כדי לדעת למשל את ההבדל בין מפתח למפתח אחר במילון. היא בודקת האם מדובר באותו אובייקט באמצעות מתודה אחרת הנקראת __eq__ כך שגם אם ניצור אובייקט בעל אותו מספר גיבוב - בדוגמא שלמטה מספר הגיבוב הוא 2 לשני אובייקטים שונים שיצרנו במחלקה MyClass – עדיין פייתון יכולה לאפשר להם להיות מפתחות באותו מילון משום שהם לא אותו אובייקט והיא בודקת גם את זה. class MyClass: def __init__(self, x): self.x = x def __hash__(self): return int(self.x) o1=MyClass(2) o2=MyClass(2) print(o1.__hash__()) >>> 2 print(o2.__hash__()) >>> 2 dicta={o1:"alfa",o2:"beta"} print(dicta) >>> {<__main__.MyClass object at 0x00000212963A43C8>: 'alfa', <__main__.MyClass object at 0x00000212963A45F8>: 'beta'} נושא הגיבוב hash הוא מיסודות התוכנה ובשלב המתקדם הזה של הלימוד כדאי להבין את המושג הזה, שאולי גם יסייע בעתיד לפתור Bug בתוכניות כאלה ואחרות, אם למשל תנסו למנות את המפתח מהסוג הלא נכון למילון שלכם. אובייקט ניתן לגיבוב חייב להיות immutable - אבל זה לא מספיק, מה לגבי tuple שהוא אימוטבילי המכיל רשימה שהיא כן מוטבילית ? יש חיה כזאת בפייתון והיא יכולה להראות למשל כך: (1,2,3,[42]) . האם הדבר הזה יכול להיות מפתח במילון ? האם הוא ניתן לגיבוב (hashable). בקיצור, לא ! גם אובייקט שהוא אימוטבלי, כמו טופל, אם הוא מכיל אלמנט שניתן לשנות, אינו ניתן לגיבוב.

  • בסיסי מספרים

    בסיסי מספרים איך אפשר ללמוד משהוא על מחשבים בלי להבין משהוא על בסיסי מספרים ועל איך שמחשבים עובדים באופן כללי. מה גם שאפשר לעשות עם הידע הזה הרבה דברים חשובים בפייתון. השיטה הפופולרית הנהוגה בקרב בני האנוש היא השיטה העשרונית, ואני קניתי את ההסבר שהדבר נובע מכך שלבני האדם יש עשר אצבעות ששימשו בתחילה לצורך אריתמטיקה פשוטה. במקום לחשוב על צורות שונות ומשונות למספר 1, 10 ו- 100 פשוט מוסיפים אפס ומזיזים את המקום של הספרה 1 כך שהמיקום שלה קובע אם המספר הוא גדול או קטן. השיטה הבינארית – היא על בסיס 2 - חסכונית בסימנים והיא כוללת רק את הספרות 0 ו- 1 אבל המיקום משתנה מהר יותר. ערך המיקומים מהימיני ביותר לשמאלי ביותר 1, 2 ,4 ,8, 16 ,32 ,64 וכך הלאה - לכן , הערך של המספר הבינארי 111 הוא סיכום של כל המיקומים בהם מופיעה הספרה 1 - כלומר 10. הערך של המספר הבינארי 10010 הוא 18. המיקום הראשון מימין הוא 2 בחזקת 0, השני 2 בחזקת 1, השלישי 2 בחזקת 2 וכו'. השיטה האוקטלית - היא על בסיס 8 (והמיקומים על בסיס חזקות של 8). ערך המיקומים הראשונים 1, 8 ,64, 512 ,4096 ,32768 וכו' (המיקום הראשון הוא שמונה בחזקת 0, השני שמונה בחזקת 1 ,השלישי 8 בחזקת 2 וכו') כך שהמספר האוקטלי 100001 שווה ל- 32,768+1 כלומר 32,769 המספר העשרוני 8 שווה ל- 10 בבסיס אוקטלי. השיטה ההקסדצימלית (hexadecimal) היא על בסיס 16 – לעיתים יש תועלת בבסיסים הגדולים מ- 10 ובעיקר כאלה שמתכתבים יפה עם איך שהמידע במחשב מאורגן ב- byte. אבל אם בבסיס הבינארי השתמשנו בשתי ספרות, באוקטלי בשמונה ספרות (אפס עד שבע) בעשרוני בעשר ספרות. איך ניקח 16 ספרות שונות אם יש לנו רק 10 ? פשוט, הספרות מ-0 עד 9 כמו בשיעור חשבון ביסודי, לאחר מכן משתמשים באותיות ה- A-B-C האנגלי, a עד f כך שהאות A היא המספר 10 והאות F היא המספר 15. ערך המיקומים בשיטה: 1, 16, 256, 4096, 65536, וכו' מה ערכו של המספר ההקסדצימלי ABC ? חחח קודם כל מצחיק שזה מספר (בדרך כלל גם יגיע עם סימון שאנו עוסקים במספר על בסיס 16 ולא במילה כלשהי). נחשב – A עשר פעמים 256 B אחת עשרה פעמים 16 C שתיים עשרה פעמים 1 סה"כ קיבלנו – 2,748 הרעיון ברור, צריך ספרות כמו בשם הבסיס, בסיס 16 פירושו שש עשרה ספרות, הערך נקבע על פי מיקום שהוא חזקה עולה מ-0 ואילך של הבסיס. עכשיו נראה איך זה בפייתון. print(hex(2748)) >>> 0xabc print(oct(32769)) >>> 0o100001 print(bin(42)) >>> 0b101010 הרישום הוא כזה: משמאל לימין רושמים תמיד את הספרה 0 אח"כ אות לועזית המסמלת את הבסיס (x עבור בסיס הקסדצימלי, האות O בסיס אוקטלי והאות b בסיס בינארי) לאחר מכן רושמים את הספרות והאותיות של אותו בסיס. אנו רואים ש- abc הם אכן 2,748 כמו שחישבנו. ואפשר גם כך (int("124",8 כדי לומר שאנו רוצים את הערך העשרוני של המספר "124" בבסיס אוקטלי, או int("f",16) אם אנו רוצים את הערך של "f" בבסיס הקסדצימלי. לשים לב שגם כאשר יש לנו ספרות, אנו מכניסים אותן כמחרוזת, והמספר השני מימין מייצג את הבסיס). print(int("f",16)) >>> 15 print(int("124",8)) >>> 84 y = [int(x, 16) for x in 'a1c28e9f7c3d58c10'] print(y) >>> [10, 1, 12, 2, 8, 14, 9, 15, 7, 12, 3, 13, 5, 8, 12, 1, 0] הדוגמא השלישית בתוכנית למעלה, היא יצירת רשימה של איברים שכל אחד מהם בין אפס ל-15 באמצעות הזנת מחרוזת שכל אחד מאיבריה יהיה מספר כזה 0-15. למה זה טוב? לעיתים זה נוח, כאשר יש לנו יותר מעשר אפשרויות למצב מסוים, ואנו רוצים לייצג כל מצב באמצעות סימן אחד בלבד, להזין ערכים הקסדצימלים, לפונקציה כלשהי. תרגיל -מבינארי לעשרוני טוב, ראינו שפייתון עוברת בקלות מבסיס לבסיס, אבל באנו ליהנות, וגם השקעתי בהסבר על הבסיסים, לכן התרגיל שלי הוא לכתוב פונקציה שתיקח מספר עשרוני שמורכב רק מאפס ואחת (כאילו היה מספר בינארי) ותמצא את הערך העשרוני שלו (בהנחה שהוא היה בינארי באמת – למשל שהמספר 1000 יהפוך ל- 8). תנסו פתרון משלכם, אפשר גם לראות את הפתרון שלי – def bin2dec(bin): decimal=0 power=0 while bin>0: decimal+=2**power*(bin%10) bin//=10 power+=1 return decimal print(bin2dec(1001101)) >>> 77 print(bin(77)) >>> 0b1001101 הפונקציה בנויה על הרעיון שהחזקה של 2 (בבסיס בינארי) עולה ככל שמתקדמים שמאלה במיקום הבינארי. והטיפול במספר מחולק לשניים – לוקחים את הספרה הימנית ביותר (0 או 1) באמצעות המודולו bin%10 שבעצם משאירה לנו את ספרת היחידות שהיא הימנית ביותר – היא השארית וכל היתר מתחלק יפה בעשר. ספרת היחידות המתקבלת (0 או 1) מוכפלת בהתחלה ב- 2 בחזקת 0 (כלומר ב-1) את המספר המתקבל מוסיפים למשתנה בשם decimal. אחרי שטיפלנו בספרה הימנית ביותר אנו מקצצים את ספרת היחידות מהמספר (bin) ומכינים את משתנה החזקה power לסיבוב הבא והוא הכפלת ספרת היחידות ב- 2 בחזקת 1 (כלומר ב-2). כך ממשיכים את כל הסבבים וצוברים ב-decimal את הערך של כל המכפלות של הספרה הימנית ביותר בחזקות הולכות ועולות של 2. אם כך יצא לנו שהמספר הבינארי 1001101 הוא בעצם 77, וכדי לבדוק וגם להראות כמה קל לעשות את הדברים בפייתון. אנו בודקים איך פייתון חושבת שהמספר 77 צריך להראות בבסיס בינארי. ואנו מקבלים את אותו דבר בתוספת 0b (אפס ואז האות b המקדימות כל מספר בינארי).

  • ניהול קבצים

    ניהול קבצים פייתון מאפשרת לנו לפתוח קבצים קיימים או חדשים מסוגים שונים, לכתוב לתוכם טקסט או תוכן אחר או לקרוא ולעבד אותו בצורות שונות. כדי לפתוח קובץ חדש או קיים (אם הוא לא קיים פייתון תבין שרוצים לייצר קובץ חדש) אפשר, למשל, לכתוב את הפקודה ("open("filename.txt","w לתוך משתנה נגיד f . הפקודה הזאת תפתח קובץ בשם filename עם סיומת txt. המחרוזת השנייה שמזינים לפקודה open למשל "w" בשביל לכתוב, "r"בשביל לקרוא, "a" בשביל לצרף טקסט בהמשך בלי לדרוס את מה שכבר כתבנו ועוד כמה שנזכיר בהמשך. לאחר מכן אפשר לכתוב לתוך הקובץ באמצעות הפקודה write או לקרוא מה שכתבנו באמצעות הפקודה read ונראה איך זה עובד. בסוף כל פעולה צריך לסגור את הקובץ באמצעות הפקודה ()close אחרת בזמן, שפייתון מנקה אשפה אחת לכמה זמן (מנקה כל מיני אובייקטים ששום דבר לא מצביע עליהם), היא עלולה לנקות גם את הקובץ הזה! f=open("myfile.txt","w") f.write("my first line ever") f.close() f= open("myfile.txt","r") print(f.read()) f.close() >>> my first line ever פתחנו קובץ חדש בשם myfile המיועד לכתיבה בלבד ולכן ה – mode שבחרנו היה "w". כתבנו את השורה "my first line ever" מאברוק. סגרנו את הקובץ ! בהמשך רצינו לראות את התוכן של הקובץ, שוב כתבנו את הפקודה open הפעם ב-mode "r" לקריאה בלבד שלא נכתוב משהו בטעות. הדפסנו את מה שיוצא מהמתודה read והופה. הדפסנו את מה שכתבנו. מסתבר שכל העניין הזה עם לסגור את הקובץ אחרי השימוש הוא די ניג'וס ולכן בפייתון חשבו על פתרון נוח יותר, שמבצע סגירה אוטומטית של הקובץ. וזה באמצעות הביטוי with , הבה נראה איך כותבים את זה אחרת – with open("myfile.txt","w") as f: f.write("my first line\n") with open("myfile.txt", "a") as f: f.write("my second line\t") f.write("tab in second") with open("myfile.txt","r") as f: for line in f: print(line) >>> my first line my second line tab in second כאן כבר עשינו כמה דברים אחרת, ה- syntax הוא with open והמשתנה שלנו f (שהוא לא ממש משתנה אלא עטיפה לקובץ הטקסט שלנו) נכתב בצורה :as f ושורה מתחת, בהזחה ימינה, כתבנו את מה שאנו רוצים לכתוב כטקסט בשורה הראשונה, בתוספת הוראה לרדת לשורה הבאה n\ משם נמשיך בפעם הבאה שכתבו משהו. זהו, אין צורך להורות על סגירת התיק, הוא נסגר אוטומטית כמעט כמו פרשות שחיתות בפוליטיקה. בפעם השנייה שפתחנו את הקובץ, על מנת שלא לדרוס את מה שכתבנו, פתחנו ב- "mode "a מהמילה append לצרף למה שכתוב ולא במקום. בסוף המשפט הראשון בשורה השנייה הוספנו t\ כדי ליצור רווח (בלי לרדת שורה) למשפט הבא בטקסט שלנו tab in second. את קריאת הטקסט ביצענו באמצעות לולאת for העוברת על כל שורה בקובץ. אפשר לפתוח קבצים מסוגים שונים ואפשר לפתוח אותם במודים אחרים – למשל +r מאפשר קריאה וכתיבה גם יחד אבל דורס מה שכתבנו קודם לכן. "+a" פותח קובץ חדש אם לא קיים, אם קיים מאפשר גם קריאה, אבל אינו דורס טקסט קודם אם יש, אלא ממשיך להוסיף החל מסוף הטקסט. העולם הזה עשיר במידע ואפשרויות שונות, ישנן ספריות כמו pandas המתאימות לניהול קבצים כמו excel וקבצים לניהול מידע בצורות אחרות. ספריה זו אינה סטנדרטית ב- python וצריך לראות סרטונים ביו טיוב כדי לראות איך מתקנים אותה אם רוצה להשתמש בה.

  • אתגר מספרים ראשוניים

    בפורום הצגנו את החידה הבאה - בתוך ריבוע קסם הכולל תשעה תאים (3X3) צריך לשבץ תשעה מספרים ראשוניים שונים (בין 0 ל-100 כולל המספר 1 שלא ברור למתמטיקאים אם הוא גם נחשב למספר ראשוני), באופן שכל שורה בריבוע, כל טור וכל אלכסון יהיה סכומם שווה ל- 111. לאחר שבונים פונקציה ליצירת כלל המספרים הראשוניים (אחת האפשרויות) מופיעה במדריך שלנו בפרק העוסק ברקורסיה, אנו מגלים שיש 26 אפשרויות שונות - [1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97] אנו זקוקים בסך הכל לתשעה מספרים. החידה מסקרנת ורוצים לפתור אותה מהר, לכן מייבאים את הספרייה itertools ומבקשים לייצר את כל האפשרויות (permutations) לתשעה מספרים מתוך 26 כאשר התשעה יכולים להיות גם אותם תשעה במיקומים שונים. כמה אפשרויות המחשב צריך לייצר לנו ? הנוסחה היא !26 לחלק ב- !(26-9) התוצאה היא 1,100,000,000,000 - גם אם המחשב שלכם מאוד מהיר, זה הרבה מאוד זמן לסיים את כל האפשרויות האלה, על זה תוסיפו את הסיפור שפייתון היא סביבת עבודה שלמה ונעדרת הידור (קומפילציה) ולכן מעט יותר איטית מתוכנות אחרות וקיבלתם נצח. ניסיון לברוח לרקורסיה גם הוא לא יניב תוצאות מהירות וטובות עם כל כך הרבה אפשרויות. ולכן אין מנוס מלשבת ולכתוב קוד שיצמצם לנו בכל פעם את מספר הפתרונות האפשריים באופן משמעותי. אם אנו יודעים למשל שהמספר הגדול ביותר ברשימה הוא 97 זה אומר לנו שסכום שני מספר שכנים לא יכול להיות קטן מ-14 משום שאז גם אם נוסיף את המספר הגדול ביותר שלנו, 97, לא נגיע ל- 111; כמו כן, סכומם של שני מספרים לא יכול לעלות על 110 משום שלא נותר מקום למספר שלישי בשורה בטור או באלכסון . וכאשר יש לנו כבר שני מספרים שכנים השלישי צריך להשלים ל- 111. עם הנתונים הללו או משתמשים באיטרציה באמצעות לולאות for הרצה על האפשרויות שלנו (רשימת המספרים הראשוניים), מחפשת אפשרויות יותר מדויקות עבורנו. ובכל סיבוב שאנו עוברים למספר הבא, מורידים מהרשימה את המספרים שכבר השתמשנו בהם. לכן, למרות שיש כל כך הרבה אפשרויות, כאשר יש לי שני מספרים בשורה ואני מחפש את השלישי - כאן יש לי רק אפשרות אחת - זו המשלימה ל- 111. למטה ישנו פתרון הדומה לפתרון של טבע מהפורום שלנו, תשומת ליבכם לכך, למי שירצה לנתח את הקוד, שבכל פעם שאנו משלימים סיבוב לגבי מספר בלוח שלנו, אנו עושים את הסיבוב הבא על רשימת הראשונים (פחות אלה שכבר השתמשנו בהם). זה החלק הראשון של הקוד - def primes(n): """ prime numbers from 1 to n """ listPrime=[] for num in range(1,n): for other in range(2,num): if num%other==0: break else: listPrime.append(num) return listPrime potentials=primes(100) board=[0 for i in range(9)] #זו רשימת המספרים הראשונים בחלק השני של הקוד כדאי לשים לב לשימוש ב- [::]potentials הפעולה הזאת מסמלת ברשימה לעבור על הפריטים מהתחלה עד הסוף, אבל חשוב מזה, היא מייצרת אובייקט חדש שלא תלוי בשינויים של רשימת הראשוניים שלנו בסיבוב הראשון. אם לא נוסיף את הסימן הזה [::] התוכנית לא תעבוד כמו שצריך, משום שכל פעם שנשנה את הרשימה ברמה אחת, היא תשתנה גם ברמה אחרת, משום שבפייתון נעשה שימוש במצביעים, ולעיתים גם כשנדמה לנו שיצרנו רשימה חדשה, בעצם יצרנו רק מצביע חדש. הסימן הזה מאפשר לנו ליצור אובייקט בלתי תלוי ולהריץ את האיטרציה עליו (ההסבר לאיטרציה במדריך) באמצעות לולאת for. board=[0 for i in range(9)] #זה ריבוע הקסם שלנו הכולל תשע משבצות for first in potentials[::]: #זו הלולאה הראשונה שלנו indx=potentials.index(first) board[0]=first potentials.remove(first) for second in potentials[::]: if (board[0] + second) > 13 and 111>(board[0] + second) : indx = potentials.index(second) board[1] = second potentials.remove(second) for third in potentials[::]: if (board[0] + board[1]+ third) == 111: indx = potentials.index(third) board[2] = third potentials.remove(third) for forth in potentials[::]: if (board[0] + forth) >13 and 111>(board[0]+ forth): indx = potentials.index(forth) board[3] = forth potentials.remove(forth) for fifth in potentials[::]: if 111 > (board[1] + fifth) and (board[1] + fifth)> 13 and (board[3] + fifth) >13 and (board[3] + fifth)<111 : indx = potentials.index(fifth) board[4] = fifth potentials.remove(fifth) for sixth in potentials[::]: if (board[3] + board[4]+sixth) == 111: indx = potentials.index(sixth) board[5] = sixth potentials.remove(sixth) for seventh in potentials[::]: if (board[0] + board[3] + seventh) == 111 and board[2]+board[4]+seventh==111: indx = potentials.index(seventh) board[6] = seventh potentials.remove(seventh) for eighth in potentials[::]: if (board[1] + board[ 4] + eighth) == 111: indx = potentials.index( eighth) board[7] = eighth potentials.remove(eighth) for last in potentials[::]: if (board[ 2] + board[ 5] + last) == 111 and board[0]+board[4]+last==111: indx = potentials.index( last) board[ 8] = last print(board) #מדפיסים פתרון כשיש לוח מלא potentials.insert(indx,eighth) #כאן מתחילים להחזיר מה שהוצאנו potentials.insert(indx,seventh) potentials.insert(indx,sixth) potentials.insert(indx,fifth) potentials.insert(indx,forth) potentials.insert(indx,third) potentials.insert(indx,second) potentials.insert(indx,first) פתרון החידה - [7, 73, 31, 61, 37, 13, 43, 1, 67] [7, 61, 43, 73, 37, 1, 31, 13, 67] [31, 73, 7, 13, 37, 61, 67, 1, 43] [31, 13, 67, 73, 37, 1, 7, 61, 43] [43, 1, 67, 61, 37, 13, 7, 73, 31] [43, 61, 7, 1, 37, 73, 67, 13, 31] [67, 1, 43, 13, 37, 61, 31, 73, 7] [67, 13, 31, 1, 37, 73, 43, 61, 7] התוכנית מניבה את כל שמונת הפתרונות שהם למעשה פתרון אחד עם סיבוב של הריבוע כל פעם לצלע אחרת ועם תמונת ראי

  • הספריה operator

    הספריה operator מאפשרת שורה של פונקציות המקבילות לאופרטורים ההשוואתיים והאחראים לפעולות המתמטיות, המובנים בפייתון (כמו שווה, גדול, קטן, לא שווה, גדול שווה, כפל, חילוק, חיבור וכו'). נניח שיש לנו רשימה של המספרים 0 עד 9 ואנו רוצים לבחון מי מהם גדול מ- 5, במקום לרשום פונקציה שתערוך את הבדיקה אנו יכולים להשתמש במתודות של הספריה operator – המתודות הללו בעצם משוות עבורנו אלמנטים שונים, למשל ברשימה, או באובייקט איטרבילי אחר ויכולות להחזיר ערכים בוליאנים. הנה דוגמא – import operator lista=[i for i in range(10)] for item in lista: print(operator.gt(5,item)) operator.gt משווה עבור אילו איברים ברשימה המספר חמש גדול יותר (greater than) התוצאה המתקבלת מהפונקציה שלנו היא בוליאנית – True True True True True False False False False False כלומר 5 גדול מחמשת האיברים הראשונים ברשימה, לכן מקבלים True בחמש הפעמים הראשונות ולאחר מכן Flase. באותו אופן יש אפשרויות נוספות המקבילות לאופרטורים המובנים האחרים בפייתון, למשל operator.lt(x,y) מקביל ל- x=y ניתן להשמש בספריה שלא למטרות השווה, למשל במקום פונקציה לכפל – באופן הבא – import operator lista=[i for i in range(5)] for item in lista: m=operator.mul(item,10) print(m) >>> 0 10 20 30 40 operator.add לחיבור operator.mod לקבל שארית כמו במודולו % operator.div לקבל מנה מפעולת חילוק operator.pow לפעולת חזקה ועוד (פעולות המקבילות למה שקיים באופן מובנה בפייתון). אנו יכולים להפוך, כמו בתוכנית הבאה, את כל המספרים לנגניטיב שלהם – import operator lista=[i for i in range(-2,2)] for item in lista: print(operator.neg(item)) >>> 2 1 0 -1 אנו יכולים לשלב את הפונקציות של operator עם מתודות נוספות, למשל כאשר אנו רוצים לבצע סדרה של פעולות בין שתי רשימות של מספרים כדי לקבל רשימה שלישית המשלבת את תוצאות הפעולה על איברים משתי הרשימות הראשונות- כמו בדוגמא הבאה - import operator list1=[2,4,6] list2=[1,2,3] maping=map(operator.mul,list1,list2) print(list(maping)) >>> [2, 8, 18]

  • re-ספריה לחיפוש בטקסט

    הביטוי המלא במקורו הוא regular expresstion הקיצור הוא re, הדברים המפורסמים שהספרייה עושה היא להגדיר תבנית ולחפש אותה בתוך מחרוזות של טקסט נתון. נתחיל עם המכניקה של הספרייה - import re pattern=re.compile("\d+") text="Eddie says my pone number is 073-7143670 and i like music from the 80's " \ 'haha@music.com' \ 'duran_duran@music.org' \ 'pink_floyd@music.co.il ' print(pattern.findall(text)) תחילה מייבאים את ספריית re ולאחר מכן יוצאים תבנית באמצעות הפקודה re.compile שבתוך הסוגריים שלה אנו בונים את התבנית שבהמשך נחפש בתוך הטקסט שלנו. הביטוי "+d\" מסמל למשל שאנו מחפשים סדרות של מספרים digit לכן האות d שאחריהן עוד מספר אחד או יותר ולכן הסימן + . תחת המשתנה text הכנסתי כמה משפטים שבהם נחפש תבניות שניצור. לתבנית שלנו קוראים pattern אנו מחפשים בטקסט באמצעות כתיבת המילה pattern ואחריה נקודה וסוג החיפוש שאנו רוצים לבצע. בתוכנית שלמעלה המחשב מחזיר רשימה, כי הפקודה findall מחזירה רשימות של כל המחרוזות העונות על הגדרת התבנית - output: ['073', '7143670', '80'] אבל מה אם אנו רוצים את כל מספר הטלפון כמו שהוא ובלי דברים אחרים שאינם מספר וטלפון ? אנו צריכים ליצור תבנית חדשה. התבנית הזאת - (pattern=re.compile("\d\d\d.\d\d\d\d\d\d\d" תיתן לנו כל מחרוזת המורכבת מרצף של שלוש ספרות, שלאחריו סימן כלשהו (הנקודה מסמלת סימן כלשהו) ולאחריו רצף של שבע ספרות. כמובן שאפשר להכין באופן דומה תבנית עבור מספר טלפון סלולרי. אנו מקבלים - output: ['073-7143670'] כעת נגיד שאנו מחפשים כתובות דוא"ל בלבד ) את הטקסט נכניס בין מרכאות משולשות (""") יותר אלגנטי- import re pattern=re.compile("\w+@\w+\.\w+\.?\w+.?") text="""Eddie says my pone number is 073-7143670 and i like music from the 80's haha@music.com duran_duran@music.org pink_floyd@music.co.il """ print(pattern.findall(text)) הסימן w מייצג אותיות מספרים וקו תחתון כך מתחילה כתובת דוא"ל טיפוסית ואנו מצפים למצוא אחד או יותר מהסימנים הללו עד שאנו מגיעים לכרוכית @ לאחר הכרוכית אנו רוצים לראות שוב רצף של אותיות ומספרים וקו תחתון לאחר מכן תמיד נקודה לאחר מכן רצף של אותיות בדרך כלל ולעיתים מסיימים בזה כאשר הסיומת שלנו למשל היא com. אבל לעתים יש המשך למשל כאשר הסיומת היא co.il כאן נכנסים סימני השאלה להביע שהאלמנט בתבנית הוא אופציונלי ולמצוא גם מחרוזות עם אותו אלמנט וגם בלעדיו. כשכתבתי את התבנית ראיתי שהיא מדפיסה את הסיומת co.i ולא co.il למרות שביקשתי רצף שלאחריו הסימן + שמשמעותו אחד או יותר משום שלא הגדרתי עד לאן הרצף הזה צריך להמשך - ורק כאשר הוספתי נקודה(.) לאחר ה + התוכנית השלימה את כל החיפוש והציגה את הדוא"ל המלא משום שנקודה עם סלש הפוך לפניה, מסמלת בעצם כל סימן אות או מספר לרבות שורה ריקה שמגיעים לאחר הרצ שהגדרנו - ['haha@music.com', 'duran_duran@music.org', 'pink_floyd@music.co.il'] ישנה אפשרות להכניס מספר אופציות לסוגריים מרובעים למשל [!,?] פירושים סימן קריאה או פסיק או סימן שאלה ורק אחד מהם. זה למשל ("pattern=re.compile(r"\d0[w'?,.]sימצא לנו את המחרוזת output: ["80's"] כל מה שנמצא בסוגריים המרובעים מיועד לקלוט את אחד הסימנים שאנו חושבים שעשויים להופיע במקום הזה במחרוזת, למשל גרש. הסיפור של האות r בתוך הסוגריים לפני התבנית, פירושה שאנו מחפשים מחרוזת בתצורתה הגולמית כלומר שסלשים הפוכים במחרוזת אינם מסמנים משהו מיוחד כמו ירידת שורה או טאב כנהוג בפייתון אלא סתם סלש הפוך המשמש את הספרייה re לעניינים אחרים, זה נחוץ משום שהספרייה re עושה כפי שראינו לעיל שימוש בסלשים הפוכים כדי להגדיר תבניות (ולא כפי שפייתון מכירה את הסלשים ההפוכים האלה). הספרייה היא גדולה ויש לה יכולות למצוא בדייקנות כל שילוב של מחרוזות שאנו יכולים לחלום עליו והיא גם יכולה לומר לנו היכן מה שחיפשנו מתחיל והיכן הוא מסתיים במחרוזת שלנו. יש אפשרות למצוא רק רצפים הפותחים את המחרוזת - import re pattern=re.compile(r"[eE]\w+") text="""Edd says: my name is Eddie Arbili""" print(pattern.findall(text)) >>> ['Edd', 'Eddie'] כדי שהתוכנית תמצא רק את Edd אנו נוסיף לתבנית את הסימן ^ שפירושו רק רצף הפותח מחרוזת - pattern=re.compile(r"^[eE]\w+") ... >>> ['Edd'] הסימן לרצף המסיים מחרוזת הוא $ בסוף התבנית - import re pattern=re.compile(r"A\w+$") text="""Edd says Am I dreaming?: my name is Eddie Arbili""" print(pattern.search(text)) >>> <_sre.SRE_Match object; span=(42, 48), match='Arbili'> אנו רואים למעלה שהתוכנית לא תפסה את המילה Am שגם היא מתילה ב- A אלא רק את המילה שמסיימת את המחרוזת. בדוגמא למעלה גם הפעלנו את המתודה search שנותנת לנו כל מיני נתונים בין היתר את המקום שבו מתחילה המילה שחיפשנו Arbili במיקום ה- 42 במחרוזת, והמיקום בו המילה מסתיימת 48 כך שאם היינו רוצים לבצע חיתוך של המחרוזת (הטקסט): import re pattern=re.compile(r"A\w+$") text="""Edd says Am I dreaming?: my name is Eddie Arbili""" print(text[42:48]) >>> Arbili הספריה היא עצומה ויש בה אפשרויות רבות - נעבור בקצרה על חלק מהסימנים ומשמעותם - \b מחרוזת ריקה לפני ואחרי מילה \B מחרוזות ריקות שאינן לפני ואחרי מילה \d ספרה 0-9 \D כל מה שאינו ספרה 0-9 \s מרווחים למיניהם כולל טאב ושורה חדשה \S כל מה שאינו רווח כאמור לעיל \w אותיות באנגלית גדולות וקטנות וכן ספרות 0-9 וכן קו תחתון \W כל מה שאינו אות ספרה או קו תחתון כאמור לעיל \Z סוף המחרוזת {n} פעמים n חזרת כל התבנית על עצמה * אפס או יותר חזרות על אלמנט +חזרה אחת או יותר על אלמנט ? אפס או אחת חזרות על אלמנט דוגמא לחיפוש של מילים המתחילות ב- E או ב- A באמצעות השימוש בסימן | (בצד ימין של המקלדת מעל הסלש ההפוך) : import re pattern=re.compile(r"E\w+|A\w*") text="""Edd says Am I dreaming?: my name is Eddie Arbili""" print(pattern.findall(text)) >>> ['Edd', 'Am', 'Eddie', 'Arbili'] אלה הדברים העיקריים, למרות שיש עוד הרבה בספרייה העצומה והשימושית הזאת re

  • functools

    הספריה functools הספריה functools מיועדת לפונקציות ממעלה גבוהה יותר, שזה אומר פונקציות הפועלות על פונקציות אחרות או המחזירות פונקציות. אחת המתודות הנפוצות שעברו לספריה הזאת לאחר שיצאו מהשפה הסטנדרטית בשל תחליפים ברורים יותר, היא functools.reduce המקבלת פונקציה ואובייקט איטרבילי, ומחזירה בסופו של דבר ערך אחד. בהתחלה היא מפעילה את הפונקציה על שני האיברים הראשונים, לאחר מכן, היא לוקחת את תוצאה שהתקבלה משני האיברים הראשונים ומפעילה את הפונקציה על התוצאה ועל האיבר השלישי, וכך הלאה, כל פעם לוקחת את התוצאה המצטברת ומפעילה על האיבר הבא, עד שמקבלים בסוף ערך אחד. import operator,functools lista=[1,2,3,4,5] f=operator.mul print(functools.reduce(f,lista)) >>> 120 בדוגמא למעלה יצרנו פונקציה המכפילה שני איברים באמצעות המתודה mul המיובאת מהספריה operator אשר ניתן לקרוא אודותיה בהרחבה במדריך, ולקחנו רשימה פשוטה של המספרים 1-5 כעת מבצעים reduce (שהיא מתודה בספריה functools) שתכפיל את 1 ו- 2 לאחר מכן את התוצאה (2) ב- 3, לאחר מכן את התוצאה(6) ב- 4 , לאחר מכן את התוצאה (24) ב- 5 ובסוף מקבלים 120. בעצם בנינו מנגנון לחישוב עצרת!. מתודה שימושית נוספת בספריית functools אודותיה כתבנו פוסט נפרד בשם memoization נקראת lru_cache והיא מסייעת בהאצת תוכניות עם פונקציות רקוסיביות מורכבות. תחפשו את הפוסט בבלוג ותקראו שם.

bottom of page