List Methods: Adding, Removing and Sorting

Most list methods change the list in place and return None. Knowing which ones return a value and which do not prevents the most common list bug.

The rule that prevents most list bugs

items = [3, 1, 2]

result = items.sort()
print(result)        # None   <- the method returned nothing
print(items)         # [1, 2, 3]  <- but the list was sorted

items = [3, 1, 2]
result = sorted(items)
print(result)        # [1, 2, 3]  <- a new list
print(items)         # [3, 1, 2]  <- unchanged

Almost every list method modifies the list in place and returns None. Writing items = items.sort() throws the list away and leaves you holding None. This is the single most common list mistake.

In place, returns NoneReturns a new object
append, extend, insertsorted(items)
remove, clear, reverse, sortreversed(items)
-items.copy(), items[:]
pop is the exception: it changes the list and returns the removed item.

Adding items

items = [1, 2]

items.append(3)              # add ONE item at the end
print(items)                 # [1, 2, 3]

items.append([4, 5])         # appends the LIST as a single item
print(items)                 # [1, 2, 3, [4, 5]]

items = [1, 2]
items.extend([3, 4])         # add each item of an iterable
print(items)                 # [1, 2, 3, 4]

items.extend("ab")           # any iterable, including a string
print(items)                 # [1, 2, 3, 4, 'a', 'b']

items = [1, 2, 3]
items.insert(1, "new")       # insert BEFORE index 1
print(items)                 # [1, 'new', 2, 3]
items.insert(0, "first")     # at the front
items.insert(999, "last")    # an index past the end just appends
print(items)                 # ['first', 1, 'new', 2, 3, 'last']

append vs extend

a = [1, 2]
a.append([3, 4])
print(a, len(a))     # [1, 2, [3, 4]] 3

b = [1, 2]
b.extend([3, 4])
print(b, len(b))     # [1, 2, 3, 4] 4

c = [1, 2]
c += [3, 4]          # same as extend
print(c)             # [1, 2, 3, 4]

Removing items

items = ["a", "b", "c", "b"]

items.remove("b")            # removes the FIRST match only
print(items)                 # ['a', 'c', 'b']
# items.remove("z")          # ValueError if not present

last = items.pop()           # removes and returns the last item
print(last, items)           # b ['a', 'c']

first = items.pop(0)         # removes and returns by index
print(first, items)          # a ['c']

items = ["a", "b", "c"]
del items[1]                 # delete by index
print(items)                 # ['a', 'c']

del items[:]                 # delete everything, same as clear()
print(items)                 # []

items = ["a", "b", "c"]
items.clear()
print(items)                 # []

Removing safely

items = ["a", "b", "c"]

if "z" in items:
    items.remove("z")

# or
try:
    items.remove("z")
except ValueError:
    pass

# Removing every match, not just the first
items = [1, 2, 3, 2, 4, 2]
items = [n for n in items if n != 2]
print(items)                 # [1, 3, 4]

Sorting

numbers = [5, 2, 9, 1]

numbers.sort()                       # in place, ascending
print(numbers)                       # [1, 2, 5, 9]

numbers.sort(reverse=True)
print(numbers)                       # [9, 5, 2, 1]

print(sorted([5, 2, 9, 1]))          # a new sorted list, original untouched

Sorting with a key

words = ["banana", "kiwi", "apple", "fig"]

print(sorted(words, key=len))                    # ['fig', 'kiwi', 'apple', 'banana']
print(sorted(words, key=str.lower))              # case insensitive
print(sorted(words, key=lambda w: w[-1]))        # by last letter

people = [
    {"name": "Meera", "age": 27},
    {"name": "Arun", "age": 31},
    {"name": "Sara", "age": 24},
]

print(sorted(people, key=lambda p: p["age"]))
print(sorted(people, key=lambda p: p["name"]))

# Sort by two things: age descending, then name ascending
print(sorted(people, key=lambda p: (-p["age"], p["name"])))

The key function is called once per item, and the results are what get compared. It never changes the items themselves.

Sorting is stable

records = [("b", 2), ("a", 1), ("c", 2), ("d", 1)]

by_number = sorted(records, key=lambda r: r[1])
print(by_number)      # [('a', 1), ('d', 1), ('b', 2), ('c', 2)]

Items comparing equal keep their original relative order. That guarantee lets you sort by a secondary key first and a primary key second, which is often simpler than building a tuple key.

Reversing

items = [1, 2, 3]

items.reverse()                  # in place
print(items)                     # [3, 2, 1]

print(list(reversed(items)))     # [1, 2, 3] - a new list
print(items[::-1])               # [1, 2, 3] - a new list via slicing

Copying

original = [1, 2, 3]

alias = original                 # NOT a copy
shallow = original.copy()        # a copy
also_shallow = original[:]       # a copy
also_shallow_2 = list(original)  # a copy

alias.append(4)
print(original)                  # [1, 2, 3, 4] - changed through the alias
print(shallow)                   # [1, 2, 3]    - unaffected

All three copy forms are shallow: the new list is independent, but the objects inside are shared. For nested lists that matters, and copy.deepcopy is the answer. The internals notes cover this fully.

The full method list

MethodDoesReturns
append(x)Add one item at the endNone
extend(iterable)Add every item of an iterableNone
insert(i, x)Insert before index iNone
remove(x)Remove the first matchNone
pop([i])Remove and return by index, last by defaultthe item
clear()Remove everythingNone
index(x[, start, end])Position of the first matchint
count(x)How many matchesint
sort(key=, reverse=)Sort in placeNone
reverse()Reverse in placeNone
copy()Shallow copya new list

Cost of each operation

OperationCostNote
items[i]ConstantDirect access
append, pop()ConstantAt the end, cheap
insert(0, x), pop(0)Proportional to lengthEverything shifts
x in itemsProportional to lengthScans
sortn log nVery fast in practice

If you repeatedly add and remove at the front, a list is the wrong structure. Use collections.deque, covered in the standard library notes.

Common mistakes

  • Writing items = items.sort() and losing the list.
  • Using append when extend was meant, producing a nested list.
  • Calling remove on a value that may be absent.
  • Expecting remove to delete every match.
  • Assuming copy() is deep.
  • Using pop(0) in a loop over a large list.

Best practices

  • Use sorted() when you need both the original and the sorted version.
  • Use a comprehension to filter, rather than removing inside a loop.
  • Use key= with a tuple for multi level sorting.
  • Reach for deque when the front of the collection is busy.

Practice

  1. Explain why numbers = numbers.sort() leaves numbers as None.
  2. Sort a list of records by grade descending and then by name ascending.
  3. Remove every occurrence of a value from a list in three different ways.
  4. Demonstrate that copy() is shallow using a list of lists.
  5. Compare the time taken by pop(0) and pop() on a list of 100 000 items.

Conclusion

List methods change the list and return None; the functions sorted and reversed return something new. Keep that split straight, use key= for anything beyond a plain sort, and remember that the front of a list is expensive.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Python notes →
Python

Trees and Graphs

A tree is a graph with no cycles and one root. Both are walked with the same two strategies - depth first with a stack, breadth first with a queue.

Read more

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.