Thrown for a loop by Loops

Hi,

I have gone through the Loops section of the Python 3 course, but I am still stumped by loops.

For example, why when you loop through a list and print the elements of that list, does Python print the elements but they are no longer in a list structure?

Here is the link to the exercise:
https://www.codecademy.com/courses/learn-python-3/lessons/learn-python-loops/exercises/introduction

This is what the code looks like:

dog_breeds = ['french_bulldog', 'dalmatian', 'shihtzu', 'poodle', 'collie']
for breed in dog_breeds:
    print(breed)

Also, why do you sometimes define a variable outside of the for loop to later use within the for loop, but other times you need to define a variable within the for loop?

For example, if I wanted to alter a list, why would the code need to look like this:

my_list = [ "cats and dogs", "frogs and toads"]
empty_list = []
#add a 1 to each element in my_list
for phrases in my_list:
  new_list = phrases + 1
  empty_list.append(new_list)
return empty_list

Why couldn’t the code just alter my_list using the empty_list variable defined outside of the for loop instead of also defining another variable (new_list) in the for loop?

Thank you!

When you loop (or iterate) through a list, the list lives on; looping through it does not damage the list.

There are two ways to “change” a list. On way is to create a new list, which is a duplicate of the original list, but with the changes in place. In this case, the original list is unchanged. Generally, if a function begins withnew_list = [], it is this technique that is being used. Since you can’t add strings and ints (as in new_list = phrases + 1, above), I’ll modify the function slightly:

def add_one(lst):
    empty_list = []
    #add a 1 to each element in my_list
    for num in lst:
      new_num = num + 1
      empty_list.append(new_num)
    return empty_list
my_list = [ 1,2,3]
print(add_one(my_list))  # This will print the output of the function: the variable empty_list
print(my_list)   # This will print the original my_list after the function has run

# Output:
[2, 3, 4]  # empty_list
[1, 2, 3]  # my_list - unchanged

The other way is to change (mutate) the list in place. That means that the original list - the one you started with - comes out altered. This involves using either list indexing or certain list methods. If you have not yet dealt with list indexing, you may want to just bookmark this thread and return to it when you do. At any rate:

def add_one(lst):    
    #add a 1 to each element in my_list
    for idx in range(len(lst)):
      lst[idx] = lst[idx] + 1   # Using the index gives you direct access to the list element
    return lst
my_list = [ 1,2,3]
print(add_one(my_list))
print(my_list)

# Output
[2, 3, 4]   # return value from the function
[2, 3, 4]   # the original list, now altered

It is actually customary to omit the return statement, since the list is altered by simply running the function. The point is that you are actually going into the list and changing the elements, one by one.

@victorialv, It starts getting weird when we find out we don’t need to return the list if it’s been mutated in-place (in situ). It’s because of how many languages, including Python, treat values. It’s a way to conserve and manage memory–have only one representation of any one object and never duplicate.

The data structure (in this case a list) we pass to a function may have a different name locally, but it is still the same object, hence no need to return if the object being manipulated by the function is a data structure. Python only has one copy of it in memory.

Thank you patrickd314.

What I am still wondering is why if you print the values of a list within a for loop, do the values show up as a string in the terminal and not a list?

Also, why do we need to define two variables - one outside of the for loop and one inside of the for loop?

1 Like

Thank you mtf.

When you say it if a list is passed through a function it may have a different name locally but it is still the same object - in the code I provided there are two different variables looking at the list (new_list and empty_list) - do both of those variable still refer to the same object?

L = [1,2,3]
for item in L:
    print(item)
print(L)

# Output
1
2
3
[1, 2, 3]

Going through that loop, the for expression one at a time takes a list element (in this case, 1, then 2, and then 3) and assigns its value to the iterator variable, item.

After each assignment, the loop block or body is entered. From within the block, only the variable item exists, with its current value. The loop block does something with the value, in this case, sends it to the screen as a line of text, which is what print() does. The process is repeated for each element of the loop.

Of course, printing each element is a fairly trivial example. We can do anything we want with the list elements. Each element might be an entire student record, for instance, and the loop block might have the task of averaging a certain sequence of grades, determining “Pass” or “Fail”, and emailing the result to the student.

We can also ask print() to print the entire list, as shown in the last line of code.

Are you referring to this?

def add_one(lst):
    empty_list = []
    #add a 1 to each element in my_list
    for num in lst:
      new_num = num + 1
      empty_list.append(new_num)
    return empty_list

The “inside” variable new_num is an intermediate variable, put in for clarity. I think that this is what you are referring to as the variable inside of the loop. It isn’t really needed at all, and could easily be omitted; the next line would then just be: empty_list.append(num + 1)

The reason for the “outside” variable empty_list is to provide a vehicle for the new list being created, the stated purpose of this function.

The only other variable is num, the iteration variable required by the use of for.

In my second example, the one where the original list is mutated, no new variable names are needed. (I have made an edit there where I see that I was inconsistent about this. I hope it is clearer now.)

As @mtf points out, there is only one list in this case. I happened to call it my_list outside of the function, and lst within, but it is still one list, passed from the calling statement to the function as an argument. There is no statement within the function that declares a new list.

1 Like

Thank you, @patrickd314!!!

1 Like

Not if one is just a construct of the other. When there is a second list that is fabricated from the terms in the other, it is independent and would need to be returned. it’s only when we mutate the list that was passed in would we not need to return it.

Lists and dictionaries are passed by reference only. The object stays where it is, outside of the function. Regardless what we call the object inside the function it still references the outside one.

In fact, we don’t even need a parameter, but that would make the function dedicated to a single list. Functions should be re-usable by other parts of the program, so using a parameter lets us pass in any list and have the function operate on that one.

a = [2, 5, 8, 11]
b = [3, 7, 11, 15]

def double_at_index(array, index):
    n = len(array)
    if -n <= index < n:
        array[index] *= 2

double_at_index(a, 3)
double_at_index(b, 2)

print (a, b)    # [2, 5, 8, 22] [3, 7, 22, 15]

Notice we didn’t need a return? array took the reference to the list named in the argument and the function operated on that list.

This is the TOUGHEST section so far. I have spent 4 days on Loops and still cannot get the answers right especially the challenge questions. what am I doing wrong ?? please advice. Sorry for the rant