1

Python Crash Course (1st edition), by Eric MATTHES

Exercice 8-10 Great Magicians

"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 called 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."

In the chapter, the author makes it quite clear that invoking a list as an argument in my defined function WILL modify said list (which is the goal, here). To the extent that, to avoid unwanted modifications to the original list, one should use as an argument to one's function "list[:]" instead of just list.

Well, modifying the list magicians in my function is precisely what I'm trying to do, through the following code. To no avail, so far. If anyone was so kind as to explain why, I would greatly appreciate it. Also, having just created a SO account for the occasion, I might be missing something about the etiquette, question-formatting or other idiosyncracies of the platform. Be sure to enlighten and correct me on these topics as you see fit.

Thanks in advance.

Code:

magicians = ["Danton", "Professor", "Fallon", "Tesla"]

def make_great(whocares):
    """making great"""
    for x in whocares:
        x = x + ", the Great"

make_great(magicians)
show_magicians(magicians) # The list magicians remained as it was: Why?

I did find a (heavy, ugly) way to fix it. Which looks more like a kludge than anything else: popping each original magician out of their magicians list, appending him (with the "Great" suffix) in a new temporary list, then plugging said new list with greatified magicians back into now empty original magicians list.

I can't help but feel this is missing the point of the exercice as it is designed: since the author was very explicit about how arguments magicians -allowing modifications to the list from within my make_great function- magicians[:] -building a copy, thus preventing modifications to the magicians list from make_great function- should be used, the exercice should be manageable without using a new list as a buffer.

  • Thank you very much. I'll try just that. Quick follow-up questions: 1°) Why do I need to use range, exactly? On a theoretical level, I mean. Is it just a matter of "Oh yes, Python is weird this way, on this very specific procedure", or is there some sort of an underlying logic I might have a chance of grasping? 2°) What would be an elegant, "pythonic" way to achieve this result (beyond the scope of the exercice)? – Derniers Outrages Nov 06 '22 at 04:23
  • Thanks again. It does. It seems I just shouldn't modify elements from a list via an iterative loop, period. I did not know that, and in fact thought this was precisely what the exercice was about ^^ – Derniers Outrages Nov 06 '22 at 04:31
  • In Python, the string is immutable, and the list is mutable. When you are doing lst[i] = j, you are modifying the list, but for x = x + ", the Great", you are trying to modify the string x whose scope is only the current iteration. Check out [here](https://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference) for more details. – Steven Nov 06 '22 at 04:36
  • @Steven Ok. So basically, I'm allowed to modify an element of a list *when treated as a whole, as a list precisely*, using list[index]. But trying to modify an element of a list as an isolate, as a random string/value/whatev' will fail. Correct? – Derniers Outrages Nov 06 '22 at 04:42
  • @DerniersOutrages It depends on what is inside the list. If you have lists inside lists, you can modify (e.g. append) the lists (but not assigning them as a new list). – Steven Nov 06 '22 at 04:47
  • I think I more or less understand what you mean. I cannot fully grab and hold on to an element of a list from a process entirely distinct from its list. I can act on it to a certain extent (checking a value of an element, appending something to an element if the element is a list), but not do anything too major to it (deletion, modification of its core, etc) treating it as an isolate. To do this, I should use a procedure referencing it as an element of the list it's coming from, lest Python start treating it as a copy ad hoc of the element I'm trying to modify. – Derniers Outrages Nov 06 '22 at 04:49
  • Nowhere in your code do you either modify the list itself or modify one of the objects in the list. – juanpa.arrivillaga Nov 06 '22 at 06:13
  • @juanpa.arrivillaga I thought "for x [element of the list] in whocares [the list]: x = x + "bla bla bla" modified the element. I can clearly see it does not modify the element, hence the problem. And some answers here have partially helped me get a feel for what I didn't understand. But simply stating "you're not modifying any element" as if I had simply forgotten to reference any element of any list in my code doesn't do me any good. I obviously tried to reference an element from a list (x in whocares), and tried to change its value, even though I admittedly failed at it. – Derniers Outrages Nov 06 '22 at 12:35

1 Answers1

0

When you have

for x in whocares:
    x = x + ", the Great"

This gives the name x to the result of your addition, which is lost at each iteration of the loop!

This gives a somewhat dated, but great overview of what is happening
https://david.goodger.org/projects/pycon/2007/idiomatic/handout.html#python-has-names

Instead, build a new list or reassign the values directly to the original array

Packing a new array

results = []
for _ in _:
    results.append(...)
return results  # caller renames like mylist = function(mylist)

Modify list members via method

def my_function(my_list):
    for index in range(len(my_list)):
        my_list[index] += ...  # mylist is updated

Assign to original list

def my_function(my_list):
    for index, value in enumerate(my_list.copy()):
        my_list[index] = value + ", the Great"
ti7
  • 16,375
  • 6
  • 40
  • 68
  • Thank you for both the "handout" and the last example "assigning to original list". This is exactly what I was trying to achieve and failed at. I did not know of the enumerate() function. But I will look into it today. I also need to fully explore this range() business in the context of iterative loops I have been hinted at in earlier answers. Thanks a bunch. – Derniers Outrages Nov 06 '22 at 12:44
  • anytime - though I would not call such a thing a _handout_ and more site purpose as you've done your due diligence and have an honest, answerable Question [more about the other side](https://softwareengineering.meta.stackexchange.com/questions/6166/)! [this may help you get started with `range()` at al. too](https://stackoverflow.com/questions/30081275/)! some officious words about site expectations https://stackoverflow.com/help/someone-answers .. finally, the latter two examples are largely the same and are a general computer science style known as "by-reference" (lists hold references) – ti7 Nov 06 '22 at 16:47
  • I thak you for all these links. I cannot say I fully understood the crux of the subject in the range discussion, about how range() could be construed by some as a generator while others maintain it is truly a sequence, or how it relates exactly to my initial issue. But then again, I started Python a week ago and am trudging my way through a beginner's book, so I probably shouldn't expect to, for now. Thank you for the kind words and the link to "Code like a Pythonista" : it made my day answering another question I had when it explained how "unpacking" works. Cheers. Answer accepted. – Derniers Outrages Nov 08 '22 at 13:37
  • in Python, objects are "duck typed" (if it quacks like a duck it is one), so a `range()` is an _iterable_ because it has an `__iter__()` method! and it's a [sequence](https://docs.python.org/3/library/stdtypes.html#typesseq) because it has a `__contains__` method and a `__len__` method and more (which here provide dedicated logic to directly determine if a value is _contained_ within its _range_ and how many values would be generated without generating them all)! in a simplistic view, `__iter__` implements `for`, and `__contains__` implements `in` – ti7 Nov 08 '22 at 17:24