Iterates Once, and Only once

Hello,

I have an issue with a certain part of my code. I create the following list which holds the values read in from the user via tk.Entry widgets for the purspose of iteration so that I can condense my code. It is created inside the init method at the time of initialization. Note that the inputs read in are technically strings but what is entered are digits and floats. For example: 7, 1.3, 8, 3.4, etc.

# Create list for iteration purposes in get_poles_zeros() method
self.values = [self.zeros1, self.poles1, self.zeros2, self.poles2, self.gain]

I have a button ('Plot’) which provides a callback to the following method:

    def get_poles_zeros(self, *args): 

        j = 0

        for item in self.values:
            
            temp = item.get()
            print(temp)
            value = [float(s) for s in re.findall(r'-?\d+\.?\d*', temp)]
            self.values[j] = value
            j += 1

        self.plot_tf()
        print('Exited get_poles_zeros()')

When I run my code, it works fine, for the first run.

I then press another button (the Reset button), which has the following callback method
(it clears all of the entered strings/values in the tk.Entry widgets):

    def clear_entries(self):

        self.gain.delete(0, 'end')
        self.zeros1.delete(0, 'end')
        self.zeros2.delete(0, 'end')
        self.poles1.delete(0, 'end')
        self.poles2.delete(0, 'end')

After pressing the Reset button, I reenter new values into the tk.Entry inputs for a new calculation. When I press the ‘Plot’ button, I receive the following error:

AttributeError: 'list' object has no attribute 'get'

Can someone please advise.

You’re starting by setting self.values to a list of widgets.

Then, in get_poles_zeros, you’re replacing the items (widgets) in self.values with lists. Why?

When you run get_poles_zeros a second time, the for loop is iterating over what is now a list of lists, not a list of widgets.

Hello,

thank you for responding to my query. I went ahead and rewrote my code a bit. I now have it working as expected. However, there is a bit of a work around that I had to implement in order to do so.

Here is my new code.

# The list is defined in the init method
self.values = []

    def get_poles_zeros(self, *args): # *args argument because accessed by more than one widget

        a = self.zeros1.get()
        b = self.poles1.get()
        c = self.zeros2.get()
        d = self.poles2.get()
        e = self.gain.get()
        
        self.values.insert(0,a)
        self.values.insert(1,b)
        self.values.insert(2,c)
        self.values.insert(3,d)
        self.values.insert(4,e)

        for i in range(len(self.values)):
            print('i = :', i)

            if i < 5:
            
                temp = self.values[i]
                value = [float(s) for s in re.findall(r'-?\d+\.?\d*', temp)]
                self.values[i] = value
            
        self.plot_tf()

I had to implement the ''if i < 5:" conditional statement because on the second go around, it would go to i = 5, and the value for i = 5 was the the value of ‘a’ from the first go around which is kind of strange.

******************************* UPDATE *******************************************

I retested the code. I notice that the i keeps increasing by multiples of the length of declared list.
Back to the drawing board.

This method is meant to reset the widgets that were put into the self.values list. But after get_poles_zeros is called, self.values doesn’t contain those widgets any more. Instead, it contains the lists that were calculated from the list comprehension.

If you wanted to calculate some values (a list of lists… I guess a list of numbers that were input into each text widget?), they should be put into a different list. I assume that the idea right now is, self.plot_tf will check the self.values to find the numbers it should use. But this is using self.values to mean to completely different things, which is a recipe for errors.

It’s not necessary to store the values at all, actually. Instead, you can pass that list as an argument to self.plot_tf, and modify it to expect that parameter. This is the normal way to use functions - and methods - in Python. Please don’t store things in an object’s attributes just because you want to send information from one method to another. An object is supposed to represent a coherent, logical… object (a fancy word for “thing”); a class is supposed to represent the type (a fancy word for “kind”, but also more rigorously defined - and some programmers also have a different, rigorous definition for “kind”) of an object (in the way that the word “human” explains what kind of thing you and I are); and the attributes of an object are supposed to represent, conceptually, the components of the object.

As another quick hint: it’s possible to list nest comprehensions; and creating a new value that way means that you don’t need to worry about tracking a j index manually.

Rewritten code (also without the debug messages) could look like:

    def get_poles_zeros(self, *args): 
        self.plot_tf([
            [float(s) for s in re.findall(r'-?\d+\.?\d*', item.get())]
            for item in self.values
        ])

Fixing plot_tf to match is left as an exercise.

If you think that is doing too much at once, one way to split it up is by using a separate function. Then you can give a useful name to the float-conversion step:

# Utility function outside the class
def floats_from_text_widget(widget):
    return [float(s) for s in re.findall(r'-?\d+\.?\d*', widget.get())]

# Method inside the class
    def get_poles_zeros(self, *args): 
        self.plot_tf([floats_from_text_widget(item) for item in self.values()])

Assuming plot_tf can handle a generator, that can be even simpler:

    def get_poles_zeros(self, *args): 
        self.plot_tf(map(floats_from_text_widget, self.values()))

This is indeed a workaround - fixing the problem properly is straightforward. Instead of trying to fix the problem that conflicting data was put into self.values, just don’t put it there. Instead, design the rest of the code to expect it somewhere else.

Yes; that’s because .insert means to put a new element into a list and leave everything else in as well - not to replace anything. When you insert the .get results into the list that original contained the widgets, the widgets are still there. Then the next time, the text from the first time is also still there. And so on.

Hi,

thank you for your response. I am still reviewing it. But noticed this part from a text book:

Method --------------------------> Description
insert(p,x) -------------------------> inserts x at index p of the list

From the wording, I interpreted x as the value to be inserted and p as the index of the list.

Yes, that is correct. But “insert” means to put something in between other existing things. The other things are all still there. It is the same as when you click in the middle of your text and start typing again: the rest of the text shifts over to make room for what you’re typing.

(This is related to the history of why there is - probably! - an “Insert” key on your keyboard. It used to be that everyone - at least in North America and Europe - expected to use a typewriter font for everything, and only have to worry about European languages; so it made perfect sense to be able to “overwrite” text by going back to a previous point and then typing a replacement character - it would always take up the same amount of space, visually, as the character it replaced. The purpose of the insert key is to switch between an “insert mode” and an “overwrite mode”, i.e., to control what happens when you go back in the text and start typing again. And of course, by “go back” I mean with the arrow keys; you couldn’t expect to have a mouse back then.)

1 Like

Thank you so much. After this clarification, I went ahead and declared the list explicitly with 5 indexes. As shown here:

self.values = ['', '', '', '', '']

Declaring it this way, and with the following assignments:

self.values[0] = a
self.values[1] = b
self.values[2] = c
self.values[3] = d
self.values[4] = e

It is working fine … i no longer increases during every callback. Note that this was my original approach. However, since it did not work with this approach: ‘= [ ]’, I used the approach using the ‘insert’ keyword. Kind of like a dog chasing its tail.

Well, like I stated, it is now working as expected. However, I will also be reviewing your other suggestions to learn from.

Thank you again for nudging me in the right direction. Greatly appreciated! :smile: