5

I'm having an issue considering the built-in Python List-methods.

As I learned Python, I always thought Python mutators, as any value class mutators should do, returned the new variable it created.

Take this example:

a = range(5)
# will give [0, 1, 2, 3, 4]
b = a.remove(1)
# as I learned it, b should now be [0, 2, 3, 4]
# what actually happens:
# a = [0, 2, 3, 4]
# b = None

The main problem with this list mutator not returning a new list, is that you cannot to multiple mutations subsequently. Say I want a list ranging from 0 to 5, without the 2 and the 3. Mutators returning new variables should be able to do it like this:

a = range(5).remove(2).remove(3)

This sadly isn't possible, as range(5).remove(2) = None.

Now, is there a way to actually do multiple mutations on lists like I wanna do in my example? I think even PHP allows these types of subsequent mutations with Strings.

I also can't find a good reference on all the built-in Python functions. If anyone can find the actual definition (with return values) of all the list mutator methods, please let me know. All I can find is this page: http://docs.python.org/tutorial/datastructures.html

Steven Roose
  • 2,731
  • 4
  • 29
  • 46
  • 1
    First, the documentation you're looking for is http://docs.python.org/library/stdtypes.html#mutable-sequence-types – Greg Hewgill Apr 02 '12 at 20:23
  • "As I learned Python, I always thought Python mutators, as any value class mutators should do, returned the new variable it created." – where'd you learn this? It is in fact very common to have objects with mutable state in Python, and lists are one of them. (Strings aren't.) – millimoose Apr 02 '12 at 20:39

5 Answers5

9

Rather than both mutating and returning objects, the Python library chooses to have just one way of using the result of a mutator. From import this:

There should be one-- and preferably only one --obvious way to do it.

Having said that, the more usual Python style for what you want to do is using list comprehensions or generator expressions:

[x for x in range(5) if x != 2 and x != 3]

You can also chain these together:

>>> [x for x in (x for x in range(5) if x != 2) if x != 3]
[0, 1, 4]

The above generator expression has the added advantage that it runs in O(n) time because Python only iterates over the range() once. For large generator expressions, and even for infinite generator expressions, this is advantageous.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • 1
    Hmm I guess when working with lists to exclude it can be handy to do something like this: `[ x for x in range(5) if not x in (2, 3) ]` – Steven Roose Apr 02 '12 at 23:13
5

Many methods of list and other mutable types intentionally return None so that there is no question in your mind as to whether you are creating a new object or mutating an existing object. The only thing that could be happening is mutation since, if a new object were created, it would have to be returned by the method, and it is not returned.

As you may have noticed, the methods of str that edit the string do return the new string, because strings are immutable and a new string is always returned.

There is of course nothing at all keeping you from writing a list subclass that has the desired behavior on .append() et al, although this seems like rather a heavy hammer to swing merely to allow you to chain method calls. Also, most Python programmers won't expect that behavior, making your code less clear.

kindall
  • 178,883
  • 35
  • 278
  • 309
4

In Python, essentially all methods that mutate the object return None.

That's so you don't accidentally think you've got a new object as a result.

You mention

I think even PHP allows these types of subsequent mutations with Strings.

While I don't remember about PHP, with string manipulations, you can chain method calls, because strings are immutable, so all string methods return a new string, since they don't (can't) change the one you call them on.

>>> "a{0}b".format(3).center(5).capitalize().join('ABC').swapcase()
'a A3B b A3B c'
agf
  • 171,228
  • 44
  • 289
  • 238
2

Neither Python or php have built-in "mutators". You can simply write multiple lines, like

a = list(range(5))
a.remove(2)
a.remove(3)

If you want to generate a new list, copy the old one beforehand:

a = list(range(5))
b = a[:]
b.remove(2)

Note that in most cases, a singular call to remove indicates you should have used a set in the first place.

phihag
  • 278,196
  • 72
  • 453
  • 469
-1

To remove multiple mutations on lists as you want to do on your example, you can do :

a = range(5)
a = [i for j, i in enumerate(a) if j not in [2, 3]]

a will be [0, 1, 4]

Xavier V.
  • 6,068
  • 6
  • 30
  • 35
  • 1
    One remark: The `list.remove(x)` method removes the item with value `x`, not the item at index `x`, which your script does. For the `range()` lists that does the same thing, for other lists it may not do the right thing ;) – Steven Roose Apr 03 '12 at 08:42