I wanted to give you a more detailed answer on the CLI, how you could clean the code by splitting it up in functions, how you could define your own exceptions, and how you could remove one of your two main loops. Moreover, instead of summing the number as we go, I chose to store every input. For instance, an output could be:
{'Computer': [1, 3], # 2 branches with 1 and 3 books
'Physics': [4], # 1 branch with 4 books
'Chemistry': [2, 3, 2],
'Biology': [2],
'Arts': [2]}
The code is divided in 3 parts:
- Exceptions
- Inputs
- Main loop
You can define your own exception. You can read e.g. this article. Moreover, I chose to limit the number of possible retries for a user input to 5. This way, the program always has an end. I defined RETRIES in capital letters I usually I would consider this as an imported global variable.
#%% Define custom exceptions
RETRIES = 5
class ConsecutiveWrongAnswers(Exception):
"""Exception raised when the users input too many times a wrong answer
to a query.
Attributes:
retries -- Number of retries
"""
def __init__(self, retries):
self.retries = retries
self.message = f'The input was invalid more than {self.retries} times!'
super().__init__(self.message)
class InvalidBranchNumber(Exception):
"""Exception raised for errors in the branch number input.
Attributes:
branch -- invalid branch number.
message -- explanation of the error.
"""
def __init__(self, branch,
message="Branch number input should be a positive integer."):
self.branch = branch
self.message = message
super().__init__(self.message)
def __str__(self):
return f'{self.branch} -> {self.message}'
class InvalidBooksNumber(Exception):
"""Exception raised for errors in the branch number input.
Attributes:
books -- invalid books number.
message -- explanation of the error.
"""
def __init__(self, branch,
message="Books number input should be a positive integer."):
self.books = books
self.message = message
super().__init__(self.message)
def __str__(self):
return f'{self.books} -> {self.message}'
Then, I define input functions which will let the user try to enter a valid input up to RETRIES times. The input is checked before it is returned.
#%% Input functions
def input_branch_number(category, retries=RETRIES):
def check_branch(branch):
if branch <= 0:
raise InvalidBranchnNumber(branch)
attempt = 0
while True:
attempt += 1
if attempt > retries:
raise ConsecutiveWrongAnswers(retries)
try:
branch = int(input(f'[IN] Branch number for category {category}: '))
check_branch(branch)
break
except (InvalidBranchNumber, ValueError, TypeError):
print (f"Invalid ID: branch number should be a positive integer.)")
except:
raise
return branch
def input_books_number(category, branch_id, retries=RETRIES):
def check_books(books):
if books <= 0:
raise InvalidBooksNumber(books)
attempt = 0
while True:
attempt += 1
if attempt > retries:
raise ConsecutiveWrongAnswers(retries)
try:
books = int(input(f'[IN] Books number for (category, branch n°) - ({category}, {branch_id}): '))
check_books(books)
break
except (InvalidBooksNumber, ValueError, TypeError):
print (f"Invalid ID: branch number should be a positive integer.)")
except:
raise
return books
And finally, the main loop is now a lot simpler to read:
#%% Main loop
# Initialize the data structure which will contain the number of books per category and per branch.
books_logs = dict()
for category in ["Computer", "Physics", "Chemistry", "Biology", "Arts"]:
# Input the number of branch per category
branch = input_branch_number(category)
# For each category, initialize an empty list. The list will be filled by the number of books per branch
books_logs[category] = list()
for branch_id in range(branch):
# Input the books for each (category, branch_id)
books = input_books_number(category, branch_id)
books_logs[category].append(books)
You can now work on the books_logs dictionary to sum per branch, per category, or to get the total.
N.B: For string formatting, instead of summing str objects as you did, I suggest you use the python formatting flag which improves readability:
f'This is a formatting string with {n_characters} characters and {n_words}.'
The element in between {} can be anything, it will be evaluated/executed by python. You could have formulas, e.g.
k = 2
f'I want to format with leading zeros integer {k} to get {str(k).zfill(3)}'
Finally, I chose a very simple data structure, but you can replace the dictionary with another one. Especially, as suggested by Ruthger Righart you could use a pandas dataframe.