In python function arguments are passed by object reference.
This means the simplest code to modify a list will modify the object itself.
a = [1,2,3]def remove_one(b):b.remove(1)remove_one(a)
print(a)
The method remove_one
returns nothing. If that was a public method one has to assume it will modify the object and isn't thread safe inside the method.
A second approach would be to structure the code snippet like this:
a = [1,2,3]def remove_one(b):b.remove(1)return(b)print(remove_one(a[:]))
Here the content of a wasn't modified and a new list is returned. This puts a lot of responsibility on the method caller.
Then again list comprehensions
, the pythonic approach of modifying list contents always create a new object.
a = [1,2,3]def remove_one(b):b = [num for num in b if b!=1]return(b)print(remove_one(a))
It isn't clear to me if there is a "pythonic" way to do it so I'll make some assumptions to be challenged:
- It's safer to assume that a function taking a mutable object will modify it's content
- There is a reason why list comprehensions don't also directly modify the object. I just don't know it
- The "pythonic" way to write functions that use list comprehensions to modify the mutable argument isn't clear
TLDR: If you modify an argument, do not return it.
list.sort()
sorts the list in place. In order to remind you of that fact, it does not return the sorted list.
Design and History FAQ
It is idiomatic to indicate whether you modify an argument by not returning it. For example, your first case is correct, your second is not:
def remove_one(b):b.remove(1) # modify, do not return
If you do not modify an argument but create a copy, you must return
the copy for the operation to be meaningful:
def one_removed(b):c = b.copy() # create new object...c.remove(1)return c # ...and return it
It is common for function/method names to reflect whether they actively modify their argument. Modifying functions tend to use active verbs, whereas non-modifying functions tend to use passive verbs or adjectives.
There is no "pythonic" choice whether to modify the argument or not - operating on the original or a copy are two fundamentally different operations. Both are valid depending on the use-case.
>>> items = [4, 3, 10, 2, 5]
>>> sorted(items)
[2, 3, 4, 5, 10]
>>> items.sort()
>>> items
[2, 3, 4, 5, 10]
In general, mutating an argument is faster in Python, whereas creating a new result is easier to reason about.
In specific, comprehensions represent functional programming operations (namely map
and filter
) -- they are designed not to modify their iterable.
Whether to return an argument due to modification only concerns said argument. For example, list.pop
modifies the list and does not return it -- however, it does return the popped element.