שלום אורח התחבר

הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

מעדיפים לקרוא מהטלגרם? בקרו אותנו ב:@tocodeil

או הזינו את כתובת המייל וקבלו את הפוסט היומי בכל בוקר אליכם לתיבה:

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

אם ניקח את Python כדוגמא נוכל לדמיין את הקוד הבא שמגדיר שני מימושים לאותה פונקציה:

@dispatch(tuple)
def go(point):
    x, y = point
    go(x, y)

@dispatch(int, int)
def go(x, y):
    print(f'x = {x}, y = {y}')

כאשר כל אחד מהמימושים אחראי על סט פרמטרים אחר. נרצה שהראשון ייקרא אם נפעיל את הפונקציה עם פרמטר יחיד:

loc = (10, 10)
go(loc)

ונרצה שהשני ייקרא כשנפעיל את הפונקציה עם שני פרמטרים:

go(10, 10)

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

{
    go: {
        (typle): <function at 0x1088b01e0>
        (int, int): <function at 0x1089d1488>
    }
}

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

מימוש פשוט של המנגנון יכול להיראות כך:

import collections

md = collections.defaultdict(dict)

def dispatch(*types):
    def decorator(f):
        md[f.__name__][types] = f
        def wrapper(*args, **kwargs):
            res = md[f.__name__][tuple(type(a) for a in args)]
            res(*args, **kwargs)

        return wrapper
    return decorator

ואם אתם בעניין של מימוש יותר רציני שגם נבדק בעולם האמיתי וכולל די הרבה אופטימיזציות שווה לבדוק את הספריה multipledispatch. זה דף הבית שלהם:

https://github.com/mrocklin/multipledispatch

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

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

מתקינים את הספריה עם:

$ pip install dulwich --global-option="--pure"

ומתוך תוכנית פייתון נוכל עכשיו לכתוב:

from dulwich.repo import Repo
r = Repo('.')
last_commit_id = r.head().decode('ascii')

result_filename = f'result.{last_commit_id}.txt'

with open(result_filename, 'w') as f:
    f.write('Hello World\n')

כדי לקבל את קובץ התוצאות עם מזהה הקומיט האחרון.

פייתון הפתיע אותי היום עם הודעת השגיאה הבאה:

Traceback (most recent call last):
  File "...", line 31, in search
    if is_text_in_file(file, word):
  File "", line 10, in is_text_in_file
    for line in fileinput.input(file):
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/fileinput.py", line 93, in input
    raise RuntimeError("input() already active")
RuntimeError: input() already active

וזאת הפונקציה שגרמה לבעיה:

def is_text_in_file(file, text):
    for line in fileinput.input(file):
        if text in line:
            return True

    return False

הפונקציה עובדת לא רע בפעם הראשונה שקוראים לה, אבל בגלל ה return באמצע לולאת ה fileinput, ברגע שמחזירים True בפעם הראשונה אי אפשר יותר לקרוא לה. הפונקציה יוצאת לפני שסגרה את הקובץ.

כיוון אחד לתקן את זה הוא להוסיף קריאה יזומה ל fileinput.close לפני ה return. וזה עובד:

def is_text_in_file(file, text):
    for line in fileinput.input(file):
        if text in line:
            fileinput.close()
            return True

    return False

אבל בינינו למה להתאמץ כשאפשר לתת ל Python לעבוד בשבילנו? כיוון הרבה יותר טוב יהיה לעטוף את כל הבלוק שמשתמש ב fileinput בפקודת with:

def is_text_in_file(file, text):
    with fileinput.input(file) as f:
        for line in f:
            if text in line:
                return True

    return False

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

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

במהלך שנה של תחרות התקבלו 24 הצעות ואחרי סקירה ובדיקה של כולן הוכרז ב 2015 הזוכה, אלגוריתם בשם Argon2.

לארגון2, כמו לאלגוריתמי Password Hashing מקבילים יש שתי מטרות:

  1. למנוע אפשרות מתוקפים להכין מראש "בסיס נתונים" של כל הסיסמאות האפשריות (נקרא חישוב מקדים), ובעצם תוקף יכול להתחיל לנסות לפרוץ סיסמאות רק אחרי שקיבל לידיו בסיס נתונים עם סיסמאות שמוצפנות ב Argon2.

  2. להפוך את החיים קשים לאותו תוקף גם אחרי שקיבל את בסיס הנתונים, באמצעות מנגנון שמכריח את התוקף להשתמש במשאבים רבים כדי לחשב את ה Hash של כל מילה וכך מאט מאוד מתקפות מסוג Brute Force ו Dictionary Attacks.

נלך לראות איך להשתמש ב Argon2 כדי לשמור סיסמאות בבסיס הנתונים בשפת Python. דרך טובה לגשת ל Argon2 מתוך פייתון היא הספריה passlib. נתקין את הספריה:

pip install passlib argon2_cffi

ספריית Argon2 נותנת שתי פונקציות מעניינות: הפונקציה hash מקבלת סיסמא שהמשתמש בחר ומקודדת אותה למחרוזת שאפשר לשמור בבסיס הנתונים, והפונקציה verify מקבלת סיסמא ומחרוזת מקודדת ותגיד לנו האם הסיסמא תואמת למחרוזת המקודדת. בתוכנית גדולה כשמשתמש בוחר סיסמא אנחנו נקודד את הסיסמא ואז נשמור את ה Argon2 שלה בבסיס הנתונים, וכשמשתמש ינסה להתחבר אנחנו ניתן את הסיסמא שהמשתמש הכניס ל verify כדי לזהות אם היא זהה למה ששמור אצלנו בבסיס הנתונים.

נראה איך זה עובד בתוכנית קטנה:

from passlib.hash import argon2
h = argon2.hash("ninja")
print(h)

# prints: $argon2i$v=19$m=102400,t=2,p=8$SkkpRci5tzZGaG0thbB2bg$OzPDd59KfGJbfqg4SsJLYw

# prints True
print(argon2.verify('ninja', h))

# prints False
argon2.verify('demo', h)

ושימו לב מה קורה כשאני מנסה להפעיל Argon2 כמה פעמים על אותה מחרוזת:

>>> argon2.hash("ninja")
'$argon2i$v=19$m=102400,t=2,p=8$6F3rHeN8790b4/yfMyYEwA$4aFNx5iWvsAt0u8U2Mdm6g'
>>> argon2.hash("ninja")
'$argon2i$v=19$m=102400,t=2,p=8$nhMCQMiZ0/ofA2BM6Z1TKg$WincHSs3Afkgqp+qn0Emdw'
>>> argon2.hash("ninja")
'$argon2i$v=19$m=102400,t=2,p=8$8D6nVArh/B9DaI3x/r83Jg$jZSKc5iwdFAVF8RN8G8xtg'

כל הפעלה יצרה תוצאה קצת שונה. הסיבה היא מנגנון ה Salt שמוטמע ב Argon2. מנגנון זה הוא שמונע מתוקפים לנסות לייצר את כל ערכי ה Hash האפשריים ממילון או ב Brute Force מבעוד מועד. רק אחרי שתוקף הצליח לקחת את כל בסיס הנתונים שלכם הוא יודע מה ה Salt של כל סיסמא ויכול לנסות לפרוץ את הסיסמאות אחת אחרי השניה. בזכות המבנה של Argon2, ביצוע מתקפת Brute Force על הסיסמאות ייקח הרבה מאוד זמן.

ארגון2 היא ספריה חופשית וזמינה להרבה מאוד שפות כך שגם אם אתם חיים מחוץ לעולם של פייתון כדאי להכיר ולהשתמש בה.

אני מוכן לנחש שהלולאות הראשונות שלמדתם היו for ו while. ובכנות זה מספיק בשביל לכתוב תוכניות שמחשבים יוכלו להבין, אבל בתור בני אדם אנחנו אוהבים שהקוד שלנו מספר קצת יותר דברים על הכוונה שמאחורי ההוראות.

בשביל שנוכל להעביר את המטרה של הקוד בצורה מדויקת יותר שפות תכנות כוללות רשימה ארוכה מאוד של פקודות לולאה, כל אחת עם הניואנסים המיוחדים שלה. בפייתון אנחנו אוהבים לדבר על List Comprehension בתור דוגמא ללולאות ייחודיות, אבל השפה לא עוצרת שם. הפונקציות any ו all יעזרו לנו לזהות האם רשימה מסוימת כוללת פריט שאנחנו מחפשים או האם כל הפריטים ברשימה מתאימים לתנאי מסוים.

נתחיל עם any. בפייתון אתם יכולים לרוץ על סידרה עם לולאת for כדי לחפש אלמנטים שמתאימים למבנה מסוים, למשל הקוד הבא בודק האם במילון מסוים יש מפתח שהערך שלו הוא 2:

c = { 'one': 1, 'two': 2, 'three': 3, 'four': 4 }

found = False
for k, v in c.items():
    if v == 2:
        found = True
        break

if found:
    print("Found a 2 in the dictionary")

אבל הרבה יותר מדויק לכתוב את אותו הקוד עם הפונקציה any. פונקציה זו מקבלת סידרה ובודקת אם אחד האלמנטים הוא True. באמצעות Generator Comprehension נוכל להפוך כל מילון לסידרה של בוליאנים וכך לקבל את הגירסא הקריאה יותר:

c = { 'one': 1, 'two': 2, 'three': 3, 'four': 4 }

if any(v == 2 for k, v in c.items()):
    print("Found a 2 in the dictionary")

פונקציה נוספת מהסידרה היא all שבודקת האם כל האיברים בסידרה הם True. לכן הקוד הבא הוא דרך קלה לוודא שכל הערכים במילון הם מספרים זוגיים:

c = { 'one': 1, 'two': 2, 'three': 3, 'four': 4 }
d = { 'two': 2, 'four': 4 }

if all(v % 2 == 0 for k, v in c.items()):
    print("All values in c are even numbers")

if all(v % 2 == 0 for k, v in d.items()):
    print("All values in d are even numbers")

שימו לב לשימוש ב Generator Comprehension ולא ב List Comprehension, כלומר סוגריים עגולים ולא מרובעים. הבחירה ב Generator Comprehension היא שגורמת לקוד להתנהג כמו לולאת for, כלומר שהערכים ייבדקו אחד-אחד ולא ייטענו כולם לזיכרון. זה חסכוני יותר בזיכרון ובמקרה של any גם ירוץ מהר יותר אם התשובה היא True.

לקראת הוובינר של יום חמישי ולטובת כותבי הבדיקות האוטומטיות שבקהל הקוראים אני מדביק כאן את הקיצורים והרעיונות המרכזיים שאנחנו משתמשים בהם בעת פיתוח בדיקות אוטומטיות ב Python ו Selenium ליישום Web. אם אתם באזור בסוף הפוסט יש לינק לפרטים על הוובינר - מוזמנים בחום להצטרף ולראות את כל הרעיונות האלה בשימוש בכתיבת בדיקות לאתר אמיתי.

המשך קריאה...

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

בואו נראה מתי זה טוב ואיך בונים את זה בפייתון.

המשך קריאה...

מחר אעביר כאן וובינר על Design Patterns בפייתון ואחת התבניות שאדבר עליה היא Singleton. בוובינר אני רוצה יותר להתמקד במשמעות ובשימושיות של כל תבנית ולכן כאן בפוסט אנסה לחקור יותר לעומק את האפשרויות השונות לבנייתה.

המשך קריאה...

שמעתי על גיט לראשונה ב 2009 כשאחד החברים לעבודה נכנס למשרד בפנים קורנות לספר שהוא ראה את האור, והאור הזה מתחיל בגימל. אני לא אחד שעף על טכנולוגיות חדשות אז אמרתי לו שנדבר עוד שבועיים כשיעבור לו. אני נשאר עם SVN.

תשע שנים אחרי ועדיין לא עבר לו, וגם אני כבר מזמן מכור.

גיט עשתה שינוי משמעותי קודם כל בזמינות של כלי ניהול גירסאות. לפני שגיט היתה פופולרית מתכנתים רבים הסתבכו עם התקנה של SVN או CVS והעדיפו לוותר על ניהול גירסאות לחלוטין בפרויקטים קטנים. בהיותה מערכת מבוזרת וקלה להתקנה גיט איפשרה לכל אחד לשלב מנגנון ניהול גירסאות גם בפרויקטים הקטנים ביותר.

אבל את היופי האמיתי של גיט מוצאים בפרויקטים גדולים: בזכות היותה מערכת מבוזרת היא מאפשרת לאלפי מתכנתים ברחבי העולם לשתף פעולה בפרויקטים גם בלי ניהול מרכזי. כיום כל עולם הקוד הפתוח כבר מתנהל בגיט וגם פיתוחים רבים בקוד סגור עוברים לשם.

מתכנתים רבים מגיעים ללמוד גיט ומחפשים איך לבצע בגיט את ה-3 פקודות שהם מכירים ממערכת ניהול הגירסאות הקודמת שלהם או דרך Tutorials של "למד את עצמך גיט בעשרים דקות". זה עובד אבל רק חלקית. כשאתם לומדים תוך כדי עבודה ומדלגים על הבסיס התיאורטי וה Best Practices יהיו דברים חשובים שתחמיצו ויהיו דברים שוליים שתקחו יותר מדי ללב.

בוובינר שהעברתי לא מזמן על גיט עלו מספר שאלות מרתקות שהזכירו לי כמה חשובה ההבנה של הכלי בשביל להבין את הפקודות ומתי להשתמש בכל אחת.

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

התוצאה היא קורס גיט מקיף שמתחיל מההתחלה ובונה לאט את ההיכרות עם הפקודות השונות והיכולות השונות של הכלי. זה הקורס שאם הייתי מתחיל ללמוד גיט ממנו היו נחסכות לי הרבה טעויות והרבה תיסכולים.

אני בטוח שתהנו מהקורס ומקווה שתבחרו להצטרף. אם אתם מנויים לאתר יכולים כבר עכשיו להיכנס ולראות את התכנים. אם לא יכולים להצטרף כמנויים או לרכוש רק קורס זה בתשלום חד-פעמי. זה הקישור לקורס עם הסילבוס המלא וכפתור הצטרפות:

https://www.tocode.co.il/bundles/git

נתראה שם, ינון

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

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

המשך קריאה...