פייתון/פייתון גרסה 2/מחלקות/זמני2

מתוך ויקיספר, אוסף הספרים והמדריכים החופשי

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

הצורך במחלקות[עריכה]

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

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

מהי מחלקה?[עריכה]

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

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

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

עבודה עם מחלקות - גרסה ראשונית[עריכה]

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

הגדרת מחלקה[עריכה]

מגדירים מחלקה בצורה הבאה:

class <name>
	<class_body>

כאשר name הוא שם המחלקה, וclass_definition היא גוף המחלקה.

נתחיל במחלקה בעלת גוף ריק, שאותה נגדיר כך:

class item
	pass

הגדרת המחלקה הנ"ל מודיעה שיש כעת טיפוס חדש, ששמו item. גוף המחלקה ריק (זו משמעות המילה השמורה pass).

עצמים מהטיפוס החדש[עריכה]

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

shoko = item()

שדות המחלקה[עריכה]

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

לדוגמה, נניח שshoko הוא משתנה מסוג item. כדי לקבוע את מחירו ל12.90, נכתוב:

shoko.price = 12.90

כדי לקבוע את שמו כ"shoko", נכתוב:

shoko.name = ,shoko,

כדי להדפיס את שמו ואת מחירו, נכתוב:

print "The price of %s is %f" % (shoko.name, shoko.price)


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

def make_item(catalog_number, price, name, num):
	itm = item()
	
	itm.catalog_number = catalog_number
	itm.price = price
	itm.name = name
	itm.num = num
	
	return itm

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

כדי לייצר עצם חדש, נוכל לבצע את הקריאה הבאה, לדוגמה:

shoko = make_item(23, 19.90, 'shoko', 100)


דוגמאות ביניים[עריכה]

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

עדכון מחיר פריט[עריכה]

הפונקציה הראשונה מקבלת סוג פריט ותוספת מחיר, ומעלה את מחיר סוג הפריט בתוספת:

def raise_price(itm, amount):
	itm.price += amount

עדכון מספר איברי פריט[עריכה]

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

def update_num(itm, how_many):
	itm.num -= how_many
  
	return itm.price * how_many

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

def update_num(itm, how_many):
	how_many = itm.num if itm.num < how_many else how_many
	itm.num -= how_many
  
	return itm.price * how_many


(השורה הראשונה בגוף הפונקצייה

	how_many = itm.num if itm.num < how_many else how_many

מחשבת כמה איברים אפשר באמת לקנות: זהו מספר האיברים במלאי אם מספר האיברים במלאי קטן מהמספר המבוקש, ואחרת פשוט מספר האיברים המבוקש.)

הדפסת נתוני פריט[עריכה]

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

def display_item(itm):
	print "name: %s, catalog number: %d, price: %f, in stock: %d\n" % \
		(itm.name, itm.catalog_number, itm.price, itm.num)

דוגמה מסכמת[עריכה]

נמשיך בדוגמה שתסכם את רוב מה שלמדנו על מחלקות.

התוכנית[עריכה]

התוכנית הבאה היא תוכנת ניהול פשוטה מאד לחנות מכולת:

class item:
	pass


def make_item(catalog_number, price, name, num):
	itm = item()
	
	itm.catalog_number = catalog_number
	itm.price = price
	itm.name = name
	itm.num = num
	
	return itm


def update_num(itm, how_many):
	how_many = itm.num if itm.num < how_many else how_many
	itm.num -= how_many
  
	return itm.price * how_many


def display_item(itm):
	print "name: %s, catalog number: %d, price: %f, in stock: %d\n" % \
		(itm.name, itm.catalog_number, itm.price, itm.num)


items = []
items.append( make_item(23, 12.90, 'shoko', 100) )
items.append( make_item(109, 5, 'roll', 100) ) 
items.append( make_item(22, 2.3, 'kartiv', 5) )
items.append( make_item(33, 1.0, 'mastik', 10) ) 
items.append( make_item(1000, 5, 'pita', 1000) ) 
items.append( make_item(2233, 23, 'humus', 20) )

done = False

while done == False:
	print 'The items in the store are:'
	for i in range(6):
		display_item(items[i])
		
	i = input('Which item would you like to purchase? ')

	if(i > 6):
		done = True
	else:
		update_num(items[i], 1)

פירוט[עריכה]

המחלקה והפונקציות הפועלות עליה[עריכה]

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

class item:
	pass


def make_item(catalog_number, price, name, num):
	itm = item()
	
	itm.catalog_number = catalog_number
	itm.price = price
	itm.name = name
	itm.num = num
	
	return itm


def update_num(itm, how_many):
	how_many = itm.num if itm.num < how_many else how_many
	itm.num -= how_many
  
	return itm.price * how_many


def display_item(itm):
	print "name: %s, catalog number: %d, price: %f, in stock: %d\n" % \
		(itm.name, itm.catalog_number, itm.price, itm.num)


הקוד לניהול הפריטים[עריכה]

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

items = []
items.append( make_item(23, 12.90, 'shoko', 100) )
items.append( make_item(109, 5, 'roll', 100) ) 
items.append( make_item(22, 2.3, 'kartiv', 5) )
items.append( make_item(33, 1.0, 'mastik', 10) ) 
items.append( make_item(1000, 5, 'pita', 1000) ) 
items.append( make_item(2233, 23, 'humus', 20) )

done = False

while done == False:
	print 'The items in the store are:'
	for i in range(6):
		display_item(items[i])
		
	i = input('Which item would you like to purchase? ')

	if(i > 6):
		done = True
	else:
		update_num(items[i], 1)

ראשית מגדירים את תכולת המלאי, המכיל 6 סוגי פריטים:

items = []
items.append( make_item(23, 12.90, 'shoko', 100) )
items.append( make_item(109, 5, 'roll', 100) ) 
items.append( make_item(22, 2.3, 'kartiv', 5) )
items.append( make_item(33, 1.0, 'mastik', 10) ) 
items.append( make_item(1000, 5, 'pita', 1000) ) 
items.append( make_item(2233, 23, 'humus', 20) )

הלולאה:

done = False

while done == False:
	print 'The items in the store are:'
	for i in range(6):
		display_item(items[i])
		
	i = input('Which item would you like to purchase? ')

	if(i > 6):
		done = True
	else:
		update_num(items[i], 1)

פועלת כל עוד לא הקליד המשתמש מספר גדול מ6.

בתוך הלולאה, ראשית מדפיסים את הפריטים:

	print 'The items in the store are:'
	for i in range(6):
		display_item(items[i])

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

	i = input('Which item would you like to purchase? ')

כל שנותר הוא (לבדוק אם הפריט חוקי ו) לטפל בבקשה:

	if(i > 6):
		done = True
	else:
		update_num(items[i], 1)

עבודה עם מחלקות - גרסה עדיפה[עריכה]

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


הכנסת הפונקציות למחלקה[עריכה]

class item:
	def __init__(self, catalog_number, price, name, num):
		self.catalog_number = catalog_number
		self.price = price
		self.name = name
		self.num = num


	def update_num(self, how_many):
		how_many = self.num if self.num < how_many else how_many
		self.num -= how_many
	  
		return self.price * how_many


	def display(self):
		print 'name: %s, catalog number: %d, price: %f, in stock: %d\n' % \
			(self.name, self.catalog_number, self.price, self.num)

נוכל לשים לב לשינויים החדשים:

  1. הפונקציות הוכנסו לתוך המחלקה.
  2. הפרמטר הראשון בכל פונקציה נקרא self.
  3. הפונקציה create_item שינתה את שמה, ונקראת עתה בשם (המוזר מעט) __init__.

יצירת עצמי מחלקה[עריכה]

נוכל ליצור עצמי מחלקה בצורה הבאה:

item(23, 19.90, 'shoko', 100)

פקודה זו קוראת לפונקציה __init__ שראינו מקודם (זו שהחליפה את make_item). מהפרמטר הראשון, self, נתעלם לרגע. שאר הפרמטרים (לדוגמה catalog_number), מקושרים לעצמים שאנו מעבירים (לדוגמה 23).

קריאה זו מייצרת עצם, ואפשר כמובן לקשר שם לעצם זה:

shoko = item(23, 19.90, 'shoko', 100)

אפשר גם להכניס עצמים כאלה לרשימה.

items = []
items.append( item(23, 12.90, 'shoko', 100) )
items.append( item(109, 5, 'roll', 100) ) 
items.append( item(22, 2.3, 'kartiv', 5) )
items.append( item(33, 1.0, 'mastik', 10) ) 
items.append( item(1000, 5, 'pita', 1000) ) 
items.append( item(2233, 23, 'humus', 20) )

שימוש בעצמי המחלקה[עריכה]

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

shoko = item(23, 19.90, 'shoko', 100)

print shoko.display()

תקרא לפונקציה display, וprint תדפיס את התוצאה.


שכתוב הדוגמה המסכמת[עריכה]

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

class item:
	def __init__(self, catalog_number, price, name, num):
		self.catalog_number = catalog_number
		self.price = price
		self.name = name
		self.num = num


	def update_num(self, how_many):
		how_many = self.num if self.num < how_many else how_many
		self.num -= how_many
	  
		return self.price * how_many


	def display(self):
		print 'name: %s, catalog number: %d, price: %f, in stock: %d\n' % \
			(self.name, self.catalog_number, self.price, self.num)


items = []
items.append( item(23, 12.90, 'shoko', 100) )
items.append( item(109, 5, 'roll', 100) ) 
items.append( item(22, 2.3, 'kartiv', 5) )
items.append( item(33, 1.0, 'mastik', 10) ) 
items.append( item(1000, 5, 'pita', 1000) ) 
items.append( item(2233, 23, 'humus', 20) )

done = False

while done == False:
	print 'The items in the store are:'
	for i in range(6):
		items[i].print()
		
	i = input('Which item would you like to purchase? ')

	if(i > 6):
		done = True
	else:
		items[i].update_num(1)

הסבר מעט יותר מעמיק[עריכה]

פונקציית הבנייה __init__[עריכה]

פונקציות מחלקה מיוחדות.

הפרמטר self[עריכה]

איברי מחלקה "פרטיים"[עריכה]

class item:
	def __init__(self, catalog_number, price, name, num):
		self._catalog_number = catalog_number
		self._price = price
		self._name = name
		self._num = num


	def update_num(self, how_many):
		how_many = self._num if self._num < how_many else how_many
		self._num -= how_many
	  
		return self._price * how_many


	def display(self):
		print 'name: %s, catalog number: %d, price: %f, in stock: %d\n' % \
			(self._name, self._catalog_number, self._price, self._num)


כדאי לדעת:

C++

פונקציות מחלקה מיוחדות[עריכה]

יתרונות שיטת המחלקות השניה[עריכה]

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

כימוס[עריכה]

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

def update_num(itm, how_many):
	itm.num -= how_many
  
	return itm.price * how_many

פועל בצורה שגויה כאשר מספר האיברים המבוקש גדול מהמלאי.

בתוכנית גדולה מאוד, גם אם בדיעבד היינו מזהים בעיה זו, היה קשה מאד לשנות

שיוך[עריכה]

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