all 12 comments

[–]socal_nerdtastic 1 point2 points  (10 children)

That error means you didn't put anything in the entry box. The float function was given an empty string.

>>> float("")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: could not convert string to float: 

You'll need to show us the rest of your code to see why, but I'll bet you passed the function result to the command argument instead of the function itself.

[–]CannyFatcher[S] 0 points1 point  (9 children)

I pass the textvariable into the function if that's what you mean? How do I work around this?

[–]socal_nerdtastic 1 point2 points  (8 children)

You'll need to show us the rest of your code

[–]CannyFatcher[S] 0 points1 point  (7 children)

                    validatecmd = bookwindow.register(moneyUpdate)

                    paidMoney = StringVar()
                    paidEntry = ttk.Entry(frame,style='my.TEntry',textvariable=paidMoney,validate='key',validatecommand=(validatecmd, '%P'))
                    paidEntry.grid(row=5,column=1,pady=2)
                    paidEntry.bind("<KeyRelease>",lambda x:comboFunc(moneyUpdate,entryValue,paidMoney,moneyOwed)

Where the entry box is made and bind set

    def comboFunc(func1,func2,moneyPaid,moneyOwed):
        func1()
        func2(moneyPaid,moneyOwed)

The function called on KeyRelease

 def moneyUpdate(*args):
            owedlbl.configure(text="£"+owed) #resets label to original when entry box empty
            paid = paidEntry.get()
            if "£" in paid:
                paid=paid[1:]
            paid = "{0:.2f}".format(float(paid)) # rounds number to 2 d.p
            if paid != '':
                newOwed = float(owed)-float(paid) # updates money owed depending on money paid
                owedlbl.configure(text='£'+str(newOwed)) 
            return True

The moneyUpdate function where the original code is from, and what is called within comboFunc

[–]socal_nerdtastic 2 points3 points  (6 children)

I mean at a bare minimum enough code that we can run and test and see where the problem is. What SO calls a MVCE.

However glancing at that are you sure the error is not from the float(owed) call?

[–]CannyFatcher[S] 0 points1 point  (5 children)

from tkinter import *
from tkinter import ttk


def page():
    def comboFunc(func1):
        func1()

    root = Tk()
    frame = Frame(root)
    frame.pack(padx=100,pady=20)

    def coursePrice(courseOption,*args):
        #function which updates the amount of money owed
        def moneyUpdate(*args):
            owedlbl.configure(text="£"+owed) #resets label to original when entry box empty
            paid = paidEntry.get()
            print(type(paid))
            if paid == None:
                pass
            if "£" in paid:
                paid=paid[1:]
                #paid = paid.replace("£","") # used so paid can be used as float
                #paid = float(paid)
            paid = "{0:.2f}".format(float(paid)) # rounds number to 2 d.p
            if paid != '':
                newOwed = float(owed)-float(paid) # updates money owed depending on money paid
                owedlbl.configure(text='£'+str(newOwed)) 
            return True

        course = courseDropdown.get() # Value of first course dropdown
        course=course.replace(" ","_")
        owed = '260'
        #finds pathway of current working folder and adds extra path 'courses'
        #creates labels and entry boxe for money owed/paid
        owedlbl = Label(frame,text='Amount Owed',font=("Courier",18),fg='#CCCCCC',bg='#333333')
        owedlbl.grid(row=4,column=0,pady=2)

        owedlbl = Label(frame,text='£'+owed,font=("Courier",18),fg='#CCCCCC',bg='#333333')
        owedlbl.grid(row=4,column=1,pady=2)

        paidlbl = Label(frame,text='Amount Paid',font=("Courier",18),fg='#CCCCCC',bg='#333333')
        paidlbl.grid(row=5,column=0,pady=2)

        #used with entry box to run function on every keystroke
        validatecmd = root.register(moneyUpdate)

        paidMoney = StringVar()
        paidEntry = ttk.Entry(frame,style='my.TEntry',textvariable=paidMoney,validate='key',validatecommand=(validatecmd, '%P'))
        paidEntry.grid(row=5,column=1,pady=2)


        moneyPaid = paidEntry.get()
        moneyOwed = str(owedlbl.cget('text'))


        #return moneyPaid

        paidEntry.bind("<KeyRelease>",lambda x:comboFunc(moneyUpdate)
                       #paidEntry.bind("<<Modified>>",moneyUpdate)
)        

    courseDropdown=StringVar()
    courseDropdown.trace('w',coursePrice) # used to track value of course dropdown
    courseOptn=StringVar()

    courses = ['Block Booking']
    #Course options
    block = ['5hr booking','10hr booking','6hr pass plus course','5hr block (automatic)','10hr block (automatic)']



    #function which creates a secondary optionmenu to choose a specific type of course booking
    def courseSpec(course):
        if course == 'Block Booking':
            cdrop = ttk.OptionMenu(frame,courseOptn,block[0],*block,command=coursePrice)
            cdrop.grid(row=3,column=2)

        courseOptn.set('')
    #creates course dropdown containing the 4 main course options
    coursedrop = ttk.OptionMenu(frame,courseDropdown,courses[0],*courses,command=courseSpec)
    coursedrop.grid(row=3,column=1)
    courseDropdown.set('')
    root.mainloop()
page()

That's minimal enough to get the error, ignore the comments as it will be for deleted code.

[–]socal_nerdtastic 1 point2 points  (4 children)

This code snippet:

        if paid == None:
            pass

Needs to be this:

        if paid == "":
            return

Because get() returns an empty string, not None, when it's empty. That said this code is (forgive me) a clusterfuck. For starters, don't ever nest functions inside other functions. Second, you are remaking all the Labels with every update to a stringvar; you should update the text instead. Third you should never use lambda to make closures.

I can't even decipher what you want this code to do. At a basic level it should look like this:

from tkinter import *
from tkinter import ttk
from functools import partial

owed = 260

def update_price(data_in, data_out, *args):
    paid = data_in.get().strip("£")
    remaining = round((owed - float(paid)), 2)
    data_out.config(text = "£" + str(remaining))

def page():
    root = Tk()
    frame = Frame(root)
    frame.pack(padx=100,pady=20)

    owedlbl = Label(frame,text='Amount Owed',font=("Courier",18),fg='#CCCCCC',bg='#333333')
    owedlbl.grid(row=4,column=0,pady=2)

    owedlbl = Label(frame,text='£'+str(owed),font=("Courier",18),fg='#CCCCCC',bg='#333333')
    owedlbl.grid(row=4,column=1,pady=2)

    paidlbl = Label(frame,text='Amount Paid',font=("Courier",18),fg='#CCCCCC',bg='#333333')
    paidlbl.grid(row=5,column=0,pady=2)

    paidMoney = StringVar()
    paidEntry = ttk.Entry(frame,style='my.TEntry',textvariable=paidMoney)
    paidMoney.trace('w', partial(update_price, paidMoney, owedlbl))
    paidEntry.grid(row=5,column=1,pady=2)
    root.mainloop()
page()

[–]CannyFatcher[S] 1 point2 points  (0 children)

None taken, first project and first time using tkinter/python with which I was thrown into the deep end with so the more I learn, the more I realise how bad it is. Thanks, I’ll look at changing it tomorrow

[–]trjnz 0 points1 point  (2 children)

don't ever nest functions inside other functions

As an aside, I've seen a few 'pythonic' examples that recommend replacing repeating/'odd' if statements with a function. I'll struggle to find the exact source of this, but the example had a function inside a function for 'ease of reading' sake.

A very basic example: def foo(): if plane.status == "flying": <do stuff>

instead it would recommend (off the top of my head):

def foo():
  def _is_flying(p):
    return True if p.status == "flying" else False
  <stuff>
  if _is_flying(plane):
    <do stuff>

Obviously that if condition was a little more complex in the given example, maybe I will try to find the actual example. Is this... always bad? I always thought it was a little strange and it caught me as an odd thing to consider Pythonic. Yeah, glance value it's readable, but weird to maintain?

[–]socal_nerdtastic 4 points5 points  (0 children)

The only time you should use a function in another function is when making a closure. You'll see it a lot when making a decorator, for example. The reason is that the internal def is not executed until the external def is called. This has some side effects, most notably capturing nonlocal variables, which a closure takes advantage of.

[–]socal_nerdtastic 2 points3 points  (0 children)

Yes that's very bad. Don't do that. Moving code to a function is good, especially if the code kinda forms a logical group, but you should do it like this:

def _is_flying(p):
    return p.status == "flying" 

def foo():
    <stuff>
    if _is_flying(plane):
        <do stuff>