Your code works! You have done the first step which is to make your code work congratulations! How your code looks is often how my own code looks before i refactor it. Refactoring is something we should do once we get the ****** thing working. The standard way a beginner Python program should look is this
imports ...
def ...
def ...
if __name__ == "__name__":
main code goes here
The definitions should do one thing and only one thing. For instance obtain the budget, ask the user if they want to continue, calculating expenses etc.
Useless comments one should really avoid useless comments as they clutter the code and does not contribute anything. For instance
# Get the budget of a user
budget = int(input('Enter the budget of the month: '))
Is just as clear without the comment. What if we decide to change the budget to include not only users but dogs cats and other felines? Than our comment is not just clutter, but even wrong! Maintaining and updating comments to be in sync with our code quickly becomes an unnecessary chore.
Your comments can be transferred into explicit typing hints and docstrings.
How to make code readable. Your code is very readable, which is a great thing! Good job! My suggestion for making code readable is as follows: 1 split the code into sensible modules (or files) where each file has a single purpose and a good name. Similarly split the file into functions with clear name, purpose and documentation. The function should then consist of variables with, you guessed it, clear, concise and succinct variable names. Comments should be reserved to explain really terse parts of the code, or sometimes explain why. I also use them for references to Stackoverflow when I borrow code.
Fragile handling of user input In my opinion your biggest mistake is fragile handling of user input. Users are not particularly clever beings, and requiring them to always provide you with perfect input is a fools errand. What happens in your code if I enter "four hundred dollars" as my budget? What happens if I enter a negative expense? What if I write in y
(notice the extra trailing whitespace) when asked to continue? These are all things that should be considered.
One way to ask the user for confirmation is as follow
def confirm(text: str) -> bool:
"""Ask the user for confirmation to continue"""
return input(f"{text} [y/N] ").strip().lower().startswith("y")
However, it is tedious for the user to have to confirm every single expense which included. A better option is to break if the input is blank. Here we can use that an empty string ""
in python is interpreted as false.
Minor details
This part here in my eyes smell
if total > budget:
print(f'You are ${total - budget} over the budget ')
elif total == budget:
print(f'Your budget is enough for expenses!')
else:
print(f'You are ${total - budget} under the budget ')
The problem is that the first and second line only differ by a single word, which indicates that we can refactor it. You are also computing the difference between the total and budget multiple times. Something like the following solves these problems
if (budget_diff := expenses - budget) == 0:
print(f"Your expenses are exactly on budget!")
else:
print(
f"You are ${abs(budget_diff)} "
f"{'under' if budget_diff else 'over'} the budget "
)
code
Fixing the things above, adding functions, typing hints an if __name__ == "__main__"
guard turns your code into this
"""This program calculates budget for over a given time period."""
from typing import Callable, Annotated
Budget = Annotated[
float, "the amount the user has budgeted for the current time period"
]
Expenses = Annotated[
float, "the total amount of expenses the user has for the current time period"
]
def user_input(text: str, condition: Callable[[str], bool], expected: str) -> str:
"""Makes sure user input satisfies condition"""
while not condition(value := input(text).strip()):
print(f"Ooops! Expected {expected}, but obtained {value} instead.")
return value
def is_non_negative(number: str) -> bool:
"""Returns true if the input is a non negative integer"""
try:
return int(number) >= 0
except ValueError:
return False
def get_budget(period) -> Budget:
"""Returns the users budget for the given time period"""
text = f"\nEnter the budget for the {period}: "
condition = is_non_negative
expected = "a positive value"
return float(user_input(text, condition, expected))
def get_expenses(period) -> Expenses:
"""Returns the users total expenses for the given time period"""
expenses = 0
text = "[leave blank to quit] expense: "
condition = lambda x: is_non_negative(x) or not x
expected = "A positive value"
print(f"\nPlease write in your expenses for the current {period}.\n")
while expense := user_input(text, condition, expected):
expenses += float(expense)
return expenses
def summary(budget: Budget, expenses: Expenses, period: str) -> str:
"""Returns a summary of the budget and expenses for the period"""
summary = []
if (budget_diff := expenses - budget) == 0:
summary.append(f"\nYour expenses are exactly on budget for the {period}!")
else:
summary.append(
f"\nYou are ${abs(budget_diff)} "
f"{'under' if budget_diff else 'over'} the budget for the {period}."
)
summary.append(f"Expenses: ${expenses}, Budget: ${budget}.")
return "\n\n".join(summary)
if __name__ == "__main__":
period = "month"
budget = get_budget(period)
expenses = get_expenses(period)
print(summary(budget, expenses, period))