Question

I'm trying to create a python 2.7 tkinter module which uses scale widget data to influence a list comprehension which selects between animals whose probability traits are represented as a list of lists. The module sorts and displays the three animals in ranked descending order upon clicking 'Submit' and activating the associated command.

In this example, all three animals are at 33% after clicking 'Submit' because they share the same probability data. The animals only differ among the scale widget data in column 2 of the list of lists in that each is either aquatic, terrestrial, or both.

from Tkinter import BOTH, BOTTOM, Button, E, END, Entry, FLAT, Frame, Grid, HORIZONTAL, Label, LEFT, N, NO, Pack, RAISED, RIGHT, S, Scale, Text, Tk, TOP, W, YES

from operator import mul

root = Tk()
root.title('Example')

class Environment:
    def __init__(self, parent):

        # layout
        self.myParent = parent

        self.main_frame = Frame(parent, background="light blue")
        self.main_frame.pack(expand=YES, fill=BOTH)

        self.main_left_frame = Frame(self.main_frame, background="light blue")
        self.main_left_frame.pack(side=LEFT, expand=YES, fill=BOTH)

        self.main_right_frame = Frame(self.main_frame, background="light blue")
        self.main_right_frame.pack(side=RIGHT, expand=YES, fill=BOTH)

        self.water = Scale(self.main_right_frame, from_=0.01, to=1.00, orient=HORIZONTAL, bd=0, label="Aquatic",
        background="white", troughcolor="cyan", length=50, width=10, sliderlength=10, resolution=0.01)
        self.water.pack()
        self.water.set(1.00)

        self.soil = Scale(self.main_right_frame, from_=0.01, to=1.00, orient=HORIZONTAL, bd=0, label="Terrestrial",
        background="white", troughcolor="saddle brown", length=50, width=10, sliderlength=10, resolution=0.01)
        self.soil.pack()
        self.soil.set(1.00)

        self.id_frame = Frame(self.main_left_frame, background="white")
        self.id_frame.pack(side=BOTTOM)

        # submit button
        self.submitbutton = Button(self.main_left_frame,text="Submit", background="black", foreground="white",
        width=6, padx="2m", pady="1m")
        self.submitbutton.pack(side=TOP)
        self.submitbutton.bind("<Button-1>", self.submitbuttonclick)
        self.submitbutton.bind("<Return>", self.submitbuttonclick)

        #Animal Matrix
        self.animal = [
        ('Odocoileous virginiana','White-tailed Deer',self.soil.get,0.99,0.01,0.99),
        ('Anguilla anguilla','American Eel',self.water.get,0.99,0.01,0.99),
        ('Trachemys scripta','Slider',lambda:self.soil.get()*self.water.get(),0.99,0.01,0.99)]

    def submitbuttonclick(self, event):
        self.id_frame.destroy()
        self.id_frame = Frame(self.main_left_frame, background="white")
        self.id_frame.pack(side=BOTTOM)

        A=self.animal

        #equation
        sigma = float(sum(reduce(mul,item[3:]) for item in A))
        B = [(item[0], "%.2f" % (item[2]()*reduce(mul, item[3:])/sigma)) for item in A]
        C = sorted(B, key=lambda item: item[1], reverse=True)  

        Label(self.id_frame, text = C[0], background = "white").pack(side=TOP, anchor = W)
        Label(self.id_frame, text = C[1], background = "white").pack(side=TOP, anchor = W)
        Label(self.id_frame, text = C[2], background = "white").pack(side=TOP, anchor = W)

environment = Environment(root)       
root.mainloop()

Thanks to many contributed improvements, this code works!

Was it helpful?

Solution

The first thing that I notice is that you define A to be an empty dictionary, and then overwrite that empty dictionary with self.animal, which is a list.

    A={}
    A=self.animal

So I'm not sure what you mean to do here. Then in your definition of B you slice it:

    B = [(A[0], "%.2f" % (reduce(mul,A[3:])*A[2][i]/sigma*A[2][i])) for A in A]

This doesn't square with either definition of A, because you can't slice a dict, but the starting index you chose is 3, and the highest index in self.animal is 2. Confusing! But looking more closely, it becomes clear that the problem is that you're reusing A as an indexing variable. You really shouldn't do that; it makes this code incredibly confusing.

It can also cause errors. Consider this code:

>>> a = range(10)
>>> [a for a in a]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a
9

As you can see, the list comprehension causes a to refer to the last value in the sequence previously known as a. This doesn't happen in your code because you used a generator expression. But it's still hard to read and confusing. I strongly recommend doing this instead:

    sigma = float(sum(reduce(mul,item[3:]) for item in A))
    B = [(item[0], "%.2f" % (reduce(mul,item[3:])/sigma)) for item in A] 

Update: OK, having made those changes, you still need to get the data from the scales. In your definition of self.animal, you use self.soil.get(), like so:

('Odocoileous virginiana','White-tailed Deer',self.soil.get(),0.99,0.01,0.99)

This puts the return value of self.soil.get() in a tuple. But then that value is fixed -- it won't ever change. You have to explicitly call self.soil.get() every time you want the updated value. Also, your list comprehensions don't ever access the value returned there. You slice them like so:

>>> l = ('Odocoileous virginiana','White-tailed Deer',
...      self.soil.get(), 0.99, 0.01, 0.99)
>>> l[3:]
(0.98999999999999999, 0.01, 0.98999999999999999)

Remember that indexing in lists and tuples starts with 0 -- so in the above tuple l, l[0] == 'Odocoileous virginiana'. So if you want everything but the first two things, you have to slice from index 2:

>>> l[2:]
(0.55000000000000004, 0.98999999999999999, 0.01, 0.98999999999999999)

But this still doesn't solve the root problem, which is that you have to call self.soil.get() to get the updated data. One way you could do that is simply by recreating self.animal every time the submit button is hit. That would be wasteful but it would work. A less wasteful (but still awkward) approach would be to save the function itself in the tuple, instead of the result of the function. You'd do that like so:

>>> l = ('Odocoileous virginiana','White-tailed Deer',
...      self.soil.get, 0.99, 0.01, 0.99)

Note the absence of () after self.soil.get. Now the tuple contains not a floating point value, but a function that returns a floating point value. You have to call it to get the value, but it returns the fully updated value every time. To combine functions, you can use lambda:

>>> l = ('Odocoileous virginiana','White-tailed Deer',
...      lambda: self.soil.get() * self.water.get(), 0.99, 0.01, 0.99)

Now you can call l[2] to get a value:

>>> l[2]()
0.30250000000000005

So to put it all together, you have to break up the list comprehension a bit more to explicitly call l[2], but once you've done that, this should work. This is not an ideal setup, but I'm afraid I have to leave creating an improved architecture as an exercise for the reader.

OTHER TIPS

A[2][i]/sigma*A[2][i] this bit is trying to index the float. Should this be: A[i]/sigma*A[i] instead?

I did make the assumption that the values in A are all floats.

The for A in A part seems a bit dodgy to me. It may be syntactically corrent, but it's ususally clearer to use different names for collections and elements in those collections.

Also for isolates in A: i = A.index(isolates) can be made much more effective by using for i, isolates in enumerate(A), as A.index(isolates) can take a long time when A is big.

I know, not really an answer to your question, but I hope it's useful nonetheless.

To make it easier to debug (and to actually help you out), please rewrite this:

[(A[0], "%.2f" % (reduce(mul,A[3:])*A[2][i]/sigma*A[2][i])) for A in A]

into something a bit more readable. If you split it into something with multiple lines you can actually use a debugger, and easily see what variable is a 'float' and where it's being indexed.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top