1 419
订阅者
无数据24 小时
-17 天
+230 天
帖子存档
1 419
# ארבע טעויות שהורסות לנו את הלימוד
תהליך לימוד טכנולוגיה חדשה יכול להיות מהנה ומספק, אבל גם יכול להיות מלחיץ ולא יעיל. ברוב המקרים כשהתהליך נכשל זה בגלל שעשינו אחת או יותר מ-4 הטעויות הבאות:
## ללמוד יותר מדי דברים ביחד
טעות מספר אחת והיא הנפוצה ביותר היא הרצון ללמוד יותר מדי דברים במכה אחת. זה בולט במיוחד בתחום של פיתוח Full Stack בגלל הכמות ההזויה של טכנולוגיות שנתקלים בהן. זה לא נדיר לראות מישהו שמנסה להיכנס לריאקט מנסה ללמוד גם Next.JS, גם JavaScript, גם React, גם Redux, גם MobX, גם CSS Modules, גם CSS In JS, גם Webpack, גם Material UI, וגם וגם וגם.
עכשיו מצד אחד יש פה הגיון כי במודעת דרושים או איפה שלא תסתכלו באמת יש רשימה ארוכה ומתישה של טכנולוגיות. אבל מהצד הפרקטי זה פשוט בלתי אפשרי להשתלט על כל כך הרבה חומר במכה אחת. כל פעם שאנחנו יורדים מהשביל כדי ללמוד עוד טכנולוגיה אנחנו רק מתרחקים מהיעד של היכרות טובה עם הטכנולוגיה אותה רצינו ללמוד מההתחלה.
במקום לרוץ לכל הכיוונים יותר משתלם להתמקד: לבחור רק טכנולוגיה אחת וללמוד אותה לעומק. אחרי זה להמשיך לשניה, ולשלישית ולרביעית. בזכות היכולת להתמקד כל פעם בטכנולוגיה אחת, הלימוד של כל טכנולוגיה יהיה מהיר יותר וזמן הלימוד הכולל יהיה קצר יותר - ובנוסף אתם תרגישו הרבה פחות מתוסכלים מהתהליך.
## לחגוג הישגים אוביקטיביים
הישג אוביקטיבי הוא משהו שאפשר לראות ולהראות לאחרים. לדוגמה אם החלטתי ללמוד לכתוב אפליקציות לאייפון אז הישג אובייקטיבי יהיה אפליקציה שאנשים מורידים אותה ונהנים ממנה, ועדיף אחת בתשלום כך שגם נראה הכנסה מהעבודה.
או אם אתם לומדים פיתוח Full Stack אז הישג אוביקטי יהיה לבנות מערכת שתרשים מראיינים פוטנציאליים כדי שיזמינו אתכם לראיון עבודה או יותר טוב למצוא ממש עבודה בתחום.
אם המוח שלכם עובד קצת כמו שלי אז כל יום שעובר בלי הישג אוביקטיבי מוסיף לחץ בצורה של: "אולי אני לא בכיוון", "אולי לעולם לא אצליח לכתוב אפליקציה שאנשים ירצו", "אולי התחרות קשה מדי", "אולי בחרתי תחום שלא מתאים לי" ועוד אינספור מחשבות ופחדים מסוג זה.
הבעיה עם הישגים אוביקטים היא שהם באמת קשים להשגה. זה יכול לקחת שנה ללמוד Full Stack ברמה שתכניס אתכם לעבודה. זה יכול לקחת שלוש שנים להתאמן על פיתוח אפליקציות עד שתפתחו אחת שאנשים ירצו לשלם עליה ולספר עליה לחבריהם. המתנה להישגים אוביקטיביים אומרת שאתם מעבירים את רוב תהליך הלימוד בחששות ובלחץ שאולי אתם לא בכיוון הנכון ושאולי זה אף פעם לא יצליח. באקלים רגשי כזה מאוד קשה להנות מהלימוד ולהגיע לנקודת הסיום.
במקום לחכות להישג אוביקטיבי הרבה יותר מומלץ לחגוג הישגים סוביקטיביים. כשהתחלתי ללמוד פיתוח אפליקציות לאייפון כתבתי את האפליקציות הראשונות שלי רק על סימולטור והחלטתי שברגע שאני מסיים לכתוב משהו שעובד אני קונה לעצמי מכשיר אמיתי (אז היה דבר בשם ipod touch). הנקודה שלבנות משהו שעובד זה הישג שתלוי רק בי והוא הרבה יותר מהיר מלבנות משהו שאנשים ירצו לקנות. ואחרי שקניתי את ה ipod touch קיבלתי חיזוק למוטיבציה שעזר לי להמשיך עד ההישג הסוביקטיבי הבא.
כשאנחנו חוגגים הישגים סוביקטיביים אנחנו לא פוגעים בסיכוי שלנו להגיע להצלחה - בדיוק להיפך. אנחנו מעודדים השקעה מתוך הבנה שההצלחה היא תוצאה של תהליך והדבר החשוב הוא לבנות תהליכים נכונים.
## ללמוד יותר מדי תיאוריה
טעות מספר שלוש היא להיתקע על תיאוריה ואצלנו בתכנות היא מאוד בולטת. זה מה שקורה כשאנשים לוקחים עוד קורס ועוד קורס ונתקעים על פרויקטים של קורסים ותרגילים קטנים במקום ליישם את הידע ולהתקדם לפרויקטים יותר ויותר גדולים.
חלוקת העבודה בין לימוד מקורס לבין עבודה אמיתית בקורס תכנותי צריכה לעמוד על חמש-אחד, כלומר כל שעה שהשקעתם בלימוד אתם צריכים להשקיע 5 שעות בכתיבת קוד אמיתי. זה אומר שמי שלוקח קורס וידאו שהאורך שלו הוא 8 שעות וידאו צריך להשקיע בנוסף ל-8 שעות צפיה בוידאו עוד 40 שעות בכתיבת קוד אמיתי על הנושאים שנלמדו בוידאו.
## לנסות לשלוט בקצב
טעות רביעית שיוצרת תסכול בתהליך הלימוד היא הניסיון לשלוט בקצב הלימוד או יותר נכון בקצב הקליטה. לימוד הוא לא לינארי והרבה מאוד פעמים אני נתקע המון שעות על איזה נושא מסוים שנראה ממש קטן ולא חשוב, כשפעמים אחרות אני רץ ומבין מהר המון נושאים אחרים.
הניסיון לסדר את הלימוד בתוך סילבוס ולהחליט שייקח לי שלוש שעות ללמוד להשתמש ב React Hooks תורם רק תסכול.
1 419
# לא שמתי לב
לא שמתי לב לגודל של ה Docker Image ... עד שהתחיל להיגמר לי המקום על הדיסק.
לא שמתי לב לזמני הטעינה של האתר ... עד שהתחילו להגיע תלונות מגולשים שלא מצליחים להתחבר.
לא שמתי לב לאיטיות השאילתות ב DB ... עד שקוד התחיל להיכשל על Timeouts בדיוק בשיא העומס על המערכת.
לא שמתי לב לבעיות האבטחה בקוד ... עד הפריצה שהפילה לי את כל המערכת לשבועות.
לא שמתי לב שהקוד כל כך מבולגן ... עד שהגיע מתכנת חדש לצוות שלא הצליח למצוא את הידיים והרגליים שם.
בתוכנה לא לשים לב זה סוג של בחירה. יש לא מעט כלים טובים שעוזרים לנו לזהות הרבה בעיות לפני שמגיעים לפרודקשן. הבחירה לשים לב עוזרת לבנות מערכות טובות יותר, ויותר מזה היא פותחת דלתות לאזורי לימוד חדשים ועוזרת לנו להתקדם כמתכנתים.
1 419
https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/.
אני מוצא שבעבודה עם קוברנטס הרבה יותר קל לי להסתכל על דוגמאות עובדות ועם kustomize זה קל כי יש תיקיית דוגמאות די טובה בדף הגיטהאב של הפרויקט בקישור:
https://github.com/kubernetes-sigs/kustomize/tree/master/examples.
1 419
command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
ports:
- containerPort: 6379
resources: {}
restartPolicy: Always
status: {}
ותוכן הקובץ redis-service.yaml הוא:
apiVersion: v1
kind: Service
metadata:
annotations:
kompose.cmd: kompose convert -f ../docker-compose.yml
kompose.version: 1.25.0 (a70f80cc)
creationTimestamp: null
labels:
io.kompose.service: redis
name: redis
spec:
ports:
- name: "6379"
port: 6379
targetPort: 6379
selector:
io.kompose.service: redis
status:
loadBalancer: {}
## תיקיית components/datavolume
התיקיה השניה היא הקומפוננטה אותה נרצה לשלב בבניה בסביבה מסוימת. אפשר לבנות כמה קומפוננטות שאתם רוצים בתור תתי תיקיות של תיקיית components. לכל קומפוננטה יש קובץ kustomization.yaml ושלי נראה כך:
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
resources:
- data-persistentvolumeclaim.yaml
patchesStrategicMerge:
- redis-deployment.yaml
הקובץ אומר שאם אתם משלבים קומפוננטה זו אתם צריכים להוסיף את הקובץ data-persistentvolumeclaim.yaml ובנוסף אליו צריכים לעשות Merge לערכים מהקובץ redis-deployment.yaml שנמצא אצלי בתיקיה. הקובץ redis-deployment.yaml הוא המעניין בין השניים אז נתחיל איתו:
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
spec:
template:
spec:
containers:
- name: redis
volumeMounts:
- mountPath: /data
name: data
volumes:
- name: data
persistentVolumeClaim:
claimName: data
ואנחנו רואים שזה לא קובץ Deployment מלא אלא רק החלק מהקובץ שמתעסק עם ה mount של ה volume החדש. מה שמאוד חשוב בקובץ זה שה metadata.name יהיה זהה לאותו deployment אותו רוצים לעדכן.
הקובץ האחרון בקומפוננטה הוא data-persistentvolumeclaim.yaml ותוכנו הוא:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
creationTimestamp: null
labels:
io.kompose.service: data
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
status: {}
זה כבר קובץ yaml מלא שיוצר PersistentVolumeClaim. בגלל שהוא בתוך הקומפוננטה ולא בתוך base, אז רק סביבות שישתמשו בקומפוננטה זו יפעילו אותו.
## תיקיית overlays/dev
הסביבה הראשונה היא dev והיא מכילה רק קובץ kustomization.yaml יחיד. זה התוכן שלו:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
והוא למעשה מציין סביבה זהה לגמרי ל base. בסביבת הפיתוח אין שום תוספת ושום שינוי.
## תיקיית overlays/production
סביבת הייצור זה כבר סיפור אחר כי היא צריכה להוסיף את הקומפוננטה שיצרנו. הנה הקובץ overlays/production/kustomization.yaml שמתאר את הסביבה:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
components:
- ../../components/datavolume
הפעם בנוסף ל resources יש לנו מפתח חדש בשם components שכולל מערך של כל הקומפוננטות ששייכות לסביבה. בגלל שכתבתי כאן את הקומפוננטה אז כשאבנה את ההגדרות לסביבת הפרודקשן הן יכללו את כל base וגם את הקומפוננטה.
## איך לבנות קבצי הגדרות לקלאסטר
אחרי שכל ה yaml-ים במקום אני יכול להריץ:
$ kubectl kustomize overlays/production
ולקבל למסך במכה אחת את כל הגדרות ה yaml של סביבת production. הרבה פעמים אנחנו לא נרצה אפילו לכתוב את ההגדרות האלה לקובץ אלא פשוט נשלח אותן ישר לקלאסטר באופן הבא:
$ kubectl kustomize overlays/production | kubectl apply -f -
או אם אתם מעדיפים להעלות את המערכת בגירסת פיתוח אז תצטרכו לעדכן את קונפיגורציית kubectl שלכם שיתחבר לקלאסטר הפיתוח ואז להפעיל:
$ kubectl kustomize overlays/dev | kubectl apply -f -
## איפה לומדים עוד
התיעוד של קוברנטס כולל כמה פרקים על kustomize אז אם אתם אוהבים לקרוא את התיעוד של קוברנטס זה יכול לשמח אתכם. הדף הרלוונטי הוא בקישור הזה:1 419
# מדריך: תמיכה במספר סביבות קוברנטס עם Kustomize
כשאנחנו בונים אפליקציה שצריכה לרוץ על קלאסטר קוברנטס הרבה פעמים נרצה להיות מסוגלים להריץ את האפליקציה על קלאסטרים שונים לסביבות שונות: קלאסטר לסביבת בדיקות, קלאסטר של סביבת פיתוח וכמובן קלאסטר הפרודקשן.
לפעמים הקלאסטרים האלה יהיו זהים במאפיינים שלהם אבל בדרך כלל האפליקציה תצטרך קצת להשתנות בין הקלאסטרים: לדוגמה אולי ג'וב מסוים צריך לרוץ רק בסביבת פרודקשן ולא בסביבת הפיתוח, מיפוי Volume מסוים ישתמש בתיקיה מקומית בסביבת פיתוח אבל באחסון רשת בסביבת הבדיקות או הפרודקשן וכך הלאה.
המנגנון המובנה ב kubectl לניהול שינויים בין סביבות נקרא kustomize. בפוסט זה אציג דוגמה פשוטה לפרויקט שמשתמש ב kustomize כדי לייצר קבצי yaml שונים לסביבות השונות.
## איך זה עובד
קסטומייז מחלק את קבצי ה yaml של קוברנטס ל-3 תיקיות מרכזיות:
1. תיקיית base שם נמצאים קבצי ה yaml ה"בסיסיים", לפני שהתאמנו אותם לסביבות מסוימות. זה בעצם החלק שמשותף לכל הסביבות. בתיקיה זו יהיה קובץ בשם kustomization.yaml שמהווה סוג של אינדקס לכל הקבצים בתיקיה.
2. תיקיית components, בתוכה אפשר ליצור תיקיה לכל "פיצ'ר" של הקלאסטר, ובתוך תיקיית הפיצ'ר יהיו כל קבצי ה yaml של אותו הפיצ'ר (או הקומפוננט).
3. תיקיית overlays שבתוכה תהיה תיקיה לכל סביבה. בתוך כל תיקיית סביבה יהיה קובץ kustomization.yaml שמכיל הוראות להתאמה של קבצי ה yaml מ base ומ components עבור סביבה זו.
זה אומר שאם יש לי פרויקט שבסביבת פרודקשן צריך איזה ג'וב מיוחד שלא קיים בסביבת פיתוח, אני יכול ליצור תת תיקיה בתוך components עם השם של הג'וב, בתוכה לשים את קובץ ה yaml המתאים לג'וב הזה, ואז בתוך
overlays/production/kustomization.yaml לבקש להוסיף את הקומפוננטה של הג'וב להגדרות הבניה בסביבת פרודקשן.
או אם אני צריך ליצור יותר replications בסביבת production, אז יהיה לי בקובץ overlays/production/kustomization.yaml הוראה שאומרת שצריך לשנות את שדה replication של אוביקט deployment מסוים לערך גבוה יותר.
## דוגמה - פרויקט שכולל מיפוי תיקיה רק בסביבת פרודקשן
נראה את זה דרך דוגמה פשוטה של סרביס רדיס שבסביבת פרודקשן מכיל מיפוי ווליום כדי שהמידע יישמר, אבל בסביבת פיתוח מגיע בלי המיפוי כדי שכל פעם שנפעיל את השרת נקבל מצב חדש ונקי.
מבנה התיקיות של הפרויקט נראה כך:
.
├── base
│ ├── kustomization.yaml
│ ├── redis-deployment.yaml
│ └── redis-service.yaml
├── components
│ └── datavolume
│ ├── data-persistentvolumeclaim.yaml
│ ├── kustomization.yaml
│ └── redis-deployment.yaml
├── docker-compose.yml
└── overlays
├── dev
│ └── kustomization.yaml
└── production
└── kustomization.yaml
6 directories, 9 files
תיקיית הבסיס כוללת את שני ה yaml-ים של redis שנדרשים בשביל להפעיל את השרת.
בתיקיית components יש לי תת-תיקיה שנקראת datavolume ובתוכה קובץ yaml שיוצר Volume ולידו קובץ yaml באותו שם של קובץ yaml בסיסי (נקרא redis-deployment.yaml) שיכלול רק את השינויים בקובץ ה deployment שיופעלו אם "קומפוננטה" זו משולבת בבניה.
ובתיקיית overlays יש לי תיקיות עבור כל סביבה dev ו production.
## תיקיית base
עכשיו נראה את הקבצים בכל תיקיה. תחילה ב base הקובץ הכי מעניין הוא kustomization.yaml שמהווה סוג של אינדקס על תיקיית הבסיס:
resources:
- redis-deployment.yaml
- redis-service.yaml
כל מה שיש בקובץ זה מפתח יחיד בשם resources שמכיל מערך של קבצי yaml ששייכים לפרויקט.
תוכן הקובץ redis-deployment.yaml הוא:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kompose.cmd: kompose convert -f ../docker-compose.yml
kompose.version: 1.25.0 (a70f80cc)
creationTimestamp: null
labels:
io.kompose.service: redis
name: redis
spec:
replicas: 1
selector:
matchLabels:
io.kompose.service: redis
strategy:
type: Recreate
template:
metadata:
annotations:
kompose.cmd: kompose convert -f ../docker-compose.yml
kompose.version: 1.25.0 (a70f80cc)
creationTimestamp: null
labels:
io.kompose.service: redis
spec:
containers:
- image: redis:6.2
name: redis
lifecycle:
postStart:
exec:1 419
בוקר טוב לכולם היום בשעה 10:00 וובינר מה חדש בפייתון 3.10 מוזמנים להצטרף בזום:
ynon@ynonperek.com is inviting you to a scheduled Zoom meeting.
Topic: מה חדש ב Python 3.10
Time: Dec 2, 2021 10:00 AM Jerusalem
Join Zoom Meeting
https://us06web.zoom.us/j/84743902303?pwd=UXpIWExqWnhlZ1gxT0xBTHdkMlprQT09
Meeting ID: 847 4390 2303
Passcode: 714926
One tap mobile
+16699006833,,84743902303#,,,,*714926# US (San Jose)
+12532158782,,84743902303#,,,,*714926# US (Tacoma)
Dial by your location
+1 669 900 6833 US (San Jose)
+1 253 215 8782 US (Tacoma)
+1 301 715 8592 US (Washington DC)
+1 312 626 6799 US (Chicago)
+1 346 248 7799 US (Houston)
+1 646 876 9923 US (New York)
Meeting ID: 847 4390 2303
Passcode: 714926
Find your local number: https://us06web.zoom.us/u/kciHlAHior
1 419
# מצאו את ההבדלים
שיטה פופולרית למדי למצוא בעיות מוזרות בקוד עובדת ככה- לוקחים קוד שלא עובד וקוד דומה שעובד ומתחילים לאט לאט להפוך אותם ליותר דומים. מסדרים אינדנטציה, מחליפים שמות של פונקציות ומשתנים או את סדר הקריאות עד שהקוד שלא עובד יהיה זהה לקוד שעובד.
איפשהו בדרך הקוד שלא עובד יתחיל לעבוד - וזה ייתן לנו רמז מה עשינו לא בסדר.
(בגיט יש מנגנון מובנה בשם git bisect שבנוי בדיוק על הרעיון הזה)
היתרון בשיטה הוא שבסוף אם נהיה מספיק מתודיים נגיע לקוד שעובד. החיסרון - לא תמיד קוד עובד הוא המטרה, וכשמחפשים הבדלים לפעמים קשה לראות למה גירסה אחת עובדת והשניה לא.
אז כן אם אני ממש תקוע אני אלך על משחק מצא את ההבדלים, אבל כמעט תמיד עדיף לרדת לעומק הבעיה ולהבין מה שבור על הקוד השבור, בלי קשר לגירסה שעובדת.
1 419
# חלומות על עננים
לפני פחות מעשרים שנה לשרתי יוניקס עדיין היתה נוכחות בחיים של מפתחי תוכנה. בחברות רבות היה "חדר שרתים" בו איחסנו שרתי יוניקס שלמים, וחברות אחרות היו שוכרות שרת שלם שישב באיזו חוות שרתים. אני אישית עוד זכיתי לעבוד על מכונת HP True 64 שיושבת בתוך ארון בחדר בהמשך המזדרון.
אפילו לפני עשר שנים עוד עבדתי בסטארט אפ ששמר שרת בארון באחד החדרים בחברה, למרות שזה כבר הריץ לינוקס.
טכנולוגיות וירטואליזציה הפכו את כל העסק של שרתים בחוות וארונות למיותר. נכון יש חברות גדולות שמתחזקות חוות שרתים ענקיות, אבל רובנו לא נזכה לבקר בהן. במקום זה אנחנו מקבלים שרת וירטואלי פרטי בעלות מזערית שעושה יותר ועובד הרבה יותר טוב מהמפלצת שישבה בארון.
וכמו שהצלחנו לנצח את שרתי היוניקס אני רוצה להאמין שגם מערכות הענן שיש היום יוחלפו יום אחד במערכות טובות יותר. אלה החלומות שלי לאיך טכנולוגיות ענן צריכות לעבוד, מוזמנים להוסיף חלומות שלכם בתגובות:
1. כל שירותי הענן צריכים לעבור סטנדרטיזציה. לא צריך 5 דרכים שונות לאחסן קובץ. אני חולם על ממשק אחד מבוסס CLI שיעבוד לכל העננים בצורה זהה.
2. צריכה להיות דרך קלה להקים ענן בבית. כמו שהיום יש לי אינסוף כלים להתקין "שרת לינוקס" בבית, כך הייתי רוצה להקים "ענן" ביתי שיתמוך בכל הממשק הסטנדרטי שדיברנו עליו.
3. המחיר של שירותי ענן חייב לרדת בצורה משמעותית. היום רובם נוקטים בגישה של מחיר כניסה נמוך כדי לפתות אותך פנימה ואז ככל שהשימוש עולה המחיר עולה בצורה לא פרופורציונאלית. אתם לא רוצים לדעת כמה עלה פעם שרת יוניקס מלא בארון.
4. אנחנו צריכים דרך לשתף spec שלם של מערכת על ענן. משהו כמו helm chart או docker compose עבור כל אותם אינסוף ממשקים של AWS או Azure או GCP או מה שלא תרצו.
כלי כמו cloudbridge נותן כיוון טוב לחיבור כזה בין עננים אבל תמיד כשמדובר בכלי חיצוני "שמתלבש" על תשתית קיימת השימוש בו הולך להיות פחות אופטימלי מאשר שימוש ישיר בתשתית הקיימת. במקרה של יוניקס - לינוקס לא בא בתור כלי שנותן ממשק אחיד לכל מכונות היוניקס השונות. הוא בא בתור מערכת הפעלה חדשה שעובדת יותר טוב, ויחד עם טכנולוגיות וירטואליזציה החליף את כל מה שהיה לפניו. זה סוג השינוי שהייתי רוצה לראות גם בעולם העננים.
ומה אתכם? איזה חלומות יש לכם על הענן?
1 419
# קיים בשפה
אחד השינויים הקטנים של פייתון 3.10 הוא מתן אפשרות ל zip לבדוק אם הקלט שקיבל הוא שווה באורך. אני אזכיר לטובת קוראים שלא הגיעו מפייתון, שהפונקציה זיפ מקבלת מספר "רשימות" ומייצרת רשימה אחת מאוחדת בה כל איבר הוא רשימה של איברים מכל רשימות הקלט. בטקסט אני יכול להבין שזה נשמע מסורבל אז ננסה בקוד. נניח שיש לכם את הרשימות:
a = [1, 2, 3]
b = ['a', 'b', 'c']
c = ['@', '!', '#']
אז הפעלה של zip על שלושת הרשימות תחזיר רשימה אחת של שלושה איברים שכל אחד מהם מורכב מאיבר אחד מכל אחת מהרשימות, כלומר:
>>> list(zip(a, b, c))
[(1, 'a', '@'), (2, 'b', '!'), (3, 'c', '#')]
וכן יש פה כוכבית שאומרת ש zip בעצם מחזיר איטרטור ולא רשימה, אבל נשים את ההבדל בין שני אלה בצד לעכשיו כי הוא לא רלוונטי לסיפור של הפוסט.
בקיצור הבעיה של זיפ היא מה עושים כשהרשימות לא באותו אורך. בפייתון החליטו לפני הרבה שנים שהפיתרון יהיה לקחת רק את החלק ברשימות שכן מתאים באורך. במילים אחרות אם הרשימה a תהיה ארוכה יותר מהשאר:
a = [1, 2, 3, 4, 5, 6]
b = ['a', 'b', 'c']
c = ['@', '!', '#']
אז התוצאה של ה zip תישאר בדיוק אותו דבר - והאיברים העודפים ב a פשוט לא יקבלו טיפול.
עכשיו תשמעו מה היה לבראנט בושר להגיד על זה:
> It is clear from the author's personal experience and a survey of the standard library that much (if not most) zip usage involves iterables that must be of equal length.
ובהמשך אותו מסמך:
> In fact, the author has counted dozens of other call sites in Python's standard library and tooling where it would be appropriate to enable this new feature immediately.
במילים פשוטות מספר לנו בראנט שבפייתון, לפחות בספריה הסטנדרטית, מתכנתים משתמשים בזיפ כשהם חושבים שהקלט שלהם הוא באותו האורך, אבל לא מוודאים לפני זה בבדיקה נוספת שזה באמת המצב. ואם זה המצב בספריה הסטנדרטית אפשר להניח שזו תופעה גלובאלית.
ופה יש שני דברים שצריך לשים לב אליהם:
1. כל אחד היה יכול לעטוף את zip בפונקציה חדשה (נקרא לה safezip) שבודקת שאכן כל רשימות הקלט באותו האורך. מעטים אם בכלל עשו את זה.
2. (וזה ניחוש שלי) - אחרי התוספת ל zip בפייתון 3.10, הרבה אנשים יתחילו להעביר את הפרמטר החדש ואפילו יסתכלו מוזר על אלה שלא מעבירים אותו.
קוד הוא דבר מדבק. לא צריך לחכות לבראנט כדי לכתוב את safezip, ובאופן כללי, כשאתם בונים את המערכת שלכם - שווה להשקיע בכתיבת פונקציות עזר ומסגרת לקוד שתעזור למתכנתים האחרים בצוות להשתמש ב Best Practices, גם אם אותם Best Practices עדיין לא נכנסו רשמית לשפה או לטכנולוגיה שלכם.1 419
# שלוש דוגמאות ל Structural Pattern Matching ב Python 3.10
לקראת הוובינר ביום חמישי הקרוב בו נדבר לעומק על פייתון 3.10 ופיצ'ר ה Structural Pattern Matching שלו, חשבתי לאמ;לק את הסיפור עם שלוש דוגמאות קצרות שמראות איך זה עובד ולמה זה טוב.
## טיפול במחרוזות ומספרים במכה אחת
בגיזרת התחביר החדש של הפיצ'ר יש לנו את המילה match והמילה case. אחרי match אנחנו כותבים שם של משתנה, ואז נקודותיים, ואז מתחילים לכתוב בלוקים של case. אחרי המילה case מגיע משהו שנקרא תבנית. יש הרבה סוגים של תבניות וה"התאמה" בין הקלט לתבנית היא שקובעת איזה בלוק case יבוצע. אפשר לחשוב על Pattern Matching כמו בלוק if, elif, elif, else ענק - רק בתחביר קצת יותר פשוט.
בשביל הדוגמה הראשונה נכתוב פונקציה שמקבלת "משהו" שיכול להיות מספר או מחרוזת ומדפיסה חזרה הודעה שונה לכל סוג נתונים שהתקבל. בגירסה בלי pattern matching זה היה יכול להיראות כך:
import numbers
def go(x: str|float):
if isinstance(x, str):
print(f"Welcome, {x}")
elif isinstance(x, numbers.Number):
print(f"I see you have {x} apples")
go(10)
go("ynon")
go("bye bye")
go(2.5)
ועם Pattern Match אני יכול להיפרד מהקריאות ל isinstance ולקבל את הגירסה הבאה:
import numbers
def go(x: str|float):
match x:
case str():
print(f"Welcome, {x}")
case int()|float():
print(f"I see you have {x} apples")
go(10)
go("ynon")
go("bye bye")
go(2.5)
ואגב הסוגריים העגולים אחרי הטיפוס הם מדליקים כי אפשר לרשום בתוכם מספר או מחרוזת, ואז התנאי יתאים רק אם הדבר שקיבלנו זהה בדיוק למה שבסוגריים העגולים. הקטע הבא למשל מזהה מילת קוד מיוחדת בקלט ומדפיס הודעה מיוחדת:
import numbers
def go(x: str|float):
match x:
case str("secretcode"):
print("Bravo! You Won!")
case str():
print(f"Welcome, {x}")
case int()|float():
print(f"I see you have {x} apples")
go(10)
go("ynon")
go("bye bye")
go('secretcode')
go(2.5)
## ביצוע פקודות במבנה לא ידוע
אבל מה שבאמת נחמד ב case זה שאפשר לכתוב שם כל מיני דברים בתור תבניות, וכל תבנית עושה משהו קצת אחר. בדוגמה הקודמת התבניות היו המילים int ו str שתורגמו ל isinstance. בדוגמה הבאה אני משתמש בסוגריים מרובעים בתור תבנית כדי לבדוק שרשימה מסוימת מגיעה במבנה מסוים.
המשימה בחלק הזה היא לבנות תוכנית שמקבלת פקודות ופרמטרים לפקודות ומבצעת אותן, למשל הפקודה add תוסיף את המספר שקיבלה כפרמטר לאיזה מונה גלובאלי והפקודה print תדפיס את אותו מונה:
accumulator = 0
def process(line: str):
global accumulator
match line.split():
case ["add", n]:
accumulator += float(n)
case ["print"]:
print(accumulator)
case _:
print(f"Unknown command: {line}")
process("add 10")
process("add 20")
process("print")
process("add -30")
process("add 5")
process("print")
process("x")
שימו לב ל case האחרון עם הקו התחתי. קו תחתי מסמל תבנית Catch All שתתאים לכל מה שתתנו לה, שזה למעשה בלוק else של הקוד.
## שימוש ב Guards
לפעמים אין לנו דרך לכתוב בדיוק את התבנית שאנחנו רוצים ובשביל זה אפשר לשלב קוד פייתון כללי שיוכל להסתכל על התאמה לתבנית ולדייק אותה. הקוד הבא הוא מימוש פשוט לפונקציית ערך מוחלט של מספר:
def myabs(n: int):
match n:
case int() if n > 0:
return n
case int() if n < 0:
return n * -1
case _:
raise Exception(f"Invalid Input {n}")
print(myabs(10))
print(myabs(-8))
print(myabs("foo"))
## מה הלאה
כמו כל פיצ'ר חדש הכי טוב להתחיל להשתמש בו בתוכניות שלכם כדי להתרגל אליו ולהחליט מה דעתכם עליו. לאורך הזמן אתם תגלו כמה דברים שתאהבו וגם לא מעט דברים שיפתיעו אתכם.
שווה גם לקרוא את ה Tutorial שמצורף לפיצ'ר בקישור:
https://www.python.org/dev/peps/pep-0636/
ואחרי שתרגישו יותר בנוח עם הפיצ'ר מומלץ לקרוא את ה PEP המלא בקישור:
https://www.python.org/dev/peps/pep-0634/1 419
# עיקרון הרצף
העיקרון בפשטות אומר שיותר קל לעשות דבר שעשית אתמול מאשר דבר חדש. הנה כמה דרכים שאנחנו רואים אותו בפעולה:
1. כשאני כותב פוסט האימקס שלי כבר פתוח על הקובץ הנכון בגופן הנכון. בלחיצת כפתור הוא נשלח לאתר כדי להיכנס ל Pipeline של הפרסום. הכל פשוט עובד.
2. הרבה יותר קל לכתוב פיצ'ר נוסף לפרויקט קיים מאשר לעשות את החצי שעה הראשונה של פרויקט חדש. אם זה רק פיצ'ר אנחנו כבר יודעים באיזה ספריות להשתמש, איך לבנות את הפרויקט, וכן כל הקוד כבר פתוח בתוך העורך.
3. הרבה יותר קל להמשיך פעילות גופנית כשאתה בכושר מאשר לקום מהכורסה אחרי חצי שנה בלי פעילות.
וגם בלימודים- אם אתה כבר בתוך תהליך לימוד ויש לך חצי שעה פנויה תמיד יהיה איזה תרגיל ששווה לנסות, או איזה וידאו לראות או טקסט לקרוא. יש אינסוף דברים. אבל אם אתה לא בתהליך לימוד ויש לך חצי שעה פנויה, הסיכוי שהיא תושקע ב"התחלת" תהליך לימודי הוא מאוד נמוך. רצף של לימוד פותח את האפשרות להשתמש בזמנים קטנים מתים כדי להתקדם בו וכך יוצר לנו עוד זמן.
התוצאה של "עשר דקות ביום" היא הרבה יותר משמעותית מ 70 דקות מרוכזות ביום אחד. עשר דקות ביום יוצרות רצף, ואם נספור את זמן הלימוד אחרי שבוע נגלה שעברנו בקלות את ה 70 ואפילו את ה 140 דקות. רצפים עוזרים לנו לנצל את הזמן טוב יותר ועל דברים שחשובים לנו. שווה להתאמץ כדי ליצור אותם.
1 419
# פייתון ומפתחות בלי מרכאות
באג מעניין בתוכנית שלי הזכיר לי לפני כמה ימים למה חשוב להשתמש ב Type Checking בפייתון. הקוד שלי אחרי ניקוי נראה משהו כזה:
filters = [
{ id: 12, filter: None },
{ id: 15, filter: None },
{ id: 16, filter: lambda x: x > 7 }
]
def run_filter(filter_id, data):
fn = next(x for x in filters if x.get("id") == filter_id)["filter"]
return [x for x in data if fn(x)]
print(run_filter(16, [2, 5, 9, 11, 20]))
במקום להפעיל את פילטר מספר 16 הקוד מתרסק עם השגיאה הבאה:
Traceback (most recent call last):
File "/Users/ynonp/tmp/blog/c.py", line 11, in <module>
print(run_filter(16, [2, 5, 9, 11, 20]))
File "/Users/ynonp/tmp/blog/c.py", line 8, in run_filter
fn = next(x for x in filters if x.get("id") == filter_id)["filter"]
StopIteration
שאומרת ש next נכשל כי הוא לא מצא אף פילטר שמתאים לתנאי. זה מוזר כי אני די בטוח שהוגדר פילטר שה id שלו הוא 16. כרגע הקוד לא כולל Type Hints ולכן גם mypy לא מתלונן.
נוסיף Type Hints ונראה אם זה יעזור לקבל יותר מידע:
from collections.abc import Callable
from typing import TypedDict
class Filter(TypedDict):
id: int
filter: Callable[[int], bool]|None
filters: list[Filter] = [
{ id: 12, filter: None },
{ id: 15, filter: None },
{ id: 16, filter: lambda x: x > 7 }
]
def run_filter(filter_id, data):
fn = next(x for x in filters if x.get("id") == filter_id)["filter"]
return [x for x in data if fn(x)]
print(run_filter(16, [2, 5, 9, 11, 20]))
התוכנית עדיין מתרסקת אבל עכשיו לפחות mypy מצליח למצוא בעיות:
$ mypy b.py
b.py:9: error: Expected TypedDict key to be string literal
b.py:10: error: Expected TypedDict key to be string literal
b.py:11: error: Expected TypedDict key to be string literal
Found 3 errors in 1 file (checked 1 source file)
מבט ממוקד יותר בשורה 9 חושף את הבעיה:
{ id: 12, filter: None },
שכחתי לכתוב מרכאות מסביב למפתחות id ו filter. פייתון ביום רגיל היה כועס על מפתחות במילון בלי מרכאות, אבל בגלל שהמילים id ו filter הן פונקציות מובנות בשפה הוא לא התרגש והשתמש בפונקציה בתור מפתח. אם היינו מוסיפים שורת הדפסה היינו מגלים שהמילון נראה בכלל ככה:
[{<built-in function id>: 12, <class 'filter'>: None}, {<built-in function id>: 15, <class 'filter'>: None}, {<built-in function id>: 16, <class 'filter'>: <function <lambda> at 0x1032b3d90>}]
וכמו תמיד כשמבינים את הבעיה התיקון הוא החלק הקל:
from collections.abc import Callable
from typing import TypedDict
class Filter(TypedDict):
id: int
filter: Callable[[int], bool]|None
filters: list[Filter] = [
{ "id": 12, "filter": None },
{ "id": 15, "filter": None },
{ "id": 16, "filter": lambda x: x > 7 }
]
def run_filter(filter_id, data):
fn = next(x for x in filters if x["id"] == filter_id)["filter"]
return [x for x in data if fn(x)]
print(run_filter(16, [2, 5, 9, 11, 20]))
הגירסה האחרונה כבר מצליחה להדפיס את כל הערכים שעוברים את הפילטר, ובנוסף mypy לא מוצא יותר שגיאות.
אז נכון צריך לא מעט מזל רע בשביל ליפול בדיוק על הפונקציות המובנות בשפה במילונים שלכם, ודווקא בגלל זה שווה להתרגל לכתוב Type Hints בכל הזדמנות.
现已上线!2025 年 Telegram 研究 — 年度关键洞察 
