# Budget Analysis Program

Posted on

Problem

I have written code for a budget calculate program.

Here is the problem :

Write a program that asks the user to enter the amount that he or she has budgeted for a month.
A loop should then prompts the user to enter each of his or her expenses for the month and keep a running total. When the loop finishes, the program should display the amount that the user is over or under budget.

Here is the code I wrote:

``````# This program calculates budget for a month for a user.
# Get the budget of a user
budget = int(input('Enter the budget of the month: '))
another = 'y'  # Variable to control the loop

# Declare the accumulator
total = 0.0

# Process one or more expenses
while another == 'y' or another == 'Y':
# Get the expenses
expenses = float(input('Enter the expenses: '))
total += expenses
# Do this again ?
another = input('Do you have another expenses? ' +
'(Enter y for yes): ')

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 ')

print(f'Total: \${total}')
``````

Here is the output :

``````# Program Output
# Enter the budget of the month: 500
# Enter the expenses: 200
# Do you have another expenses? (Enter y for yes): y
# Enter the expenses: 600
# Do you have another expenses? (Enter y for yes): n
# You are \$300.0 over the budget
# Total: \$800.0

# Program Output
# Enter the budget of the month: 700
# Enter the expenses: 200
# Do you have another expenses? (Enter y for yes): y
# Enter the expenses: 150
# Do you have another expenses? (Enter y for yes): n
# You are \$-350.0 under the budget
# Total: \$350.0

# Program output
# Enter the budget of the month: 1000
# Enter the expenses: 500
# Do you have another expenses? (Enter y for yes): y
# Enter the expenses: 500
# Do you have another expenses? (Enter y for yes): n
# Your budget is enough for expenses!
# Total: \$1000.0
``````

The purpose of this exercise is to practice loops. Because I’m new to programming, I like to read
other people’s code but I haven’t found anything related to this exercise.
This is an exercise in the tony gaddis python book.

Can someone read this code and comment about it. Mistakes, things i need to improve.

Solution

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.

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"

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 "nn".join(summary)

if __name__ == "__main__":
period = "month"
budget = get_budget(period)
expenses = get_expenses(period)
print(summary(budget, expenses, period))
``````

Overlapping a little with the feedback from N3buchadnezzar:

• Move your code out of the global namespace into functions
• Use `locale` to format your currencies
• Don’t use `float` to represent currencies; use `Decimal` to prevent accuracy loss
• Consider simplifying your user interface to terminate expense entry with a blank string

## Suggested

``````"""This program calculates budget for a month for a user."""
from decimal import Decimal, DecimalException
from locale import currency, setlocale, LC_ALL
from typing import Iterator, Optional

def try_get_decimal(as_string: str) -> Optional[Decimal]:
try:
value = Decimal(as_string)
if value <= 0:
print('Number must be positive')
else:
return value
except DecimalException:
print('Invalid decimal number')

def get_budget() -> Decimal:
while True:
value = try_get_decimal(input('Enter the budget of the month: '))
if value is not None:
return value

def get_expenses() -> Iterator[Decimal]:
while True:
as_string = input('Enter an expense, or press enter to continue: ')
if as_string == '':
break
value = try_get_decimal(as_string)
if value is not None:
yield value

def main() -> None:
setlocale(LC_ALL, '')

budget = get_budget()
total = sum(get_expenses())

surplus = budget - total
if surplus > 0:
print(f'You are {currency(surplus)} under the budget')
elif surplus == 0:
print('Your budget is enough for expenses!')
else:
print(f'You are {currency(-surplus)} over the budget')

print(f'Total: {currency(total)}')

if __name__ == '__main__':
main()
``````