Scientific Computing with Python Projects - Budget App

I’m working on the Budget App and running into an issue where the output from Replit does not match the output of PyCharm.

In the Category class, I store attributes for a name and a total spend.
I try to pass the value of those attributes to the external function create_spend_chart() in a loop by using iteration.attribute (i.name and i.spend). When doing this in PyCharm, everything works as I would expect it to; the values are passed into the function and it returns the expected output. However, when I implement this code in Replit, it returns the following error:

Traceback (most recent call last):
  File "main.py", line 22, in <module>
    print(create_spend_chart([food, clothing, auto]))
  File "/home/runner/boilerplate-budget-app/budget.py", line 76, in create_spend_chart
    spending.append(getattr(i,'spend'))
AttributeError: 'list' object has no attribute 'spend'. Did you mean: 'append'?

After searching for potential solutions, I tried to use getattr(), which should (to my understanding) pass the attribute to an external function successfully. It works in PyCharm, but in Replit I receive the same error as above.

I will highlight the relevant sections of code here, with the full code pasted below

class Category:
    def __init__(self,name):
        self.ledger = []
        self.balance = float(0)
        self.name = name
        self.spend = float(0)

def create_spend_chart(cat1 ,cat2 = None,cat3 = None,cat4 = None) :
    catdict = {}
    # get the total of spending across all passed categories
    spending = []
    for i in [cat1, cat2, cat3, cat4]:
        if i != None:
            spending.append(getattr(i,'spend'))   #####here (the alternative form to this that also worked in PyCharm for me was "spending.append(i.spend)"
    spending = sum(spending)

    # add all passed categories to a dictionary with key = category and value = percentage of total spend
    for i in [cat1, cat2, cat3, cat4]:
        if i != None:
            PercentageSpend = getattr(i,'spend') / spending     #####here#####
            catdict[getattr(i,'name')] = PercentageSpend        #####here#####
        else:
            pass

The full code is here. Again, this works outside of Replit, so I am really struggling to understand why it would be different inside of it.

class Category:
    def __init__(self,name):
        self.ledger = []
        self.balance = float(0)
        self.name = name
        self.spend = float(0)

    #create custom representation for print(self) 30 characters wide left/right justified item and cost
    def __repr__(self):

        InstPrnt = ''

        #create header with name centered in asterisks
        starlength = int((30 - len(self.name)) / 2)
        line1 = starlength * '*' + self.name + starlength * '*'+'\n'
        InstPrnt += line1

        #split ledger into dictionaries
        for dict in self.ledger:
            WorkingDict = dict
            # split dictionaries into items, and add the values (cost, and description, respectively) to a list
            ledgerline = []
            for i, j in WorkingDict.items():
                ledgerline.append(j)
            cost = str('{:.2f}'.format(ledgerline[0]))
            item = ledgerline[1]
            #check spacing
            if (len(cost) + len(item)) > 29:
                item = item[0:(29 - len(cost))]
            space = 30 - (len(cost) + len(item))
            #add for each dictionary in ledger to the print string
            InstPrnt += (item + ' ' * space + cost + '\n')
        #add the total for the entire string, formatted as a monetary value (xxxx.xx)
        InstPrnt += 'Total: '+'{:.2f}'.format(self.balance)

        return InstPrnt

    def deposit(self,amount,description = ''):
        entry = {'amount': amount, 'description': description}
        self.balance += amount
        self.ledger.append(entry)

    def withdraw(self,amount,description = ''):
        entry = {'amount': -1 * amount, 'description': description}
        if self.check_funds(amount) == False :
           return False
        else :
            self.balance += -1 * amount
            self.ledger.append(entry)
            self.spend += amount
            return True

    def transfer(self,amount,budgetcategory):
        if self.check_funds(amount) == False:
            return False
        else:
            self.withdraw(amount,'Transfer to '+str(budgetcategory.name))
            budgetcategory.deposit(amount,'Transfer from '+str(self.name))
            return True

    def get_balance(self):
        return(self.balance)

    def check_funds(self,amount):
        if self.balance + -1 * amount < 0 :
            return False
        else :
            return True

#try to get it to recognize code blocks
def create_spend_chart(cat1 ,cat2 = None,cat3 = None,cat4 = None) :
    catdict = {}
    # get the total of spending across all passed categories
    spending = []
    for i in [cat1, cat2, cat3, cat4]:
        if i != None:
            spending.append(getattr(i,'spend'))
    spending = sum(spending)

    # add all passed categories to a dictionary with key = category and value = percentage of total spend
    for i in [cat1, cat2, cat3, cat4]:
        if i != None:
            PercentageSpend = getattr(i,'spend') / spending
            catdict[getattr(i,'name')] = PercentageSpend
        else:
            pass

    #create the y axis labels
    linedict = {'100|': 1.00, ' 90|': .90, ' 80|': .80, ' 70|': .70, ' 60|': .60, ' 50|': .50, ' 40|': .40, ' 30|': .30, ' 20|': .20, ' 10|': .10, '  0|': 0}
    output = ''
    #create bars by testing the value from the category dictionary against the value from the line dictionary
    for line in linedict:
        output += line
        for i in catdict:
            if catdict[i] >= linedict[line]:
                output += ' o '
            else:
                output += '   '
        output += '\n'

    # add the spacer bar
    output += '    ' + '-' * (3 * len(catdict) + 1) + '\n'

    # add the labels
    # find how many extra lines to add
    ExtraLines = len(max(catdict, key=len))
    # print(ExtraLines)

    for i in range(ExtraLines):
        output += ' ' * 4
        for key, value in catdict.items():
            try:
                x = key
                output += ' ' + x[i] + ' '
            except:
                output += '   '
        output += '\n'

    return output

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0

Challenge: Scientific Computing with Python Projects - Budget App

Link to the challenge:

I’m not sure why this works in PyCharm. Are you sure main.py or test module were run in PyCharm? create_spend_chart is expected to accept single argument, which is a list with categories to be included on the chart. That means, the i in for i in [cat1, cat2, cat3, cat4]: would be still a list.

1 Like

Ah, I think I understand, thank you.

I was looking to take in a value for each category

def create_spend_chart(cat1 ,cat2 = None,cat3 = None,cat4 = None)

You are suggesting I should only take in a single value, with that value being a list, correct?

def create_spend_chart([all_categories_in_a_list])

I’ll try that and report back.

@sanity

You were exactly correct. When I was testing it in PyCharm I was using

create_spend_chart(Food, Clothing, Auto)

while the test was entering the list passed from the main file

create_spend_chart([Food, Clothing, Auto])

This actually let me clean up some additional code meant to exclude optional arguments, too. And getattr() worked where I thought it did, when I was passing the argument in the right format.
Needed a bit of cleaning on the formatting for the chart, but in the end it passed everything.

Thank you so much!

1 Like

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.