4.2 String Indexing, Slicing, and Comparison
Key Takeaways
- Positive indexing begins at 0 and increments to `len(s) - 1`, while negative indexing begins at `-1` (the last character) and decrements to `-len(s)`; indexing outside this range raises `IndexError`.
- Slicing uses the syntax `s[start:stop:step]` and is boundary-tolerant, never raising an `IndexError` even when indices lie completely outside the string's length.
- Negative slice steps reverse traversal direction, causing `start` to default to `len(s) - 1` and `stop` to default to the position before index 0, making `s[::-1]` the standard reversal idiom.
- Sequence operators allow string concatenation (`+`), integer repetition (`*`), and substring membership verification (`in` and `not in`).
- String comparison operators compare strings lexicographically code point by code point, which makes all ASCII uppercase letters evaluate as strictly less than lowercase letters (`'Z' < 'a'`).
String Indexing, Slicing, and Comparison
Strings in Python are ordered sequences of individual characters. Because they are sequences, they support all standard Python sequence operations, including direct indexing, advanced slicing, repetition, concatenation, and relational comparisons.
1. String Indexing Mechanics
Every character in a string occupies a fixed positional index. Python supports dual indexing:
- Positive Indexing (Forward): Starts at
0for the first character and advances tolen(s) - 1for the final character. - Negative Indexing (Backward): Starts at
-1for the rightmost character and decrements to-len(s)for the leftmost character.
String: P Y T H O N
Positive Index: 0 1 2 3 4 5
Negative Index: -6 -5 -4 -3 -2 -1
language = "PYTHON"
# Positive indexing
print(language[0]) # 'P'
print(language[5]) # 'N'
# Negative indexing
print(language[-1]) # 'N' (last character)
print(language[-6]) # 'P' (first character)
The IndexError Trap
Attempting to access an index outside the valid range [-len(s), len(s) - 1] immediately triggers an IndexError:
s = "PYTHON" # len(s) is 6
# Out-of-bounds access raises IndexError
# s[6] -> IndexError: string index out of range
# s[-7] -> IndexError: string index out of range
Type Requirement: String indices must be integers. Passing floats (e.g.,
s[1.0]) or strings raises aTypeError: string indices must be integers.
2. Slicing Syntax and Parameter Defaults
Slicing creates a new substring from an existing string using the slice bracket syntax:
start: The index where extraction begins (inclusive).stop: The index where extraction terminates (exclusive; the character atstopis never included).step: The stride or step interval between extracted characters (defaults to+1).
Default Values for Positive Step (step > 0)
When step is positive (or omitted):
startdefaults to0(the beginning of the string).stopdefaults tolen(s)(the end of the string).stepdefaults to1.
s = "PROGRAMMING"
print(s[0:4]) # 'PROG' (indices 0, 1, 2, 3)
print(s[:7]) # 'PROGRAM' (start defaults to 0)
print(s[7:]) # 'MING' (stop defaults to len(s))
print(s[:]) # 'PROGRAMMING' (full shallow copy)
print(s[::2]) # 'PGAMNG' (every 2nd character: indices 0, 2, 4, 6, 8, 10)
3. Boundary Tolerance in Slicing
A critical distinction on the PCAP exam is the difference in error handling between direct indexing and slicing:
- Direct Indexing (
s[i]): Strict boundary checking. Out-of-range indices raiseIndexError. - Slicing (
s[start:stop]): Completely boundary-tolerant. Python automatically clamps out-of-range boundaries to valid sequence limits without raising any errors.
s = "PYTHON" # len(s) == 6
# Slices extending far beyond string boundaries
print(s[2:100]) # 'THON' (clamps stop to 6)
print(s[10:20]) # '' (empty string, start is past the end)
print(s[-100:2]) # 'PY' (clamps start to 0)
print(s[5:2]) # '' (empty string, stop <= start with positive step)
4. Negative Steps and String Reversal
When step < 0, the traversal direction is reversed (moving from right to left). This inverts the default slice boundaries:
startdefaults tolen(s) - 1(the last character).stopdefaults to one position before index0(so index0is included).
Reversal Idiom: s[::-1]
Traverses from the last character back to the first character.
s = "PYTHON"
# Standard reversal idiom
print(s[::-1]) # 'NOHTYP'
# Slice with explicit negative indices and step
# Starts at index 4 ('O'), stops BEFORE index 1 ('Y')
print(s[4:1:-1]) # 'OHT' (indices 4, 3, 2)
# Direction conflict returning empty string
# Moving left-to-right with start < stop but negative step
print(s[1:4:-1]) # ''
Step Arithmetic Rule Table
| Expression | Start Index | Stop Index | Step | Output on "PYTHON" |
|---|---|---|---|---|
s[1:5:2] | 1 ('Y') | 5 ('N') | +2 | 'YH' (indices 1, 3) |
s[5:0:-2] | 5 ('N') | 0 ('P') | -2 | 'NHY' (indices 5, 3, 1) |
s[-2:-5:-1] | -2 ('O') | -5 ('Y') | -1 | 'OHT' (indices -2, -3, -4) |
s[-4:] | -4 ('T') | 6 (len) | +1 | 'THON' (indices -4, -3, -2, -1) |
5. Sequence Operators: +, *, in, and not in
Strings support sequence concatenation, repetition, and membership testing:
Concatenation (+)
Combines two strings into a newly allocated string object. Both operands must be str:
greeting = "Hello" + " " + "World"
print(greeting) # "Hello World"
# Mixed type error:
# "Score: " + 100 # TypeError: can only concatenate str (not "int") to str
Repetition (*)
Replicates a string by an integer multiplier. The multiplier can appear on either side (s * n or n * s):
print("-" * 5) # "-----"
print(3 * "Abc") # "AbcAbcAbc"
print("Python" * 0) # "" (multiplier <= 0 yields an empty string)
print("Python" * -3)# ""
Membership Testing (in and not in)
Tests whether a target substring exists as a contiguous slice within the string:
text = "Object Oriented Programming"
print("Oriented" in text) # True
print("oriented" in text) # False (case-sensitive)
print("OOP" in text) # False (must be contiguous)
print("xyz" not in text) # True
# The empty string rule:
# The empty string is considered a substring of EVERY string (including "")
print("" in text) # True
print("" in "") # True
6. Lexicographical Comparison Rules
Python compares strings using relational operators (<, <=, ==, !=, >=, >):
- Comparisons are performed lexicographically (character-by-character from left to right).
- Each character's ordinal position is determined strictly by its Unicode code point (
ord()). - The comparison halts at the very first mismatched character.
The ASCII / Code Point Hierarchy
Because ASCII values dictate order:
# 1. Case sensitivity: All uppercase letters precede all lowercase letters
print('Apple' < 'apple') # True ('A' is 65, 'a' is 97)
print('Zebra' < 'ant') # True ('Z' is 90, 'a' is 97)
# 2. Digit strings vs Alphabet strings
print('9' < 'A') # True ('9' is 57, 'A' is 65)
# 3. Numeric string comparison pitfall
print('100' < '20') # True ('1' is 49, '2' is 50; '1' < '2')
print(100 < 20) # False (integer comparison compares numeric magnitude)
# 4. Prefix comparison rule (shorter prefix is smaller)
print('app' < 'apple') # True (all matching, 'app' is shorter)
7. Built-in Functions: min() and max() on Strings
When applied to a string, min() and max() return the individual characters with the lowest and highest Unicode code points, respectively:
word = "Python"
print(min(word)) # 'P' (code point 80; 'h'=104, 'n'=110, 'o'=111, 't'=116, 'y'=121)
print(max(word)) # 'y' (code point 121)
phrase = "Hello World"
print(min(phrase)) # ' ' (space character has code point 32, which is lowest)
# Empty sequence error
# min("") # ValueError: min() arg is an empty sequence
What is the output of the following slice operation?
word = "OpenEDG"
print(word[10:20])
Which of the following comparison expressions evaluates to True in Python?
What is the result of evaluating the following Python expression?
s = "ABCDEF"
print(s[4:1:-1])
Given the string s = "Python 3.10", what is returned by min(s)?