לכל שפת תכנות שאני מכיר יש את המוזרויות הפרטיות שלה: תחביר לא עקבי, סימונים דו-משמעיים, "יוצאי דופן", או כל חריגה אחרת מהקו הראשי-כביכול של השפה, שלמתכנתים אין ברירה אלא פשוט לזכור בעל-פה. מהבחינה הזו פייתון לא מיוחדת, אבל מה שכן הצליח להרגיז אותי הוא המהירות והעוצמה שבה המוזרויות של פייתון נחתו עליי.
מתישהו בחטיבת הביניים, כשעוד הייתי מתכנת לתומי ב-QBasic, חבר לכיתה סיפר לי שיש שפת תכנות אחרת(!) שקוראים לה "פסקל". הוא אפילו היה מספיק בעניינים כדי להראות לי את השלד המינימלי שחייב להיות לכל תוכנית כדי שהיא תוכל לרוץ בכלל:
program MyProgName; begin end.
אני זוכר שזה נראה לי מופרך לחלוטין. מה השטויות האלה? ארבע מילים שלמות שחייבים לכתוב אבל לא עושות שום פעולה? ועוד עם סימני פיסוק? אבסורד!
כמובן, זמן לא רב לאחר מכן זכיתי להיכרות קצת יותר רצינית עם פסקל, והיא הפכה להיות הפייבוריטית שלי למשך שנים רבות. ברגע שהתגברתי על רפלקס "זה לא כמו בבייסיק!" הצלחתי לראות את הסדר שבשפה החדשה, את ההיגיון ואת העקביות שבמבנה שלה. זה לא מקרי, כידוע: היא נבנתה בכוונה תחילה בצורה מסודרת וברורה במיוחד, כאמצעי ללימוד תכנות ואלגוריתמים.
מאוחר יותר, כשלמדתי את השפה יותר ביסודיות (והשפה עצמה השתכללה והשתנתה עם גרסאות הקומפיילרים וסביבות הפיתוח החדשות), התחלתי לראות גם את הסדקים שנוצרו במבנה המרשים. פסקל הפכה עם השנים משפה אקדמית "נקייה" לכלי עבודה מעשי בשטח, וכתוצאה מכך נוספו לה כל מיני מרכיבים שלא הצליחו לשמור על הקו הברור והמסודר, בין אם זה בגלל כורח הנסיבות או בגלל עצלנות של המפתחים.
את פייתון אני מכיר כבר זמן-מה, בעיקר מקורסים מקוונים ומהמעט שעשיתי עם הגרסה המפושטת, MicroPython. מההתחלה היא לא הפעימה אותי, אבל זה היה בסדר מבחינתי: לא למדתי אותה כדי להתפעם, אלא כי היה לי ברור שזה כלי עבודה נפוץ וחשוב שאני צריך להכיר. מצד שני, קשה מאוד להכיר שפת תכנות באמת בלי שכותבים בה קוד לעולם האמתי, וכעת כשאני עושה זאת הכול נופל עליי בבום. הנה הסיפור.
בפייתון יש שלושה סוגים עיקריים של "אוספים" של אובייקטים: List (רשימה "רגילה"), Tuple (כמו List רק שאי אפשר לשנות את מה שיש בתוכו לאחר שנוצר), ו-Dictionary (רשימה שאנחנו מגדירים במפורש את האינדקסים של האלמנטים שבה). כל סוג אוסף נבדל מהאחרים כבר ברמת התחביר הבסיסי: List מוקפת בסוגריים מרובעים, Tuple מוקף בסוגריים רגילים (וזה כשלעצמו קצת עקום כי יש לסוגריים האלה מספיק שימושים גם ככה), ו-Dictionary מוקף בסוגריים מסולסלים.
יש בפייתון גם פטנט שנקרא List comprehension. זהו מין תחביר ייחודי של לולאות ו/או תנאים, שלא מתקבל כפקודה רגילה בתוכנית, אבל אם הוא בתוך סוגריים מרובעים הוא מפיק List של ערכים. סתם לדוגמה, כדי ליצור רשימה (List) של המספרים הזוגיים בין 2 ל-20 אפשר לכתוב כזה דבר:
Evens = [n*2 for n in range(1, 11)]
למה צריך לכתוב שם 11 ולא 10? ככה זה בפייתון, שיהיה. אבל עכשיו מתחיל הבלגן. רציתי ליצור Tuple עם ערכים מסוימים, והנחתי בנאיביות שאם אכתוב קוד של List comprehension בתוך סוגריים של Tuple במקום סוגריים של List, זה מה שאקבל. במבט לאחור הייתי צריך להבין שזה לא יילך: הרי יש סיבה שקוראים לזה List comprehension ולא Tuple comprehension… אבל הקוד רץ ואפילו עבד: כשבדקתי אם ערך חיצוני מסוים נמצא בתוך הדבר הזה שיצרתי, קיבלתי תשובה נכונה.
…אבל אז, כשבמקרה קראתי שוב לאותה פונקציה, קיבלתי תשובה לא נכונה. מה קרה?
הסתבר שאם כותבים ביטוי של List comprehension בתוך סוגריים של Tuple, מה שמתקבל זה לא Tuple אלא Generator, מין אובייקט שמפיק את המספרים בזה אחר זה לפי דרישה. אז בפעם הראשונה שזה רץ, הקוד שלי עבר (בלי שהבנתי את זה) על המספרים שה-Generator הפיק עד שמצא את הערך החיצוני המבוקש. אבל בקריאה השנייה, ה-Generator לא התחיל מהתחלה אלא מהמקום האחרון שהגיע אליו בפעם הקודמת. אז כמובן שהערך שחיפשתי כבר לא היה שם.
מתוך שאט נפש על חוסר העקביות של השפה, וגם מסקרנות, חיפשתי מידע על מה שקורה אם כותבים List comprehension בתוך סוגריים מסולסלים, כמו של Dictionary. מסתבר שגם דבר כזה יכול לרוץ, ומה שיוצא הוא לא רשימה רגילה ולא Generator, אלא set – רשימה שאין בה ערכים כפולים.
כל אחד מהאלמנטים האלה, כשלעצמו, הוא בסדר גמור. יש מה לעשות עם גנרטורים ועם סטים וכל זה. אבל צורת הסימון והיצירה שלהם נטולת כל היגיון. זה כאילו שבכל פעם שחשבו על פיצ'ר חדש, מישהו פשוט הגריל בשבילו צורת סימון מתוך הסימנים הקיימים בשפה. ומה שעוד יותר גרוע, מכיוון שזו שפה סלחנית וגמישה כל כך, אין מנגנון שיגיד לנו אם ובמה טעינו. רק הבאג, אם וכאשר יתגלה, ירמוז לנו שהייתה טעות איפשהו לאורך הדרך.
כמו שהזכרתי בהתחלה, זה לא ששפות אחרות חפות מבעיות, אבל פייתון היא מקרה קיצוני. השינויים בה מבוצעים תוך כדי תנועה, כשהיא כבר מיושמת בשטח, וכך נוצר מצב שקוד מפייתון 2.7 עשוי לא לעבוד בפייתון 3, אבל אי אפשר לוותר על 2.7 כי יותר מדי יישומים כבר נכתבו בה. אז בעצם יש שתי שפות שנקראות פייתון, ואסור לסמוך על דוגמאות קוד מהרשת לפני שמוודאים במאה אחוזים שמדובר על הנכונה, וגם אז יש סיכוי שמדובר בטכניקות שכבר הפכו ל"לא מומלצות" ומי יודע אם ייתמכו בכלל בגרסה הבאה.
ובכלל, איזו מין שפת תכנות זו שאין בה לולאת do…while (או repeat..until בתחביר הפסקלי)? את זה ידעתי כבר קודם, אבל זה מעצבן אותי בכל פעם מחדש: בכל שפה עילית נורמלית יש שלושה סוגי לולאות עיקריים, רק בפייתון שתיים. למה? כי לא הצליחו לחשוב על תחביר מתאים לשלישית. בחיי, זה התירוץ. מה, לא יכולתם לשים את זה בתוך סוגריים מסולסלים או משהו? ועכשיו ברצינות, אם יש לכם מבנה תחבירי כזה (נלקח מ-docs.python.org):
try: raise KeyboardInterrupt finally: print('Goodbye, world!')
אי אפשר להסתמך עליו ולעשות את הלולאה הארורה פשוט ככה?
repeat: # stuff to do until: # condition
כל הזכויות שמורות לי, תזכרו איפה ראיתם את זה בפעם הראשונה 🙂
תוספת: רגע אחרי שכתבתי את זה, גיליתי שבשפת Go של גוגל התחכמו עוד יותר ויש שם רק סוג לולאה אחד, for. זה לא משנה את דעתי: בכל השפות העיליות הנורמליות יש שלושה סוגי לולאות!
אפשר לעשות dict comprehension, פשוט עם:
d = {key, value for key, value in blablabla}
למשל.
הסיבה שבלי הפסיק והאיבר השני זה סט זה בגלל כתיב מתמטי… (וזה גם תואם לכתוב הרגיל של סט…)
לא סותר את מה שאמרתי…
(ועל איזה פסיק ואיבר שני דיברת?)
חחח… תסתכל על הרשימה כאן ולא תאמין כמה אוצרות יש בשפה המפולאה הזו
https://stackoverflow.com/questions/101268/hidden-features-of-python
למי שמגיע מפסקל או גירסות c שונות פייתון עלולה להיות מוזרה אבל בחיי שקשה לא להתאהב בה. זו שפה נהדרת למתחילים ועוד יותר למפתחים מנוסים שרוצים להתמודד בעיקר עם הלוגיקה של המוצר ולא עם איך לעשות זאת.
אם הדוגמה לכמה פייתון טובה זה מבנה שנקרא for/else, זה רק מוכיח ביתר שאת את הנקודה שלי… 🙂
למה בעצם זה משנה כמה מבנים לכתוב לולאה יש? איך זה משליך על שפה ״טובה״ או לא?
עוד לא נתקלתי באמת בבעיה שהפתרון שלה בפייתון פחות קריא משפות אחרות, אשמח מאוד לראות דוגמא אמיתית.
(המורה עידו התחיל!)
בכוונה נמנעתי בפוסט מהגדרות מעורפלות כמו "שפה טובה", כי מזה באמת לא ייצא שום דבר 🙂 התייחסתי רק לעקביות של השפה עם עצמה ועם עקרונות בסיסיים של תכנות.
לגבי קריאות, זה מאוד פשוט: תן למישהו שעוד לא התרגל לאף אחת מהשפות לקרוא אתחול של מערך עם List comprehension ועם לולאה ב-C, ותראה את מי הוא מצליח להבין יותר בקלות. ברור שמרגע שמתרגלים לשפה, גם Lisp נראית ברורה 😉
ועוד משהו, אם כבר: כתבת "למפתחים מנוסים שרוצים להתמודד בעיקר עם הלוגיקה של המוצר" – אז לא, בפיתוחים רציניים ההתמודדות עם הלוגיקה של המוצר צריכה להתבצע מראש, בתכנון, לפני שכותבים אפילו שורה אחת של קוד. אם עושים את שלב התכנון "על הנייר" כמו שצריך, אפשר ליצור מוצר מעולה גם ב-VB. מה שטוב להאקתון של יומיים, או לפיתוח של סקיצה בשביל להרשים משקיעים, לא בהכרח רלוונטי לפרויקט רציני עם השלכות בעולם האמיתי.
האמת שכאן אתה צודק במאה אחוז ואני לא הסברתי את עצמי כמו שצריך. למערכת מורכבת אין תחליף לתכנון מוקדם ונכון של רכיבים ותקשורת בין רכיב לרכיב. ובכל זאת ארחיב, בין האקתון/סקיצה לבין ״פרוייקט רציני עם השלכות בעולם האמיתי״ יש מנעד רחב של שימושים אפשריים ברמות שונות של ״רצינות״ גם עם לפתח משהו לוקח כמה שעות, התוצר בהחלט יכול להיות רציני, מושקע ועם חשיבות אמיתית כולל יצירת ערך אמיתי לגוף שעבורו הוא מפותח. ״בעולם האמיתי״ בחירת הכלי המתאים למשימה זהו תהליך חשוב וחלק מתהליך פיתוח של מוצר. מנסיוני (האישי בלבד!) אין תחליף לפייתון במשימות שרת או עיבוד טקסט/מספרים – וזו היתה… לקרוא עוד »
חסר גם case/switch !
נכון, אבל יש elif, ובשקלול של קלות הקריאה וכמות הקוד שצריך לכתוב זה יוצא בערך אותו הדבר.