ToCode
Открыть в Telegram
1 421
Подписчики
Нет данных24 часа
+37 дней
-430 день
Архив постов
1 421
# חדש באתר: מיני קורס Type Hints בפייתון
"מה הבעיה הכי גדולה שלך בכתיבת פייתון?" היא השאלה שאני אוהב לפתוח איתה קורסי פייתון שאני מעביר בחברות, ובין התשובות תמיד יהיה מקום של כבוד לנושא הטיפוסים: "חסר לי הקומפיילר", "אני רואה את השגיאות רק בזמן ריצה", "אי אפשר לדעת אם קוד יעבוד". רבים מאוד מאיתנו לא משתמשים בהגדרות טיפוסים בקוד שאנחנו כותבים או לא משתמשים בהן מספיק.
וזה חבל כי Type Hints הם הכרחיים בעבודה על כל פרויקט גדול. כי לא משנה כמה ההשלמה האוטומטית טובה, בלי Type Hints היא תמיד תטעה לפעמים, וכי לא משנה כמה אתם בטוחים שהקוד שלכם קריא, שלושה חודשים אחרי שכתבתם אותו הסיכוי לזכור מה צריך להעביר לאיזו פונקציה יורד משמעותית.
אני חושב שהסיבה שאנחנו לא כותבים מספיק Type Hints היא קודם כל עקומת למידה: ברגע שהולכים מעבר להוספת int או str אחרי נקודותיים, אנחנו מגלים שיש המון דברים ללמוד על מערכת הטיפוסים של פייתון. במקומות מסוימים אנחנו מגלים שכתיב שהיינו בטוחים שיעבוד מציג שגיאות על קוד שדווקא עובד מעולה, ובמקומות אחרים אנחנו לא מצליחים להגדיר את הטיפוסים כך שיציגו את השגיאות שקל מאוד לראות בעיניים. ובגלל שתמיד יש משהו יותר דחוף לעשות, אנחנו דוחים את הלימוד ומתכננים להוסיף את הגדרות הטיפוסים בגירסה הבאה.
אם זאת היתה הבעיה שלכם, תשמחו לשמוע שהגירסה הבאה היא היום. במיני קורס חדש וממוקד על הגדרות טיפוסים ריכזתי (בגירסאות וידאו וטקסט מלא מלווה) את כל מה שצריך לדעת על Type Hints כדי להשתמש בהם בתוכניות אמיתיות. בקורס תלמדו בין השאר:
1. איך להגדיר טיפוסים לפונקציות, רשימות, מילונים ושאר מבני נתונים בפייתון.
2. איך להגדיר טיפוסים לפונקציות מיוחדות כמו Decorators, Generators ופונקציות המקבלות מספר משתנה של פרמטרים.
3. איך (ולמה) לעבוד עם TypeVar.
4. איך להגדיר פרוטוקול ומתי להשתמש בו, ואיזה פרוטוקולים כבר מוגדרים בשפה.
אבל בעיקר תלמדו איך לכתוב הגדרות טיפוסים לקוד מגוון דרך דוגמאות פרקטיות.
הקורס הוא קצר ומורכב משיעור פתיחה ואחריו עוד שבעה שיעורים ואפשר לסיים לצפות בו ולתרגל בחצי יום, ואחרי זה ליישם את הדברים שלמדתם בכל תוכנית פייתון שתכתבו.
מנויים לאתר יכולים כבר לצפות בתוכן בקישור:
https://www.tocode.co.il/boosters/13.
ואם אתם עדיין לא מנויים היום הוא הזדמנות מצוינת להירשם.
שאלות, דעות, פידבקים והצעות למיני קורסים נוספים מוזמנים לשלוח אליי למייל או להשאיר הודעה דרך האתר.
1 421
# כמה סיבות לשפר קוד שעובד
קוד הבדיקה הבא ב PyTest עובד מעולה (גם אם לא ממש ברור למה הוא טוב כי זו בדיקה מומצאת רק בשביל הפוסט):
from unittest.mock import MagicMock
def test_mock(monkeypatch):
hello = MagicMock()
fake_len = MagicMock()
fake_len.return_value = 8
hello.__len__ = fake_len
with monkeypatch.context() as m:
m.setattr(hello, '__len__', fake_len)
assert len(hello) == 8
assert fake_len.called
הבדיקה לימדה אותנו שהפונקציה len של פייתון באמת קוראת למתודה __len__ של הדבר שהעבירו לה. אבל זה לא הדבר החשוב כאן. יותר מעניין לשים לב לגירסה הפשוטה יותר של הבדיקה שעושה בדיוק את אותו דבר:
def test_mock(monkeypatch):
hello = MagicMock()
hello.__len__ = MagicMock(return_value=8)
monkeypatch.setattr(hello, '__len__', hello.__len__)
assert len(hello) == 8
assert hello.__len__.called
שני השיפורים בגירסה הפשוטה הם מחיקות: מחיקת המשתנה fake_len שלא היה בו צורך, ומחיקת השימוש ב context - שוב מנגנון שבהקשר של הבדיקה הנוכחית לא היה נדרש.
ובכל זאת כשאנחנו מסתכלים על שתי הגירסאות קל לדמיין שאלה שינויים לא חשובים, שהכל היה בסדר גם קודם ושעדיף להתעסק בכתיבת פיצ'רים חדשים במקום לתקן את הקיים. אני חושב שהמציאות קצת יותר מורכבת מהסיבות הבאות:
1. בשביל לתקן אני צריך להבין מה עושה monkeypatch.context ומתי כן צריך להשתמש בו. רק להבין את זה שווה את המאמץ, בלי קשר לקוד שיתקבל.
2. קוד גרוע משתכפל ומייצר Cargo Cults - לאורך זמן אני יכול לדמיין עשרות ומאות בדיקות שישתמשו ב context, אפילו שאין בזה שום צורך. זה מבזבז זמן ובמיוחד אנחנו רואים את זה ב Code Reviews כשמתכנתת חדשה נכנסת לצוות וצריכה ללמוד את כל החוקים המשונים של הצוות שאין להם חשיבות טכנית.
3. יום אחד כשיהיה באג יהיה יותר קל לחקור את הבעיה כשהקוד מורכב מפחות מבנים וכאלה שאני מבין טוב יותר.
לאורך זמן קוד מינימליסטי מנצח. אם התיקון קטן, אם יש גיט ובדיקות ואם אתם מבינים למה הקוד המוזר הגיע לשם (אולי אפילו מזהים מאיזה פוסט בסטאק אוברפלו הוא הועתק), שווה למחוק אותו.1 421
# כמה סיבות לשפר קוד שעובד
קוד הבדיקה הבא ב PyTest עובד מעולה (גם אם לא ממש ברור למה הוא טוב כי זו בדיקה מומצאת רק בשביל הפוסט):
from unittest.mock import MagicMock
def test_mock(monkeypatch):
hello = MagicMock()
fake_len = MagicMock()
fake_len.return_value = 8
hello.__len__ = fake_len
with monkeypatch.context() as m:
m.setattr(hello, '__len__', fake_len)
assert len(hello) == 8
assert fake_len.called
הבדיקה לימדה אותנו שהפונקציה len של פייתון באמת קוראת למתודה __len__ של הדבר שהעבירו לה. אבל זה לא הדבר החשוב כאן. יותר מעניין לשים לב לגירסה הפשוטה יותר של הבדיקה שעושה בדיוק את אותו דבר:
def test_mock(monkeypatch):
hello = MagicMock()
hello.__len__ = MagicMock(return_value=8)
monkeypatch.setattr(hello, '__len__', hello.__len__)
assert len(hello) == 8
assert hello.__len__.called
שני השיפורים בגירסה הפשוטה הם מחיקות: מחיקת המשתנה fake_len שלא היה בו צורך, ומחיקת השימוש ב context - שוב מנגנון שבהקשר של הבדיקה הנוכחית לא היה נדרש.
ובכל זאת כשאנחנו מסתכלים על שתי הגירסאות קל לדמיין שאלה שינויים לא חשובים, שהכל היה בסדר גם קודם ושעדיף להתעסק בכתיבת פיצ'רים חדשים במקום לתקן את הקיים. אני חושב שהמציאות קצת יותר מורכבת מהסיבות הבאות:
1. בשביל לתקן אני צריך להבין מה עושה monkeypatch.context ומתי כן צריך להשתמש בו. רק להבין את זה שווה את המאמץ, בלי קשר לקוד שיתקבל.
2. קוד גרוע משתכפל ומייצר Cargo Cults - לאורך זמן אני יכול לדמיין עשרות ומאות בדיקות שישתמשו ב context, אפילו שאין בזה שום צורך. זה מבזבז זמן ובמיוחד אנחנו רואים את זה ב Code Reviews כשמתכנתת חדשה נכנסת לצוות וצריכה ללמוד את כל החוקים המשונים של הצוות שאין להם חשיבות טכנית.
3. יום אחד כשיהיה באג יהיה יותר קל לחקור את הבעיה כשהקוד מורכב מפחות מבנים וכאלה שאני מבין טוב יותר.
לאורך זמן קוד מינימליסטי מנצח. אם התיקון קטן, אם יש גיט ובדיקות ואם אתם מבינים למה הקוד המוזר הגיע לשם (אולי אפילו מזהים מאיזה פוסט בסטאק אוברפלו הוא הועתק), שווה למחוק אותו.1 421
# אופס עשיתי את זה שוב
כן בסיסי נתונים הם חשובים. כן הם צריכים לעבוד מהר וזה אחלה שהם יודעים לנהל המון המון מידע. אבל איכשהו מסע בזמן אף פעם לא היה הצד החזק שלהם.
וככה כשמישהו כותב בטעות:
DELETE FROM users;
ושוכח להוסיף את ה WHERE, או שטועה בתנאי באיזה UPDATE אנחנו נתקעים.
וכן אפשר לחזור מגיבוי וחשוב שיהיו גיבויים, אבל בינינו לחזור מגיבוי זה לא טיול בפארק אפילו כשאתם יודעים מה אתם עושים. וכן אפשר להוציא מגיבוי רק את התוכן של טבלת המשתמשים ואיכשהו לייצא את הנתונים שם לבסיס נתונים נפרד ולייבא אותם בחזרה לבסיס נתונים הראשי, אבל גם זה לא הולך לעבור חלק.
במקום כל אלה הייתי רוצה לראות כלי פשוט שיגרום לבסיס הנתונים להתנהג כמו גיט ביחס למסע בזמן. למשל שאפשר היה לכתוב:
git log -- users;
ולקבל את כל השינויים שבוצעו לטבלת המשתמשים, ואפילו:
git restore -s HEAD~1 users
כדי להחזיר את התוכן של טבלת המשתמשים קומיט אחד אחורה.
עד שימציאו כזה מנגנון שימו לב לגיבויים ונסו להתרחק מפקודות מחיקה.1 421
# השנה 2023. הגיע הזמן להפסיק לדבר על ירושה בפייתון
הקונספט של ירושה בין מחלקות היה פופולרי כשכולם כתבו Java ו C++ בגלל שזו היתה הדרך היחידה לכתוב קוד פולימורפי. אם רצינו ב Java ליצור מערך של מוצרים ממחלקות שונות, היה עלינו לוודא שלכל המחלקות יש Base Class משותף, והשפה עצמה וידאה שאנחנו מתיחסים רק למאפיינים מה Base Class של הדברים מהמערך.
אני מבין את ההגיון הזה ב Java. אבל בפייתון? הנה הקוד למחלקה של עגלת קניות שמכילה 3 סוגים של מוצרים:
from dataclasses import dataclass
class Cart:
def __init__(self):
self.products = []
@dataclass
class Table:
price: float
name: str
@dataclass
class Shoes:
price: float
color: str
c = Cart()
c.products.append(Table(price=120, name="Big Table"))
c.products.append(Table(price=80, name="Small Table"))
c.products.append(Shoes(price=180, color="orange"))
יותר מזה, אני יכול להוסיף פונקציה ל Cart שתדפיס את המחיר הכולל של המוצרים:
class Cart:
def __init__(self):
self.products = []
def price(self):
return sum(p.price for p in self.products)
ואפשר להגדיר פרוטוקול כדי לבדוק טיפוסים בזמן כתיבת הקוד:
class Product(Protocol):
price: float
class Cart:
def __init__(self) -> None:
self.products: list[Product] = []
def price(self):
return sum(p.price for p in self.products)
ואז אפשר לקבל שגיאות כשמנסים להוסיף משהו שאין לו מחיר לעגלת הקניות, בדיוק כמו שהיה קורה ב Java.
תרחיש שני שאולי היה גורם למישהו לחשוב על יצירת עצי ירושה הוא שימוש חוזר בקוד. במקרה שלנו אפשר לדמיין קוד שיהיה משותף לכל המוצרים ולכן כדאי לכתוב אותו רק במחלקת הבסיס "ולרשת" את המימוש במחלקות של שאר המוצרים. לצערי גם כאן כל דוגמה שאני מצליח לחשוב עליה אני יכול לשכתב לקוד טוב יותר תוך שימוש במנגנונים אחרים לשימוש חוזר בקוד של השפה, בדגש על Descriptors ו Class Decorators. החיסרון בירושה הוא שאתה מקבל המון דברים במכה אחת ובלי לבקש אותם, ובלי אפשרות לשלוט באיזה פונקציונאליות בדיוק תקבל.
השנה 2023 וכבר מזמן אין סיבה להשתמש בירושה ב Python.1 421
# שני סוגים של Outsourcing
יש את ה Outsourcing של אלה שיודעים ורוצים לחסוך זמן: זה יהיה הצוות שעובד על פיתוח פרויקט בריאקט אבל המעצב שלהם יודע רק פיגמה. הם היו שמחים לייצא את העיצוב מ figma לקומפוננטות ריאקט ולהתחיל לעבוד אבל לא מצאו עדיין כלי מספיק טוב ולכן יעדיפו לשלוח את ה figma לקבוצה חיצונית שתמיר את הכל לקומפוננטות ריאקט כדי שהם יוכלו להתמקד בלוגיקה, בביצועים ובפונקציונאליות של היישום.
ויש את ה Outsourcing של אלה שאין להם מושג: זה יהיה אותו צוות כשיצטרכו להנגיש את האתר. הם לא למדו איך לעשות את זה ואין למנהלים שלהם רצון "לבזבז" זמן על הכשרה. עדיף להוציא החוצה את העבודה לקבוצה שמתמחה בהנגשה ולקבל קוד מתוקן.
אחד חוסך זמן ומשפר את קצב העבודה. השני כמעט תמיד ייכשל בגלל חוסר הבנה ואינטרסים לא מתואמים של הצדדים.
1 421
# שני סוגים של Outsourcing
יש את ה Outsourcing של אלה שיודעים ורוצים לחסוך זמן: זה יהיה הצוות שעובד על פיתוח פרויקט בריאקט אבל המעצב שלהם יודע רק פיגמה. הם היו שמחים לייצא את העיצוב מ figma לקומפוננטות ריאקט ולהתחיל לעבוד אבל לא מצאו עדיין כלי מספיק טוב ולכן יעדיפו לשלוח את ה figma לקבוצה חיצונית שתמיר את הכל לקומפוננטות ריאקט כדי שהם יוכלו להתמקד בלוגיקה, בביצועים ובפונקציונאליות של היישום.
ויש את ה Outsourcing של אלה שאין להם מושג: זה יהיה אותו צוות כשיצטרכו להנגיש את האתר. הם לא למדו איך לעשות את זה ואין למנהלים שלהם רצון "לבזבז" זמן על הכשרה. עדיף להוציא החוצה את העבודה לקבוצה שמתמחה בהנגשה ולקבל קוד מתוקן.
אחד חוסך זמן ומשפר את קצב העבודה. השני כמעט תמיד ייכשל בגלל חוסר הבנה ואינטרסים לא מתואמים של הצדדים.
1 421
# ה Regexp הלא נכון
באמצע משחק באיזה אתר של ביטויים רגולאריים הם שאלו איך לתפוס דרך ביטוי רגולארי טקסט שמכיל רווחים בשני הצדדים, בלי לתפוס את הרווחים שבהתחלה ובסוף. ההצעה שלהם היתה:
^\s*(.*)\s*$
אני יכול לראות למה זה נראה כמו רעיון טוב - אתה חושב שאתה תופס את הטקסט שבאמצע, ויותר מזה אם ננסה את הביטוי ונדפיס את ה Capture Group הראשון התוצאה "תיראה" נכונה:
$ echo " hello world " | perl -nE '/^\s*(.*)\s*$/ && say $1'
hello world
עד שאנחנו שמים לב שחסר משהו בביטוי. תיקון תוכנית הבדיקה כבר יחשוף את הבעיה:
$ echo " hello world " | perl -nE '/^\s*(.*)\s*$/ && say "[$1]"'
[hello world ]
וזה ברור - החלק של הנקודה כוכבית באמצע תפס את כל הביטוי כולל את כל הרווחים שבסוף. עד שמישהו הגיע לחפש את החלק האחרון של הביטוי כבר לא נשארו רווחים בטקסט. ואחרי שרואים את זה גם הפיתרון ברור, פשוט מוסיפים סימן שאלה אחרי הנקודה כוכבית שבאמצע כדי לקחת "הכי מעט תווים שאפשר" לחלק הזה. התוצאה היא:
$ echo " hello world " | perl -nE '/^\s*(.*?)\s*$/ && say "[$1]"'
[hello world]
וכן ביטויים רגולאריים זה מסורבל וכל מה שתגידו נכון, אבל הטיפ שלנו היום הוא יותר כללי מביטויים רגולאריים - כן חשוב לכתוב קוד נכון, אבל אפילו יותר חשוב לכתוב תוכניות בדיקה טובות. כשרואים את הסוגריים המרובעים מסביב לפלט מיד רואים את הטעות ואפשר לתקן אותה.1 421
# שישה שלבים של שימוש בספריה חיצונית
בעבודה עם ספריה חיצונית לאורך זמן אנחנו מטפסים בסולם בן שישה שלבים-
1. לעשות מה שב Readme.
2. לחפש עוד דוגמאות ב API Doc.
3. להיכנס לקוד הספריה כדי להבין טוב יותר איך דברים עובדים.
4. לחפש Github Issues רלוונטים כדי לפתור בעיות נקודתיות.
5. לכתוב קוד שיתממשק עם ה Internals של הספריה (ישתמש בפונקציות פרטיות, יעשה Monkey Patch לחלקים שם).
6. למזלג, ליצור גירסה שלנו ולשלוח Pull Request.
מרגישים שאתם יודעים מספיק על ספריה כלשהי? זה זמן טוב לעלות שלב בסולם.
Уже доступно! Исследование Telegram 2025 — ключевые инсайты года 
