Recent chats
ASKGEEZ Q&A GUIDETechnology & devices

Why does a function change my list even though it returns nothing?

Quick answer

A function can change a list because the argument refers to the same mutable list object your code uses. The change is visible to the caller even if the function has no return statement. Returning a value is separate: a function that builds and returns a new list gives you a result to assign, while an in-place change updates the existing list. Python list methods such as append also modify the list and return None. [S1] [S2]

LET’S WORK THROUGH IT

The question, the details, the next step.

Question

I wrote a function that adds something to a list, but I didn't return anything. After calling it, the list outside the function has changed. How?

Geez

The function receives a reference to the list object you passed in. Since a list is mutable, changing that object inside the function is visible through the name you use outside it. A return statement is a different operation: it provides a value back to the caller. If a function reaches its end without returning a value, Python supplies None; that does not undo changes already made to a list. [S1]

For example, this function changes the existing list:

def add_label(items):
    items.append("new")

labels = ["draft"]
result = add_label(labels)
print(labels)  # ['draft', 'new']
print(result)  # None

The list changed; the function's result is still None.

Follow-up question

Is that because append() returns the changed list? I sometimes write items = items.append(...) and then get None.

Geez

No. append() changes the list in place, and its return value is None. Python's tutorial describes list methods that only modify a list, including insert, remove, and sort, as having no return value. [S2]

So this works when you want to update the existing list:

items.append("new")

But this assigns the method's return value to items, making it None:

items = items.append("new")

Keep the operation and assignment separate. If you want to keep the original list unchanged and get a different list, build that list and return it instead.

Follow-up question

Can you show the returned-new-list version next to the in-place version, and how I should call each one?

Geez

Here are two small versions with different effects:

def add_in_place(items, label):
    items.append(label)

def with_label(items, label):
    result = items.copy()
    result.append(label)
    return result

Use them like this:

labels = ["draft"]
add_in_place(labels, "review")
print(labels)  # ['draft', 'review']

other_labels = with_label(labels, "final")
print(labels)        # ['draft', 'review']
print(other_labels)  # ['draft', 'review', 'final']

The first function changes the list it receives and returns no useful result. The second makes a copy, changes that copy, and explicitly returns it. Assign the returned value if you need to use it. This example uses a shallow copy and a list of simple strings; it is not a demonstration of how to duplicate nested mutable objects. The tutorial supports the distinction between modifying a list and returning a result, but copying is not necessarily appropriate for every program. [S1] [S2]

Follow-up question

When I inspect a function that's changing my list, what should I check first?

Geez

Trace the list object and the function's effects separately. Start with the argument passed at the call site, then look inside the function for in-place operations such as append, sort, or item assignment like items[0] = "updated". Finally, check whether the function also has a return statement. A function can both mutate a list and return a value, so don't assume those behaviors exclude each other. [S1] [S2]

A useful debugging note might be: “I pass labels to add_in_place; it appends to that list, so labels changes. The call returns None, so I should not assign its result. If I need a separate list, I will call with_label and save the returned list.” This makes the intended behavior explicit before you change the code.

A practical checklist

  • Find the exact list passed as the function argument.

  • Look for in-place changes such as append, sort, remove, or item assignment.

  • Check whether the function explicitly returns a value; reaching its end produces None.

  • Do not assign the result of a mutating list method such as append().

  • If you need a separate result, build and return a new list, then assign that return value.

A few more useful answers

Can a function change a list and also return a value?

Yes. A function may mutate its argument and then return something. Check the body for both in-place operations and a return statement. [S1]

Does `copy()` return a separate list?

It returns a shallow copy. That is suitable for the simple-string example here, but a shallow copy does not duplicate nested mutable objects. [S2]

Why does `sort()` return `None` when it sorted my list?

sort() modifies the list in place rather than returning a sorted list. Call it as a standalone statement when you want to change that list. [S2]

Sources

References used for this guide.

  1. [S1] Python Tutorial: More Control Flow Tools — function calls and return values
    docs.python.org · Checked
  2. [S2] Python Tutorial: Data Structures — list methods and mutation
    docs.python.org · Checked

YOUR QUESTION. YOUR DETAILS.

Let’s work through your situation.

Get a tailored answer or help with your next task. Starts at $1, with a quote before you pay.

Ask your own question
← Back to Geez at Work