4.5 Iterating, Sorting, and Comparing Strings Against Other Types
Key Takeaways
- `str` objects have no `.sort()` method — calling `"text".sort()` raises `AttributeError`; `sorted()` is the built-in that accepts a string and always returns a **list of single-character strings**, never a string.
- `list.sort()` mutates a list of strings in place and returns `None`, whereas `sorted()` leaves its argument untouched and returns a new list, so `words = words.sort()` destroys the data.
- Default string sorting is by Unicode code point, which places every ASCII uppercase letter before every lowercase letter; `key=str.lower` restores case-insensitive alphabetical order.
- Equality between a string and a number is always defined and always `False` (`'2' == 2`), but ordering comparisons such as `'2' < 3` raise `TypeError: '<' not supported between instances of 'str' and 'int'`.
- Iterating a string with `for ch in s` yields one-character strings, so a string is an iterable of strings — there is no separate character type in Python.
Iterating, Sorting, and Comparing Strings Against Other Types
Objective 3.3 lists .sort() and sorted() among the built-in string tools, and objective 3.2 names iteration and comparison "against strings and numbers". All three share one root idea that the exam probes relentlessly: a string is a sequence, but it is not a list, and it is immutable. This section works through what that means for looping, ordering, and cross-type comparisons.
1. Why This Sits in the Strings Block
Objective PCAP-31-03 3.3 explicitly names .sort() and sorted() in its list of built-in string methods, and objective 3.2 explicitly names iterating through strings and comparing (against strings and numbers). Those three items are grouped here because they share a single exam trap: strings are sequences, but they are not lists, and the exam repeatedly probes the boundary between the two.
2. Iterating Through a String
A string is an iterable. A for loop over a string yields its characters left to right, and every yielded value is itself a str of length 1 — Python has no distinct character type.
word = "abc"
for ch in word:
print(ch, type(ch).__name__, len(ch))
# a str 1
# b str 1
# c str 1
Because each character is a full string, all string operations work on it immediately:
title = "python 3"
upper_count = 0
for ch in title:
if ch.isalpha() and ch.islower():
upper_count += 1
print(upper_count) # 6
Two supporting built-ins appear constantly in exam items:
enumerate(s)yields(index, character)tuples:list(enumerate("ab"))→[(0, 'a'), (1, 'b')].reversed(s)returns a lazyreversedobject, not a string.list(reversed("abc"))→['c', 'b', 'a'], and"".join(reversed("abc"))→'cba'. The slice idiom"abc"[::-1]produces the reversed string directly and is usually the intended answer.
Exam Trap:
print(reversed("abc"))displays something like<reversed object at 0x...>. If an option offers'cba'for a barereversed()call without ajoin()orlist(), it is wrong.
3. Sorting: sorted() Versus .sort()
str Has No .sort() Method
This is the single most misread line in the syllabus. .sort() is a list method. Strings are immutable, so no in-place sort can exist for them:
s = "banana"
s.sort()
# AttributeError: 'str' object has no attribute 'sort'
sorted() Accepts a String but Returns a List
The built-in sorted(iterable) consumes any iterable — including a string — and returns a new list:
print(sorted("banana"))
# ['a', 'a', 'a', 'b', 'n', 'n']
print("".join(sorted("banana")))
# 'aaabnn'
To get a sorted string back you must re-join the list. Forgetting the join() is the classic distractor.
list.sort() Returns None
When the data is already a list of strings, .sort() mutates it in place and returns None:
words = ["b", "a"]
result = words.sort()
print(result) # None
print(words) # ['a', 'b']
# The destructive anti-pattern the exam loves:
names = ["Zoe", "Adam"]
names = names.sort() # names is now None, the data is gone
| Expression | Mutates original? | Return value | Works on a str? |
|---|---|---|---|
sorted(x) | No | New list | Yes |
sorted(x, reverse=True) | No | New list, descending | Yes |
x.sort() | Yes | None | No — AttributeError |
x.sort(reverse=True) | Yes | None | No — AttributeError |
4. Sort Order Is Code-Point Order
Sorting uses the same lexicographical rules as the < operator: characters are ordered by Unicode code point. In ASCII, uppercase A–Z occupy 65–90 and lowercase a–z occupy 97–122, so every uppercase letter sorts before every lowercase letter:
print(sorted("Zebra"))
# ['Z', 'a', 'b', 'e', 'r']
print(sorted(["delta", "Echo", "alpha"]))
# ['Echo', 'alpha', 'delta']
The key parameter fixes this by supplying the value each element is ranked by, without altering the values that are returned:
print(sorted(["delta", "Echo", "alpha"], key=str.lower))
# ['alpha', 'delta', 'Echo']
print(sorted(["delta", "Echo", "alpha"], key=len))
# ['Echo', 'delta', 'alpha']
Digits (48–57) sort before letters, and the space character (32) sorts before everything printable:
print(sorted("hello world"))
# [' ', 'd', 'e', 'h', 'l', 'l', 'l', 'o', 'o', 'r', 'w']
Numeric strings sort as text, not as numbers, unless a key converts them:
print(sorted(["10", "9", "100"]))
# ['10', '100', '9']
print(sorted(["10", "9", "100"], key=int))
# ['9', '10', '100']
5. Comparing Strings Against Numbers
Python draws a hard line between equality comparisons and ordering comparisons across types.
Equality Is Always Defined
== and != never raise for mismatched types. A str is never equal to an int, even when it looks identical:
print("2" == 2) # False
print("2" != 2) # True
print("" == 0) # False
Ordering Raises TypeError
<, <=, >, and >= are undefined between str and numeric types:
print("2" < 3)
# TypeError: '<' not supported between instances of 'str' and 'int'
The same rule propagates through anything that orders values internally, which is why a mixed-type list cannot be sorted and why min()/max() fail on mixed arguments:
sorted(["a", 1])
# TypeError: '<' not supported between instances of 'int' and 'str'
| Expression | Result |
|---|---|
"2" == 2 | False |
"2" != 2 | True |
"2" < 3 | TypeError |
sorted(["a", 1]) | TypeError |
"10" < "9" | True (both strings — code-point comparison of '1' vs '9') |
int("10") < int("9") | False |
Exam Trap:
'10' < '9'isTruebecause'1'(49) precedes'9'(57). Candidates who mentally convert numeric strings to integers pick the wrong option almost every time.
6. Putting It Together
A frequent scenario item asks for a canonical, case-insensitive, de-duplicated signature of a string. Every tool above appears at once:
raw = "Mississippi"
print(sorted(raw)) # ['M', 'i', 'i', 'i', 'i', 'p', 'p', 's', 's', 's', 's']
print(sorted(raw, key=str.lower)) # ['i', 'i', 'i', 'i', 'M', 'p', 'p', 's', 's', 's', 's']
print("".join(sorted(set(raw)))) # 'Mips'
Note how sorted(raw) places the uppercase 'M' first on code point, while key=str.lower ranks it as 'm' and pushes it past the 'i' characters — the returned element is still the original uppercase 'M'.
What is printed by the following code?
result = sorted("banana")
print(result)
Consider the following code:
What is printed?labels = ["delta", "Echo", "alpha"]
output = labels.sort()
print(output, labels)
Which of the following expressions raises a TypeError in Python 3?
Given text = "Zebra", which expression evaluates to the string 'aberZ'?