Python Part I · Module 5 · Lists
Sorting and Ordering Results
A list of results in the order the tests happened to finish is not a report. Put them in order and the same data becomes readable — failures grouped together, the slowest test at the top, names alphabetical so a reader can find one. Python gives you two ways to do that, and choosing between them is the real content of this chapter, because one of them changes the list you already have and the other leaves it alone and builds a new one. That is the mutable-versus-new tension from Chapter 5.3 showing up as an actual decision for the first time. The chapter also covers the ordering rule that makes people think sorting is broken the first time they meet it.
Foundation13 min readPythonPart IFundamentalsListsSorting
Part I · Module 5 · Chapter 5.5 · Sorting and Ordering Results
1. sort(): Put This List in Order
sort is a list method, and like every mutating method in Chapter 5.3 it changes the list you already have. Before and after:
tests = ["reset", "burst", "smoke"]
print(tests)
tests.sort()
print(tests)['reset', 'burst', 'smoke']
['burst', 'reset', 'smoke']The same list, now alphabetical. No new list was made and nothing needed assigning.
Which means the Chapter 5.3 trap applies in full:
t = ["b", "a"]
print(t.sort())
print(t)None
['a', 'b']sort() evaluates to None, exactly as append did, and for the same reason — the change already happened to your list. So tests = tests.sort() sets tests to None, silently. If that rule is not yet automatic, Chapter 5.3 Section 5 is where it is explained.
Numbers sort numerically, which is worth confirming rather than assuming:
seeds = [4210, 77, 9124]
seeds.sort()
print(seeds)[77, 4210, 9124]77 first. Had these been the strings "4210", "77" and "9124" the answer would have been different, because text is compared character by character — "77" starts with 7 and "4210" starts with 4, so "4210" would come first. The type of your elements decides the order you get.
2. Descending Order
sort takes an option for reversing the result:
runtimes = [12.4, 8.7, 19.2]
runtimes.sort(reverse=True)
print(runtimes)[19.2, 12.4, 8.7]reverse=True inside the parentheses is a named option — the name says which setting you are changing. You have seen the shape before in print() and split(); this is the first time the name matters for reading the line. Written out, sort(reverse=True) says sort, with reversing switched on.
Slowest first is the usual thing you want from a runtime column, so this is the option you will reach for most.
3. Why "APB" Sorts Before "burst"
Here is the result that makes people think sorting is broken:
mixed = ["smoke", "Reset", "burst", "APB"]
mixed.sort()
print(mixed)['APB', 'Reset', 'burst', 'smoke']Both capitalised names came first, and within each group the order is alphabetical. That is not a bug and it is not random.
Sorting text compares characters, using the same < you met in Chapter 3.4. And on characters, every capital letter comes before every lowercase one:
print("A" < "a")
print("Z" < "a")True
TrueEven "Z" is less than "a". So a sort puts everything beginning with a capital ahead of everything beginning with a lowercase letter, and the result looks alphabetically scrambled to a reader who was thinking about words rather than characters.
4. sorted(): Order Without Changing Anything
sorted() is a built-in function, not a method, and that difference in notation reflects a real difference in behaviour. It leaves your list exactly as it was and hands back a new list in order:
original = ["reset", "burst", "smoke"]
ordered = sorted(original)
print(original)
print(ordered)['reset', 'burst', 'smoke']
['burst', 'reset', 'smoke']Two lists now exist. original is untouched — in the order the results arrived — and ordered is a new list with the same elements arranged.
That is exactly the len() situation from Chapter 4.6 again: a built-in function that works on many kinds of value, written with the value in the parentheses. Which is why it also works on a string, and tells you something about what it returns:
print(sorted("cab"))['a', 'b', 'c']A list, not a string. sorted() always produces a list, whatever it was given.
Choosing between them
This is the decision the chapter is really about, and it is not a style preference:
tests.sort() | sorted(tests) | |
|---|---|---|
| What it is | a list method | a built-in function |
| Your list afterwards | reordered | unchanged |
| Evaluates to | None | a new sorted list |
| Correct usage | tests.sort() on its own line | ordered = sorted(tests) |
| Works on | lists | lists, strings, and more |
5. Duplicates Survive
Sorting rearranges; it never removes:
s = ["PASS", "FAIL", "PASS"]
s.sort()
print(s)['FAIL', 'PASS', 'PASS']Three elements before, three after. Both "PASS" entries are still there, now adjacent. That is the behaviour you want for results data — two tests that both passed are two facts, not one.
6. reverse() Is Not Sorting
There is also a reverse() method, and it is easy to confuse with the reverse=True option from Section 2. They do different things.
reverse() flips the list end to end, in whatever order it currently is:
tests2 = ["smoke", "reset", "burst"]
tests2.reverse()
print(tests2)['burst', 'reset', 'smoke']That happens to look alphabetical, which is a coincidence of this example. Try it on a list that is not already ordered and the point is clear:
t3 = ["b", "c", "a"]
t3.reverse()
print(t3)['a', 'c', 'b']Not sorted — just backwards. reverse() never compares anything; it only turns the list around.
Like the other mutating methods, it changes the list and returns nothing useful:
t4 = ["a", "b"]
print(t4.reverse(), t4)None ['b', 'a']7. What You Cannot Do Yet
Two real limitations, and it is better to name them than to leave you to discover them.
Case-insensitive sorting. Section 3 showed that ["smoke", "Reset"] sorts capitals first. The fix is to tell sort to compare something other than the element itself — the lowercased version of it — and that means handing sort a small piece of code to apply to each element. Code you can pass around as a value is a function, and Module 11 is where functions are built.
Sorting by something other than the element. Given a list of rows like ["smoke", "PASS", 12.4], sorting by runtime means comparing the third item of each row rather than the row itself. Same tool, same module.
8. Broken on Purpose
Sorting a list of mixed types. Ordering means comparing, and Python will not invent an order between a number and a piece of text:
x = ["smoke", 4210]
x.sort()TypeError: '<' not supported between instances of 'int' and 'str'Read the message literally: to sort, Python needed to ask whether one element is less than another, and there is no answer to 4210 < "smoke". Chapter 3.4 made exactly this point about comparing values of different types; sorting is where it becomes a practical limit.
This is the concrete reason Chapter 5.1 argued that most useful lists hold one kind of thing. A list of runtimes sorts; a list of a name, a status and a runtime does not.
Assigning the result of sort(). The trap from Chapter 5.3, applied here:
t = ["b", "a"]
t = t.sort()
print(t)NoneNo error, and t is now None. The list was sorted correctly and then thrown away.
9. Predict, Run, Observe
Predict every line before reading on.
names = ["reset", "APB", "smoke"]
ordered = sorted(names)
print(names)
print(ordered)
names.sort()
print(names)
print(names.sort())
nums = [3, 1, 2]
nums.sort(reverse=True)
print(nums)
nums.reverse()
print(nums)The verified output:
['reset', 'APB', 'smoke']
['APB', 'reset', 'smoke']
['APB', 'reset', 'smoke']
None
[3, 2, 1]
[1, 2, 3]The first line is the point of sorted(): names is unchanged after it. The second shows "APB" first, because capitals sort ahead of lowercase — Section 3.
The third line shows names now reordered, because sort() changed it. The fourth is None, because that is what sort() evaluates to.
The last pair is worth checking carefully. nums.sort(reverse=True) gives [3, 2, 1] — sorted, descending. Then nums.reverse() flips that to [1, 2, 3]. The second call did not sort anything; it turned the already-descending list around, which in this case happens to produce ascending order.
10. Exercises
Exercise 1 — in place or new. For runtimes = [12.4, 8.7, 19.2], write the line that puts runtimes itself in order, and separately the line that leaves it alone and gives you an ordered copy called ranked. Print runtimes after each to prove the difference.
Exercise 2 — predict the order. Without running anything, give the sorted result of each, then verify:
(a) ["b", "a", "c"] (b) ["B", "a", "C"] (c) [10, 9, 100] (d) ["10", "9", "100"] (e) ["PASS", "FAIL", "PASS"]
Explain (c) and (d) — the same-looking data gives different orders.
Exercise 3 — the None trap. This prints None. Say which three steps produced it and fix it with the smallest change:
tests = ["reset", "burst"]
tests = tests.sort()
print(tests)Exercise 4 — descending. Write two different expressions that both give the runtimes largest-first, one using sort and one using sorted. Say which you would use if the original arrival order still mattered.
Exercise 5 — sort or reverse. A colleague wants ["c", "a", "b"] in alphabetical order and writes items.reverse(). Say what they actually get, why, and give the line they meant.
Exercise 6 — the mixed list. Explain in two sentences why ["smoke", 4210].sort() raises, quoting the operator named in the error, and say what that tells you about how sorting works.
Exercise 7 — which tool. For each, name sort() or sorted() and give one sentence of justification:
(a) print an alphabetical index of test names, keeping the run order for a later report
(b) put a working list of jobs into a fixed order before processing
(c) show the five slowest runtimes without disturbing the results list
11. Summary
tests.sort()orders the list in place and evaluates toNone.tests = tests.sort()setsteststoNone.sorted(tests)leaves your list alone and returns a new ordered list. It is a built-in function, likelen(), and always returns a list.- Choose by whether the original order still matters. Sorting in place discards it permanently.
sort(reverse=True)gives descending order — the usual choice for a runtime column.- Text sorts by character order, and every capital comes before every lowercase letter.
"Z" < "a"isTrue, so"APB"sorts before"burst". - Numbers sort numerically; strings of digits sort as text.
[77, 4210]and["77", "4210"]order differently. - Duplicates are kept, moved next to each other.
reverse()flips the list; it does not order it. It is a different thing from thereverse=Trueoption.- Sorting mixed types raises
TypeError: '<' not supported between instances of ..., because ordering requires comparing. - Case-insensitive sorting and sorting by a field need the
keyoption, which takes a function — Module 11.
Ordering arranges what is already in a list. The next chapter puts two lists together.
Where this fits
Part of the Python curriculum.
