Why do I need an if statement for the mutable argument workaround with "None"

Hello, I wrote this on Christmas eve, so Merry Christmas to anyone celebrating and happy holidays.
I am learning about mutable function arguments and I can’t seem to understand why the need to provide an if statement checking for if the mutable argument is set to None? Is it preventing someone from providing values to the mutable argument before initializing (I.e. after the first call)?

This is the example given in Codecademy:

def createStudent(name, age, grades=None): if grades is None: grades = [] return { 'name': name, 'age': age, 'grades': grades } def addGrade(student, grade): student['grades'].append(grade) # To help visualize the grades we have added a print statement print(student['grades'])

hi @antinomy-0 ,

Although i’m not quite sure what’s the objective, but my guessing here is other functions do rely on the ‘grades’ key/variable generated from createStudent function. In order to utilize .append , your variable (in this case, ‘grades’ object) must be allocated as an array type in order for it work, thus the if statement is there to prevent error from happening.

You can try it out by placing try…catch… pattern to understand further.

Happy Coding :grinning: And Happy :boxing_glove: :package: day to yea!

1 Like

It’s a common pattern you’ll see when one parameter should be a mutable type that may not always be passed to function (it has a default). For example append some new values to either the list that is the second argument or use an empty list. The problem with using mutable types as default arguments is that they are only evaluated once, the first time the function is defined. So if you mutate that default argument it will remain changed from then on.

In the example below each call to the function that does not pass a second argument modifies the default argument used for lst (as does mutation of the returned object).

def func(x, lst=[]): lst.append(x) return lst # fine we pass a second argument print(func(3, [1, 2])) # make use of empty list as second arg print(func("f")) # our "empty" list is longer empty! print(func(2)) # Worse still the result IS the default! # so if you mutate it... result = func(0) result.extend(range(10)) print(func("red"))

This is often unexpected or unwanted behaviour, especially the second kind. Instead you’ll often see None used as the default argument and an if statement creates a new mutable type on each new call instead.

1 Like

Just to understand this more in-depth, what would the iteration through the if statement look like?
If we follow the function call it would be:

  1. set grades to default which is, in this case, None (resetting the list for the purpose of usage in this function call)
  2. check if it is none
  3. initialize a new one since it was none and add the intended values of the call

am I right?

How would I apply the try-catch pattern? I haven’t used catch before, I will google it for sure, but I wanna see how you would use it in this case to expand on the need to use if here :smiley: thanks <3

Aye if you passed no second argument (or I suppose if you deliberately passed the None object) then the function local name lst would be assigned to None before any of the function body is executed. Then it’s as you expected.

1 Like

hi @antinomy-0 ,

I see, pardon me as I’ve no idea which level of learning phase you’re at but certainly you’re welcome to ask questions for improvements. Nonetheless, thank you for your patience, and pro-activeness, you can refer to the codes in the following CodeByte example.

# With default value 'None' def createStudent(name, age, grades=None): if grades is None: grades = [] return { 'name': name, 'age': age, 'grades': grades } # Without default value 'None' def createStudentTwo(name, age, grades): if grades is None: grades = [] return { 'name': name, 'age': age, 'grades': grades } # def addGrade(student, grade): # student['grades'].append(grade) # # To help visualize the grades we have added a print statement # print(student['grades']) stud_list = ["John", "William", "Raymond"] students = [] studentsTwo = [] for student in stud_list: try: students.append(createStudent(student, 10)) except TypeError: print("Missing Grades array") print(students, "\n") for student in stud_list: try: studentsTwo.append(createStudentTwo(student, 10)) except TypeError: print("Missing Grades array") print(studentsTwo)

I’ve duplicated another example from your old to show what happened if the default value wasn’t assigned in the createStudent function.

Happy Coding!

1 Like

I see, it’s basically saying there is no array because we didn’t set it to None and therefore in this context didn’t create the same object as we intended?

This is kinda like reverse-engineering the TypeError :smiley:

Thank you so much, don’t apologize, I do apologize for taking so much of your time to write that code in the CodeByte. :heart:

Is it essentially like two variables working in two different sets and one of them has the ability to interact with the other set?

I don’t know if I’m making sense, thank you so much for replying and answering my questions :purple_heart:

Hmm, I’m sorry I’m not sure what you mean there (tried to add some detail but if it’s not what you’re after then say so).

The given example simply creates a new empty list and assigns it to the local name grades if the grades name previously referenced the None object.

if grades is None:
    grades = []

Under other circumstances it is expected that grades was passed as an argument so there’s no need to create an empty list.

1 Like

Yes that’s correct.

In a way, you can say so? But try-except pattern does more advance stuffs with error handling as you can personalized the messages and stuffs.

And you’re welcome :smiley: pleasure to help!

1 Like

Expected from a Maya fan <3 much love, thanks a lot for the help.

I was just comparing the example of grades being initialized prior to the function call vs when the function call happens.