6 min. read

פרק קודם:

פייתון 20 - פונקציות קישוט חלק א

מה נלמד

  • שימושים נפוצים בפייתון
    • העברת פרמטר ל-Decorator
    • functools.wraps
    • ABC - Abstract Base Class
    • AbstractMethod
    • Property

שימושים נפוצים בפייתון

בספריות פייתון קיימים שימושים שונים ל-Decorators.
נבין כמה מהשימושים הנפוצים.

העברת פרמטרים לפונקציות שהן בעצמן Decorator

מה שאנחנו רוצים להשיג זה להעביר פרמטר ל-Decorator.
למשל:

1
2
3
@CallTimes(Times = 2)
def SomeFunk():
pass

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import functools

def GuardAndBlame(developerName=''):
def actual_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except:
print(f'Func {func.__name__} Failed! Blame : {developerName}')
return wrapper
return actual_decorator

@GuardAndBlame(developerName = 'Bob')
def SomeFunkyFunc(a,b,c):
print("Funk Funk and only Funk")
raise Exception('Oops')


SomeFunkyFunc(1,2,3)

קודם כל הדבר הראשון שצריך לשים לב זה שיש לנו פונקציה פנימית נוספת שלה בעצמה יש Decorator!
הפונקציה wrapper מקבלת את הקישוט wraps.
wraps מקבלת כפרמטר את הפונקציה ש-actual_decorator מקבלת.
ז”א func היא הפונקציה המקורית שלנו - SomeFunkyFunc.

השורה:

1
return wrapper

בעצם מחזירה פונקציה אחרת, היא מחזירה את - wraps אשר עוטפת את wrapper.
ניתן להבין זאת בדיאגרמה הבאה:

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

1
def GuardAndBlame(developerName=''):

הפונקציה actual_decorator היא הפונקציה המקורית שהיינו כותבים - היא הראשונה שמקבלת את func כפרמטר.

1
def actual_decorator(func):

ה-Decorator האמיתי הוא בגוף הפונקציה וממומש בעזרת wraps.

1
2
3
4
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)

כדי להבין את זה יותר טוב ניתן לקרוא באנגלית:

wrapper wraps func

ו-wraps כבר מממש לנו את כל הלוגיקה של ה-Decorator שאתם מכירים מהפרק הקודם.

הנה החלק הקשה יותר להבנה, הפונקציה הזו נקראת ומחזירה פונקציה שישר קוראים לה.
השורת קוד הזו:

1
@GuardAndBlame(developerName = 'Bob')

מתורגמת ל:

1
SomeFunkyFunc = GuardAndBlame('Bob')(SomeFunkyFunc)

זה למה הפונקציה actual_decorator היא בעצם הדקורטור האמיתי שמקבלת את -SomeFunkyFunc.

למה functools.wraps?

אחת הבעיות שיש לנו עם Decoratorים זה שהם מאבדים את המידע על הפונקציה המקורית.

למשל אם אני לא אשים את ה-wraps:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import functools

def GuardAndBlame(developerName=''):
def actual_decorator(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except:
print(f'Func {func.__name__} Failed! Blame : {developerName}')
return wrapper
return actual_decorator

@GuardAndBlame('Bob')
def SomeFunkyFunc(a,b,c):
print("Funk Funk and only Funk")
raise Exception('Oops')

SomeFunkyFunc(1,2,3)

print(SomeFunkyFunc.__name__)

במקרה הזה מה שיודפס בסוף זה:

1
2
3
Funk Funk and only Funk
Func SomeFunkyFunc Failed! Blame : Bob
wrapper

ועם השורה הזו:

1
2
@functools.wraps(func)
def wrapper(*args, **kwargs):

מודפסת שם הפונקציה המקורית:

1
2
3
Funk Funk and only Funk  
Func SomeFunkyFunc Failed! Blame : Bob
SomeFunkyFunc

תרגיל

ממשו את ה-Decorator הבא:

1
2
3
@CallTimes(Times = 2)
def SomeFunk():
pass

הוא ייקרא לפונקציה כמספר הפעמים שהמשתמש הביא לו בפרמטר Times.



Abstract Base Classes

מחלקה אבסטרקטית היא מחלקה שלא ניתן ליצור משתנה חדש שלה.

בפסודו קוד שדומה לפייתון נכתוב:

1
2
3
4
5
6
7
8
9
10
abstract class A:
pass
class B(A):
pass

# זה לא אפשרי!
a = A()

# אפשרי כי זו מחלקה לא אבסטרקטית
b = B()

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

איך נעשה זאת בפייתון?
abc בא להציל אותנו!

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

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

המחלקה ABC

כדי לסמן מחלקה כאבסטרקטית צריך לרשת מ-ABC:
*שימו לב לאותיות גדולות וקטנות - המודול נקרא abc והמחלקה האבסטרקטית היא ABC.

1
2
3
4
5
6
import abc

class Provider(abc.ABC):
pass

a = Provider()

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

@abstractmethod

ה-Decorator abstractmethod מתייג את הפונקציה כמתודה אבסטרקטית שהמחלקה המממשת צריכה לממש גם כן!
נסו להוריד את ההערה ולהריץ את הקוד עם מופע חדש של Provider.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import abc

class Provider(abc.ABC):
@abc.abstractmethod
def Get(self)->int:
pass

class SixProvider(abc.ABC):
def Get(self)->int:
return 6

# basic = Provider()
a = SixProvider()
print(a.Get())

@property

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

1
2
a.X = 2
print(a.X)

דרך ראשונה לממש מאפיין

לשים ערך ציבורי מהמחלקה

1
2
3
4
5
6
7
8
class A:
def __init__(self):
self.X = 1

a = A()
a.X = 2

print(a.X)

דרך שנייה לממש מאפיין

פונקציות getXXX ו-setXXX.

1
2
3
4
5
6
7
8
9
10
class A:
def getX(self):
return self.X
def setX(self, value):
self.X = value

a = A()
a.setX(2)

print(a.getX())

דרך שלישית לממש מאפיין

שימוש ב-@property

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from abc import ABC

class C(ABC):
@property
def X(self):
return self.mX

@X.setter
def X(self, val):
self.mX=val

c = C()

c.X = 6
a = c.X

print(c.X)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from abc import ABC

class C(ABC):
@property
def X(self):
return self.mX

@X.setter
def X(self, val):
print(val)
self.mX= val * 2

c = C()

c.X = 6
a = c.X

print(c.X)

תרגיל

1.
בבלנדר יש סכין שניתנת להחלפה.
ממשו תכנית קטנה שמאפשרת למחלקת בלנדר להחליף את הסכין שהבלנדר חותך בעזרתו.
על מחלקת הסכין גם להגדיר מאפיין abstract Get-Only property שמחזיר במספר מ1-10 כמה חדה הסכין.

  1. כתבו מחלקה אבסטרקטית ל”צורה”.
    למחלקה תהיה מתודה לחישוב השטח.

ממשו מחלקות נוספות המממשות את “צורה”:

  • ריבוע
  • מלבן
  • עיגול
  • משולש


השימושים שלנו ב-Decoratorים עוד לא הסתיימו.
בפרק הבא נראה שני פיצ’רים שהופכים את החיים להרבה יותר נוחים.
DataClass & Enum!

פייתון 22 - פונקציות קישוט חלק ג

אהבתם? מוזמנים להביע תמיכה כאן: כוס קפה