0

I'm currently reading Python Crash Course by Eric Matthes and I'm having an incredibly difficult time understanding chapter 8 which is all about functions. I am stuck on exercise 8-10 which asks me to use a new function to change a list used in the previous exercise.

Here is the exercise:

8-9. Magicians: Make a list of magician's names. Pass the list to a function called show_magicians(), which prints the name of each magician in the list.

8-10. Great Magicians: Start with a copy of your program from exercise 8-9. Write a function make_great() that modifies the list of magicians by adding the phrase the great to each magician's name. Call show_magicians() to see that the list has actually been modified.

Here is my code for 8-9:

def show_magicians(names):
    """Print each magician name"""
    for name in names:
        msg = name.title()
        print(msg)

magician_names = ['sonic', 'tails', 'knuckles']
show_magicians(magician_names)

I've seen a very similar topic on this website and so I tried to use the code in the first answer on this page to help me out: Python crash course 8-10

However, my code still appears to be incorrect as the compiler prints 'the great' 3 times after each name.

Here is the current code I used for 8-10

def show_magicians(names):
    """Print each magician name"""
    for name in names:
        msg = name.title()
        print(msg)

magician_names = ['sonic', 'tails', 'knuckles']
show_magicians(magician_names)

def make_great(list_magicians):
    """Add 'Great' to each name."""
    for magician_name in magician_names:
        for i in range(len(list_magicians)):
            list_magicians[i] += " the great!"

make_great(magician_names)
show_magicians(magician_names)

I don't know why but it just seems that I've been struggling through out this entire chapter of functions. Does anyone by any chance have any recommended tutorials to take a look at to help me understand functions better? Thank you for your time.

4
  • Sorry do not have a recommendation for a resource but the problem is the double loop : for magician_name in magician_names: for i in range(len(list_magicians)): you only need the second one Commented Sep 11, 2016 at 22:30
  • In make_great() you have nested for loops - how many times does the inner loop run? Commented Sep 11, 2016 at 22:31
  • As you can see from my answer in the linked question, it used only one loop. You're using two. Commented Sep 11, 2016 at 22:37
  • @Andrew L, Oh you're right, I must have overlooked deleting my first for-loop before looking at your answer. Thank you for the clarification. :) Commented Sep 13, 2016 at 4:58

4 Answers 4

2

Ok, so you have an extra loop on the outside, remove that. The final code:

def show_magicians(names):
    """Print each magician name"""
    for name in names:
        msg = name.title()
        print(msg)

magician_names = ['sonic', 'tails', 'knuckles']
show_magicians(magician_names)

def make_great(list_magicians):
    """Add 'Great' to each name."""
    for index, item in enumerate(list_magicians):
        list_magicians[index] += " the great!"

make_great(magician_names)
show_magicians(magician_names)

You were doing a for-in along with a for-in range. That made the code repeat the appending of the string. Let me explain what your previous program did:

Explanation: On every iteration, you looped with a for-in, causing it to loop the inner loop 3 times. So, one every iteration of the outer loop, it would repeat the inner loop 3 times, making it append the great 3 times to every name.

Also, as the answerer of the linked question, I'd rather you use enumerate over range(len(list_magicians)).

Sign up to request clarification or add additional context in comments.

7 Comments

Instead of just giving a refactored answer, You should try to demonstrate what was wrong and why - maybe using some extra print statements.
Your make_great won't do anything but modify its parameter, then discard the modified parameter as it returns. You need to return the modified parameter, so that the caller can use it. I suspect this was how the OP ended up with a nested loop. The version that changed only list_magicians "didn't do anything" (more accurately, it did something and then discarded what it had done), so the OP added another loop, which explicitly affected the global variable magician_names.
@D-Von What exactly do you mean by 'modify its parameter'? The code works just fine as I don't use a for-in which just copies the value into a new variable. This is modifying the global list.
Eep, you're right. Sorry for the distraction. I've spent too much time in pass-by-copy languages. Here, you're passing the list by reference, so modifying the local list is the same thing as modifying the global list.
@D-Von No problem :)
|
1

Change the second method to:

def make_great(list_magicians):
    """Add 'Great' to each name."""
    i = 0
    for magician_name in magician_names:
        list_magicians[i] += " the great!"
        i += 1

Currently you are looping twice using two for loops causing it to be added 3 times!

5 Comments

I agree that this will work correctly, but it has two stylistic problems. First, the function ignores its parameter, which is confusing. Second, the function directly modifies magician_names, which is outside the scope of the function.
@D-Von magician_names isn't outside the scope of the function. magician_names is global but I do agree that this function is confusing in many ways.
@AndrewL. This is probably just a disagreement about the definition of the word "scope." I am using it to mean the new names introduced at the launch of the function. So I would say list_magicians is in the function's scope, magician_names is not, and magician_names is nonetheless visible inside the function, because it is at global scope. I think you are using "scope" to mean all the names visible within the function. True? I pity the poor newbie....
@D-Von Just something to point out, it'd be better to avoid long discussions in comments.
Thank you for your response, yes the second for-loop seemed to be the cause of my problem. ^^;
1

Let's look at your make_great function:

def make_great(list_magicians):
    """Add 'Great' to each name."""
    for magician_name in magician_names:
        for i in range(len(list_magicians)):
            list_magicians[i] += " the great!"

What does this do?

for magician_name in magician_names: Loops through magician_names.

for i in range(len(list_magicians)): Loops through list_magicians by index.

list_magicians[i] += " the great!" Adds the great! to the ith element of list_magicians

What line of code isn't necessary?

for magician_name in magician_names: Looping through this provides no use. Since its length is 3, that's why you get 3 the great!s added to each element. Remove that line of code and the function should work properly.

4 Comments

After removing the outer for-loop, the function won't do anything, because it will just modify its parameter, then discard the modifications. You need to add a return. This is probably how the OP ended up with the nested loop: the inner loop alone "didn't do anything," so he added the outer loop.
@D-Von: In his code, he calls make_great() without assigning the result to his list of names, so he probably wanted to change the list in place.
It's not clear he understands the difference between changing the list in place and changing a copy of the list. I suspect that's the reason for his confusion about functions. But I agree that his original code did change the list in place. Also, as was pointed out by @Andrew L. above, my comment is actually incorrect, since the list is passed by reference, not by copy. I have edited my answer to reflect this. Kindly ignore my comment to your answer! I am inclined to delete it, if Stack Overflow will let me.
Thank you for your answer, I believe that I need more practice with loops.
1

As another answerer said, your nested loops are doing basically the same thing, but for each time the outer loop runs, the inner loop runs. Both loops are trying to iterate over the list of magicians, one using the name the list goes by outside the function (magician_names) and one using the name the list goes by inside the function (list_magicians). Either way would work, but both is too many. From this point of view, your misunderstanding is not of functions, but of loops.

But the fact that you mention magician_names inside the function at all shows a misconception. It will work, but it's bad form. Usually, the code of a function should refer exclusively to names that are passed to the function via the parameters. So in this case, you should discard the loop over magician_names and keep the loop over list_magician. There are exceptions to the rule, but you should have a definite reason in mind before breaking it. The justification for the rule is encapsulation: you can completely understand the function by reading its parameter list and its code, without having to ask about the role of anything outside the function.

If you accept the idea that functions should operate only on their parameters, then you have to ask, So how does a function affect the outside world? One answer is that the function returns something, and the outside world voluntarily does something with what was returned. Another answer is that the function's parameter happens to point to an object in the outside world, so that changing the object inside the function automatically changes it outside the function. I prefer the first way, but it's probably easier in your case to use the second. So when you pass magician_names into your function, Python renames it to list_magicians; you operate on list_magicians inside the function, so magician_names also changes outside the function; you return from the function, and the name list_magicians goes away, but the changes to magician_names survive.

Here's a last bit of advice, which extends your knowledge to "list comprehensions," a topic you probably haven't learned about yet (but they're perfect for this application): I recommend writing a function to operate on a single piece of data, then calling that function repeatedly, rather than trying to do both the repetition and the single-item data modification in the same function. Like this:

def append_great(magician):
    return magician + " the Great"

With that function available, a list comprehension looks quite nice. It processes a list element by element, according to your instructions. Like this:

>>> magicians = [ "Houdini", "Fubini" ]
>>> [ append_great(m) for m in magicians ]
['Houdini the Great', 'Fubini the Great']

Note that the list comprehension returned a new list; magicians remains unchanged, unless you change it yourself. Which is how things ought to be. Functions shouldn't reach out and touch the outside world (unless there's a really good reason); instead, the outside world should give some data to the function, receive the result, and use the result as the outside world knows best. That's my advocacy for the return-a-value way of affecting the outside world, but as I said, in this case it's easier to use the pass-a-reference way.

1 Comment

Thank you for your long and detailed answer. To be honest, I'm having a bit of a difficult time understanding everything you typed probably because it's late and I'm tired on my end, but I'll take another look at your answer tomorrow and apply it to my current code to get a better idea of how looping works. :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.