זה נושא דיון מלווה לערך המקורי ב- https://www.tocode.co.il/bundles/python/lessons/21-class-syntax-lab
זה נושא דיון מלווה לערך המקורי ב- https://www.tocode.co.il/bundles/python/lessons/21-class-syntax-lab
למה אין פתרונות לשאלות?
הי @gizmo
המטרה של הקורס והשיעורים והדוגמאות זה להראות את החומר,
המטרה של התרגילים היא לתת לך מקום להתנסות בו - דווקא בתוך סיטואציה שאין תשובה נכונה ואין פיתרון בית ספר אלא מתוך מטרה כמה שיותר לדמות את העולם האמיתי.
אני כאן ואם אתה נתקע על אחד התרגילים אשמח לעזור להגיע לתשובה הנכונה - פשוט פרסם על מה הסתכלת ומה ניסית לעשות ונמשיך יחד.
בנוסף אם אתה רוצה פידבק על פיתרון שכתבת ועובד גם מוזמן להעלות לכאן ואני שמח להסתכל ולהגיב
היי @ynonp
אני מנסה לפתור את התרגיל הראשון לא כזה הולך לי…
אני רוצה להבין משהו יש לי את הקוד הזה:
class Summer:
total = 0
def __init__(self, ):
def print_total(self):
total =
def add(self):
s = Summer()
t = Summer()
s.add(10, 20)
t.add(50)
s.add(30)
# should print 60
s.print_total()
# should print 50
t.print_total()
אני לא יודע כמה משתנים לשים בפונקציה ADD וכמה ב Print total.
אם אני שם 2 משתנים בפונקציה add לדוג :
def add(self,num1,num2):
אז יש לי בעיה פה
t.add(50)
s.add(30)
אני מקבל מpycharm : parameter num2 unfilded
איו לי מושג איך להמשיך
תוכל לעזור לי ?
נסה לראות את השיעור הזה ותראה אם זה נותן לך רעיון:
https://www.tocode.co.il/bundles/advanced-python3/lessons/variadic-functions
הצעת הפתרון שלי לארבעת התרגילים:
# Ex1
print('\n ----- Ex1 -----')
class Summer:
def __init__(self):
self._total = 0
def add(self, *numbers: int) -> None:
self._total += sum(numbers)
def print_total(self):
print(self._total)
s = Summer()
t = Summer()
s.add(10, 20)
t.add(50)
s.add(30)
s.print_total() # should print 60
t.print_total() # should print 50
# Ex2
print('\n ----- Ex2 -----')
class MyCounter:
count = 0
def __init__(self):
MyCounter.count += 1
for _ in range(10):
c1 = MyCounter()
print(MyCounter.count) # should print 10
# Ex3
print('\n ----- Ex3 -----')
class Widget:
def __init__(self, name):
self.name = name
self._dependencies = []
self.built = False
def add_dependency(self, *dependencies):
self._dependencies.extend(dependencies)
def build(self):
self.built = True
for d in self._dependencies:
if not d.built:
d.build()
print(d.name)
luke = Widget("Luke")
hansolo = Widget("Han Solo")
leia = Widget("Leia")
yoda = Widget("Yoda")
padme = Widget("Padme Amidala")
anakin = Widget("Anakin Skywalker")
obi = Widget("Obi-Wan")
darth = Widget("Darth Vader")
_all = Widget("All")
luke.add_dependency(hansolo, leia, yoda)
leia.add_dependency(padme, anakin)
obi.add_dependency(yoda)
darth.add_dependency(anakin)
_all.add_dependency(luke, hansolo, leia, yoda, padme, anakin, obi, darth)
_all.build()
# code should print: Han Solo, Padme Amidala, Anakin Skywalker, Leia, Yoda, Luke, Obi-Wan, Darth Vader
# (can print with newlines in between modules)
# Ex4
print('\n ----- Ex4 -----')
from collections import deque
debug = False
class Queue:
def __init__(self, clerk):
self.id = clerk
self.queue = deque()
if debug: print(f'__init__ Queue for {clerk}')
def queue_len(self):
return len(self.queue)
def add_to_queue(self, customer):
self.queue.append(customer)
def serve_next(self):
return self.queue.popleft()
class QueueSystem:
no_valid_command_msg = """No valid command was given.
Please use one of these formats:
wait <clerk-name> <customer-name>
next <clerk-name>
"""
def __init__(self):
self.clerks = {}
if debug: print('__init__ QueueSystem')
def accept_commands(self):
while True:
command = input(': ')
try:
(action, clerk_customer) = command.split(' ', 1)
except ValueError:
if 'exit' == command:
print('bye')
return None
print(QueueSystem.no_valid_command_msg)
continue
if 'wait' == action:
try:
(clerk, customer) = clerk_customer.split(' ', 1)
except ValueError:
print(QueueSystem.no_valid_command_msg)
continue
self.wait(clerk, customer)
elif 'next' == action:
self.next(clerk_customer)
else:
print(QueueSystem.no_valid_command_msg)
if debug: self.show_queues()
def wait(self, clerk, customer):
if debug: print(f'customer: {customer} will wait in clerk {clerk} queue.')
if clerk in self.clerks:
self.clerks[clerk].add_to_queue(customer)
else:
new_clerk = Queue(clerk)
new_clerk.add_to_queue(customer)
self.clerks[clerk] = new_clerk
def next(self, clerk):
if clerk not in self.clerks:
new_clerk = Queue(clerk)
self.clerks[clerk] = new_clerk
try:
customer = self.clerks[clerk].serve_next()
except IndexError: # No customers in given clerk's queue
clerks_queues_by_len = {self.clerks[c].queue_len(): c for c in self.clerks} # OK to squash same size queues.
busiest_queue_len = max(clerks_queues_by_len.keys())
if 0 < busiest_queue_len:
customer = self.clerks[clerks_queues_by_len[busiest_queue_len]].serve_next()
try:
print(customer)
except UnboundLocalError:
print('There are no more customers to serve.')
def show_queues(self):
for cur_clerk in self.clerks:
print(cur_clerk, self.clerks[cur_clerk].queue)
qs = QueueSystem()
qs.accept_commands()
הפלט (כולל הקלט שהכנסתי עבור תרגיל 4):
----- Ex1 -----
60
50
----- Ex2 -----
10
----- Ex3 -----
Han Solo
Padme Amidala
Anakin Skywalker
Leia
Yoda
Luke
Obi-Wan
Darth Vader
----- Ex4 -----
: wait dave mika
: wait dave jane
: wait dave bill
: wait michael eddy
: wait michael jim
: next dave
mika
: next dave
jane
: next michael
eddy
: next michael
jim
: next michael
bill
: exit
bye
Process finished with exit code 0
לגבי האחרון - נדמה לי שאפשר לשפר את הקוד אם מחלקים לקלאסים קצת אחרת:
-
קלאס עבור ״פקודה״ שמקבל שורת טקסט ומגדיר לעצמו מאפיינים (כמו סוג הפקודה, לאיזה פקיד היא יכולה להישלח, מה הפרמטרים)
-
קלאס עבור ״פקיד״ שמקבל פקודה ומבצע אותה
מה דעתך? רוצה לנסות?
אתה מתכוון שמערכת התורים לא תהיה מחלקה, אלא התוכנית עצמה והיא תעזר במחלקה (ספריית כלים) עבור הפקודות ובמחלקה (“אמיתית”) עבור הפקידים?
משהו כזה?:
from collections import deque
debug = False
class Clerk:
def __init__(self, clerk_name):
self.name = clerk_name
self.queue = deque()
if debug: print(f'__init__ Clerk for {clerk_name}')
def wait(self, customer):
self.queue.append(customer)
def next(self):
return self.queue.popleft()
def has_customers_in_queue(self):
return True if 1 <= len(self.queue) else False
class CommandTools:
valid_actions = ('wait', 'next')
customer_actions = ('wait')
no_valid_command_msg = """
Please use one of these formats:
wait <clerk-name> <customer-name>
next <clerk-name>
"""
@staticmethod
def process_command(command):
command_words = command.split(' ')
if 2 > len(command_words) or 4 <= len(command_words):
raise UserWarning(f'Wrong number of words ({len(command_words)}).')
command_attribs = {}
if command_words[0] not in CommandTools.valid_actions:
raise UserWarning(f'Invalid command action ({command_words[0]}).')
command_attribs['action'] = command_words[0]
command_attribs['clerk'] = command_words[1]
if command_attribs['action'] in CommandTools.customer_actions:
if 3 > len(command_words):
raise UserWarning(f"Missing mandatory customer on a {command_attribs['action']} action.")
command_attribs['customer'] = command_words[2]
return command_attribs
clarks = {}
while True:
this_command = input(': ')
try:
this_command_attributes = CommandTools.process_command(this_command)
except UserWarning as e:
print(f'No valid command was given: {e}', CommandTools.no_valid_command_msg)
continue
if this_command_attributes['clerk'] not in clarks:
new_clerk = Clerk(this_command_attributes['clerk'])
clarks[this_command_attributes['clerk']] = new_clerk
if 'wait' == this_command_attributes['action']:
clarks[this_command_attributes['clerk']].wait(this_command_attributes['customer'])
elif 'next' == this_command_attributes['action']:
next_customer = None
if clarks[this_command_attributes['clerk']].has_customers_in_queue():
next_customer = clarks[this_command_attributes['clerk']].next()
else:
for clark in clarks:
if clark == this_command_attributes['clerk']:
continue
if clarks[clark].has_customers_in_queue():
next_customer = clarks[clark].next()
if next_customer is not None:
print(next_customer)
else:
print('There are no more customers to serve.')
if debug:
for c in clarks:
print(f'{c}\t{clarks[c].queue}')
יותר טוב אבל אני חושב שאפשר לקחת את זה עוד כמה צעדים קדימה. שים לב איך עכשיו המחלקה של Clrek נראית נקיה עם פונקציות קטנות שברור מה כל אחת עושה, ולעומתה שאר הקוד נראה מאוד עמוס.
בוא נתחיל עם הרעיון של Dispatching כלומר תוכן לולאת ה while True שיש לך שקוראת פקודות ושולחת אותן לפקידים. בדרך כלל מבנה של לולאה כזאת נראה ככה:
while True:
next_command = read_next_command()
next_clerk = get_handling_clerk(next_command)
next_clerk.handle(next_command)
עם הפרדה כזאת לפונקציות כל הלולאה תהיה הרבה יותר ברורה. בגלל שהפונקציה get_handling_clerk
צריכה לעבוד על אוסף הפקידים וכל פעם למצוא פקיד פנוי, יהיה נחמד לשים אותה במחלקה שתוכל לנהל את כל אוסף הפקידים, ליצור חדש אם צריך ולמצוא פקיד פנוי אם אפשר. במילים אחרות קוד יותר מדויק יראה כך:
dispatcher = Dispatcher()
while True:
cmd = read_command()
clerk = dispatcher.get_clerk(cmd)
clerk.handle(cmd)
נסה לכתוב את המימוש של Dispatcher ושל הפונקציות כאן כדי שהלולאה הראשית תהיה קצרה יותר ונמשיך משם.
הוספתי מחלקה של Dispatcher והתאמתי את שאר הקוד:
from collections import deque
class Clerk:
def __init__(self, clerk_name):
self.name = clerk_name
self._queue = deque()
def handle(self, cmd_atr):
if 'wait' == cmd_atr['action']:
self.wait(cmd_atr['customer'])
elif 'next' == cmd_atr['action']:
self.next()
def wait(self, customer):
self._queue.append(customer)
def next(self):
try:
print(self._queue.popleft())
except IndexError:
print('There are no more customers to serve.')
def has_customers_in_queue(self):
return True if 1 <= len(self._queue) else False
class CommandTools:
valid_actions = ('wait', 'next')
customer_actions = ('wait')
no_valid_command_msg = """
Please use one of these formats:
wait <clerk-name> <customer-name>
next <clerk-name>
"""
@staticmethod
def read_command():
while True:
cmd_input = input(': ')
try:
return CommandTools.parse_command(cmd_input)
except UserWarning as e:
print(f'No valid command was given: {e}', CommandTools.no_valid_command_msg)
continue
@staticmethod
def parse_command(command):
command_words = command.split(' ')
if 2 > len(command_words) or 4 <= len(command_words):
raise UserWarning(f'Wrong number of words ({len(command_words)}).')
command_attribs = {}
if command_words[0] not in CommandTools.valid_actions:
raise UserWarning(f'Invalid command action ({command_words[0]}).')
command_attribs['action'] = command_words[0]
command_attribs['clerk'] = command_words[1]
if command_attribs['action'] in CommandTools.customer_actions:
if 3 > len(command_words):
raise UserWarning(f"Missing mandatory customer on a {command_attribs['action']} action.")
command_attribs['customer'] = command_words[2]
return command_attribs
class Dispatcher:
def __init__(self):
self._clerks = {}
def get_clerk(self, cmd_atr):
clerk_on_call = cmd_atr['clerk']
if clerk_on_call not in self._clerks:
new_clerk = Clerk(clerk_on_call)
self._clerks[clerk_on_call] = new_clerk
if 'next' == cmd_atr['action'] and not self._clerks[clerk_on_call].has_customers_in_queue():
for alt_clerk in self._clerks:
if self._clerks[alt_clerk].has_customers_in_queue():
clerk_on_call = alt_clerk
break
return self._clerks[clerk_on_call]
dispatcher = Dispatcher()
while True:
cmd = CommandTools.read_command()
clerk = dispatcher.get_clerk(cmd)
clerk.handle(cmd)
כבר נראה הרבה יותר טוב לא?
הפונקציות קצרות יותר, ומסודרות בצורה יותר מאוזנת בין המחלקות כך שיותר קל להתמצא. עכשיו אפשר להסתכל על הקוד ברזולוציה יותר גבוהה ולדבר על קטעים ספציפיים.
בוא נתחיל עם הפונקציה get_clerk
. איזה פעולות היא עושה שאפשר היה להוציא לפונקציות נפרדות כך שיהיה יותר קל להבין אותה? אם היית צריך לתאר את הלוגיקה שלה באמצעות פונקציות עזר איך היית כותב אותה? (תחשוב על התהליך שעשיתי עם הלולאה הראשית)
גרסה חדשה מותאמת להערה שלך:
from collections import deque
class Clerk:
def __init__(self, clerk_name):
self.name = clerk_name
self._queue = deque()
def handle(self, cmd_atr):
if 'wait' == cmd_atr['action']:
self.wait(cmd_atr['customer'])
elif 'next' == cmd_atr['action']:
self.next()
def wait(self, customer):
self._queue.append(customer)
def next(self):
try:
print(self._queue.popleft())
except IndexError:
print('There are no more customers to serve.')
def has_customers_in_queue(self):
return True if 1 <= len(self._queue) else False
class CommandTools:
valid_actions = ('wait', 'next')
customer_actions = ('wait')
no_valid_command_msg = """
Please use one of these formats:
wait <clerk-name> <customer-name>
next <clerk-name>
"""
@staticmethod
def read_command():
while True:
cmd_input = input(': ')
try:
return CommandTools.parse_command(cmd_input)
except UserWarning as e:
print(f'No valid command was given: {e}', CommandTools.no_valid_command_msg)
continue
@staticmethod
def parse_command(command):
command_words = command.split(' ')
if 2 > len(command_words) or 4 <= len(command_words):
raise UserWarning(f'Wrong number of words ({len(command_words)}).')
command_attribs = {}
if command_words[0] not in CommandTools.valid_actions:
raise UserWarning(f'Invalid command action ({command_words[0]}).')
command_attribs['action'] = command_words[0]
command_attribs['clerk'] = command_words[1]
if command_attribs['action'] in CommandTools.customer_actions:
if 3 > len(command_words):
raise UserWarning(f"Missing mandatory customer on a {command_attribs['action']} action.")
command_attribs['customer'] = command_words[2]
return command_attribs
class Dispatcher:
def __init__(self):
self._clerks = {}
def get_clerk(self, cmd_atr):
clerk_on_call = cmd_atr['clerk']
self.add_clerk_if_not_exist(clerk_on_call)
if 'next' == cmd_atr['action']:
clerk_on_call = self.get_clerk_with_customers(clerk_on_call)
return self._clerks[clerk_on_call]
def add_clerk_if_not_exist(self, name):
if name not in self._clerks:
clerk_obj = Clerk(name)
self._clerks[name] = clerk_obj
def get_clerk_with_customers(self, requested_clerk):
if self._clerks[requested_clerk].has_customers_in_queue():
return requested_clerk
for c in self._clerks:
if c != requested_clerk and self._clerks[c].has_customers_in_queue():
return c
return requested_clerk
dispatcher = Dispatcher()
while True:
cmd = CommandTools.read_command()
clerk = dispatcher.get_clerk(cmd)
clerk.handle(cmd)
האמת שלי עדיין קשה לקרוא את זה. כשאני מסתכל על הקוד בתוך get_clerk
שלך קשה להבין ממנו מה המהות של הפונקציה - מה היא צריכה לעשות, ומה האילוצים שלה.
הייתי מתחיל ממשהו כזה:
def get_clerk(self, cmd):
clerk_on_call = self.find_or_create_clerk(cmd.clerk_name)
if clerk_on_call.is_busy and cmd.allows_other_clerks:
clerk_on_call = self.find_free_clerk()
clerk_on_call.handle(cmd)
כלומר לתאר במילים את הדבר שאתה רוצה שהפונקציה תעשה באמצעות האוביקטים שיש לה גישה אליהם, ולהפריד כל מנגנון שיטפל בחלקים שרלוונטים אליו.
מה דעתך?
קוד מעולה יהיה פשוט (ולכן לרוב קצר) יעיל ומסביר את עצמו ללא צורך בתיעוד.
זה לא קוד מעולה
לא בטוח אם כל אחד, לכל משימה, יכול להפיק קוד מעולה.
עדיין, הקוד כנראה טוב ולא מסובך במיוחד, כך שהוספת תיעוד (שבמסגרת תרגיל זה לא עשיתי) יכולה להשלים את ולעזור לקוד להיות קריא וברור.
במיוחד כשהקורא לא יודע את מטרת התכנית בכללותה ומטרות הפונקציות בפרט, קשה יותר לקוד לבדו “להסביר את עצמו”. יש סיכוי שגם אתה קצת התרחקת מהמטרות של החלקים השונים - לדוגמה הצעת עכשיו:
- לבצע
handle
לפקודה בתוךget_clerk
כשהמקום הראוי לזה (כפי שאתה הצעת שתי תגובות קודם) הוא בלולאה הראשית. - לבדוק אם פקיד “עסוק” - קונספט לא רלוונטי באופן ישיר, כשבאופן עקיף רלוונטי רק לפקודות מסוימות.
אני רואה את מטרת פונקציית get_clerk
במחלקת Dispatcher
כך:
לקבל את מאפייני הפקודה לטיפול ולהחזיר אובייקט של פקיד המתאים לטפל בה.
מאפייני הפקודה הם: הפעולה לביצוע, שם הפקיד המבוקש, שם לקוח (רלוונטי רק לפעולת “המתנה”).
לכן לוגיקת הפונקציה הזו היא:
- אם הפקיד המבוקש עדיין לא במאגר הפקידים של ה-
Dispatcher
, צור וצרף אתו. - אם הפעולה לביצוע דורשת שיהיו לפקיד לקוחות בתור, קבל שם של פקיד כזה (הפקיד המבוקש אם יש לו לקוחות, אחרת פקיד אחר עם לקוחות אם יש כזה)
- החזר את אובייקט הפקיד
שיפור מסוים לקריאות של הקוד עשוי להיות אם אשתמש ב CommandTools.customer_actions
כחלק מהתנאי בזיהוי סוג הפעולה לביצוע. אבל אם יודעים את מטרת הפונקציה ומבנה האובייקטים Dispatcher
ו-Clerk
הקוד די ברור לדעתי.
def get_clerk(self, cmd_atr):
clerk_on_call = cmd_atr['clerk']
self.add_clerk_if_not_exist(clerk_on_call)
if cmd_atr['action'] in CommandTools.customer_actions:
clerk_on_call = self.get_clerk_with_customers(clerk_on_call)
return self._clerks[clerk_on_call]
אוי כן אני רואה - הפקודה handle לא צריכה להיות שם. הייתי הולך על משהו כזה:
def get_clerk(self, cmd):
clerk_on_call = self.find_or_create_clerk(cmd.clerk_name)
if clerk_on_call.is_busy and cmd.allows_other_clerks:
clerk_on_call = self.find_free_clerk()
return clerk_on_call
אני חושב על ה Dispatcher הזה כמו סדרן בתחנת מוניות - אתה מתקשר לתחנה מבקש מונית, מספר את האילוצים שלך והעדפות שלך והסדרן צריך למצוא לך מונית מתאימה.
האילוצים וההעדפות נשמרים בתוך האוביקט cmd, ומאפייני המונית בתוך clerk. בגלל זה הבחירה להוסיף את busy ל clerk ואת allows_other_clerks
ל cmd.
אבל בכל מקרה אפשר להסכים שהגישות שלנו שונות לגבי הפונקציה הזאת ובכל מקרה אני חושב ששתי הגישות טובות.
הפונקציה הבאה שהייתי ניגש לטפל בה היא הפונקציה parse_command
של CommandTools.
שים לב איך הפונקציה הזאת צריכה להכיר את כל סוגי הפקודות וכמה פרמטרים כל פקודה צריכה לקבל ובדיוק איפה כל פרמטר שייך לכל פקודה. זה המון לוגיקה שככל שהשפה תאפשר יותר פקודות הפונקציה הזאת הולכת לגדול ולהיות קשה לתחזוקה.
בפיתוח מונחה עצמים אפשר להתמודד עם מצבים כאלה באמצעות חלוקה למחלקות - נסה להגדיר לכל סוג פקודה מחלקה משלה, לשנות את parse_command
כך שהיא רק תצטרך לזהות איזה סוג פקודה היא קיבלה, ותתן למחלקת הפקודה המתאימה לשבור את הראש איך לפענח את כל הפרמטרים. זה יבטל לך את כל ה if-ים בפונקציה ואני חושב שיעשה אותה קצת יותר פשוטה
אני לא חושב שהגישות שלנו שונות ומתחבר מאוד לדוגמה סדרן המוניות.
אולי ההבדל נובע מהסתכלות שלך על הפונקציה הספציפית במנותק מהמחלקה שלה ומכלל התכנית:
- למחלקת “סדרן המוניות” שלנו יש “מאגר” פקידים ב-
dict
עם שמות הפקידים כמפתחות המצביעים על האובייקט שלהם.
בסוף הפונקציה פשוט מחזירים את האובייקט לפי מפתח שם הפקיד. ללא “חיפוש”, אלא בעזרת שם הפקיד שהתקבל ממאפייני הפקודה שניתנו לנו (או ממקרה מיוחד, אליו אתייחס בסעיף הבא).
לכן הפעולה הראשונה בפונקציה היא רק לייצר ולהוסיף את הפקיד אם הוא לא קיים. לא “לחפש” ולהחזיר כבר בשלב זה את האובייקט של הפקיד. - בהגדרת הבונוס של התרגיל התייחסת לפקודת
next
כך שיטופל לקוח מתור של פקיד אחר אם אין לקוחות לפקיד שבפקודה.
הלוגיקה הנובעת מזה היא: בפקודתnext
; אם הפקיד המבוקש פנוי (אין לקוחות בתור); יש להשתמש בפקיד עם לקוחות.
לכן יצרתי אתget_clerk_with_customers
שמופעלת אם מדובר על פקודתnext
והיא “דואגת” לזה - תחזיר לנו שם של פקיד בהתאם ללוגיקה הזו (כולל הפקיד “המקורי” אם יש לו לקוחות בתור).
[בפקודהget_clerk_with_customers
יש שימוש בפקודה המקבילה ל-is_busy
(שהיאhas_customers_in_queue
).]
האם אנחנו כבר גולשים לירושה? כי זה היה תרגיל אחרי פרק התחביר בלבד… צריך להשאיר משהו לתרגיל המסכם של כלל פרקי תכנות מונחה עצמים
אגב, אשמח לקבל ממך משוב גם על מה שכתבתי בעקבות הפרק הבא:
[וגם על פרק מוקדם יותר]
למה ירושה? אנחנו ב Python כאן לא ב Java אין צורך בירושה בשביל להתיחס בצורה דומה למספר מחלקות. במקרה שלנו לא צריך שהפקודות השונות ירשו מאיזה מחלקת בסיס
לכאורה אמורות להיות להם מטודות זהות מבחינת ממשק, כשבפרק 24 המלצת להשתמש בירושה במקרים כאלו, למרות שבשימוש הנוכחי (המאוד מצומצם) באמת לא נראה שיהיה בזה תועלת (אלא רק אם יהיה משהו שבאמת אפשר לשתף בינהן).
from collections import deque
class Clerk:
def __init__(self, clerk_name):
self.name = clerk_name
self._queue = deque()
def handle(self, cmd_atr):
if 'wait' == cmd_atr['action']:
self.wait(cmd_atr['customer'])
elif 'next' == cmd_atr['action']:
self.next()
def wait(self, customer):
self._queue.append(customer)
def next(self):
try:
print(self._queue.popleft())
except IndexError:
print('There are no more customers to serve.')
def has_customers_in_queue(self):
return True if 1 <= len(self._queue) else False
class CommandTools:
valid_actions = ('wait', 'next')
customer_actions = ('wait')
no_valid_command_msg = """
Please use one of these formats:
wait <clerk-name> <customer-name>
next <clerk-name>
"""
@staticmethod
def read_command():
while True:
cmd_input = input(': ')
try:
return CommandTools.parse_command(cmd_input)
except UserWarning as e:
print(f'No valid command was given: {e}', CommandTools.no_valid_command_msg)
continue
@staticmethod
def parse_command(command):
command_words = command.split(' ')
# if command_words[0] not in CommandTools.valid_actions:
# raise UserWarning(f'Invalid command action ({command_words[0]}).')
try:
if 'wait' == command_words[0]:
return WaitCommand.parse(command)
elif 'next' == command_words[0]:
return NextCommand.parse(command)
else:
raise UserWarning(f'Invalid command action ({command_words[0]}).')
except ValueError:
raise UserWarning(f"Invalid number of words for {command_words[0]} action.")
class WaitCommand:
@staticmethod
def parse(command):
com_atr = {}
(com_atr['action'], com_atr['clerk'], com_atr['customer']) = command.split(' ')
return com_atr
class NextCommand:
@staticmethod
def parse(command):
com_atr = {}
(com_atr['action'], com_atr['clerk']) = command.split(' ')
return com_atr
class Dispatcher:
def __init__(self):
self._clerks = {}
def get_clerk(self, cmd_atr):
clerk_on_call = cmd_atr['clerk']
self.add_clerk_if_not_exist(clerk_on_call)
if 'next' == cmd_atr['action']:
clerk_on_call = self.get_clerk_with_customers(clerk_on_call)
return self._clerks[clerk_on_call]
def add_clerk_if_not_exist(self, name):
if name not in self._clerks:
clerk_obj = Clerk(name)
self._clerks[name] = clerk_obj
def get_clerk_with_customers(self, requested_clerk):
if self._clerks[requested_clerk].has_customers_in_queue():
return requested_clerk
for c in self._clerks:
if c != requested_clerk and self._clerks[c].has_customers_in_queue():
return c
return requested_clerk
def print_queues(self):
for c in self._clerks:
print(f'{c}\t{self._clerks[c]._queue}')
dispatcher = Dispatcher()
while True:
cmd = CommandTools.read_command()
clerk = dispatcher.get_clerk(cmd)
clerk.handle(cmd)