Essay
Learning Python, Part 4: Strings, Lists and Files
Study notes on indexing and slicing, string and list methods, aliasing, f-string formatting and reading and writing text files. Written entirely by an AI model.
Strings and lists are both sequences: ordered collections you can index, slice, loop over and measure with len. They differ in one important way. A string cannot be changed after it is made, and a list can.
Indexing and slicing
Positions start at 0, and negative positions count back from the end.
word = "lantern"
word[0] # 'l'
word[-1] # 'n'
word[7] # IndexError: valid positions are 0 to 6
A slice s[start:stop] runs from start up to but not including stop. Leave either end out to go to the edge, and add a third number for a step.
word[1:4] # 'ant'
word[:3] # 'lan'
word[4:] # 'ern'
word[::-1] # 'nretnal'
word[2:99] # 'ntern' (slices clip instead of raising an error)
Assigning to a position, as in word[0] = "L", raises TypeError. Build a new string instead: "L" + word[1:].
String operators and methods
+joins strings and*repeats them:"ha" * 3is'hahaha'.+with a non-string fails:"table " + 4is aTypeError. Usestr(4)or an f-string.intests for a substring:"ant" in wordisTrue.
Methods belong to the string and return a new value. They never change the original, so always use the result.
| Job | Methods |
|---|---|
| Case | upper(), lower(), title(), capitalize() |
| Tests | isalpha(), isdigit(), isalnum(), isupper(), startswith(), endswith() |
| Search | find(sub), count(sub), replace(old, new) |
| Trim | strip(), lstrip(), rstrip() |
| Split and join | split(), split(sep), sep.join(items) |
Some details that save time:
split()with no argument splits on any run of whitespace and drops the empty pieces.split(",")keeps them:"1,,2".split(",")is['1', '', '2'].findreturns-1when the text is missing, and-1is truthy. Soif s.find("x"):is the wrong test for presence. Useif "x" in s:."-".join(["a", "b", "c"])is'a-b-c'. The separator goes between items.
A short cleaning job shows the pieces working together:
raw = " Mei LING ,omar , priya "
names = [part.strip().title() for part in raw.split(",")]
print(names) # ['Mei Ling', 'Omar', 'Priya']
The one-line [... for ... in ...] is a list comprehension, a compact loop that builds a list. The loop version is fine while you are learning.
Characters and numbers
ord("a") is 97 and chr(100) is 'd'. Letters are consecutive, so shifting a letter by a fixed amount is arithmetic:
def shift(letter, amount):
start = ord("a")
return chr((ord(letter) - start + amount) % 26 + start)
print("".join(shift(ch, 3) for ch in "lantern")) # odqwhuq
The % 26 wraps z back to a. This is the idea behind a Caesar cipher. A fixed substitution like it is not secure, and anyone can break it by counting letter frequencies.
Lists
Lists use the same indexing and slicing, but they are mutable.
queue = ["Ana", "Ben", "Cyd"]
queue.append("Dee") # add to the end
queue.insert(0, "Eli") # add at position 0
queue.remove("Ben") # remove the first match
last = queue.pop() # remove and return the last item
queue.sort() # sorts in place, returns None
Two traps to know:
queue.sort()returnsNone. Writingqueue = queue.sort()throws your list away. If you want a sorted copy and an untouched original, usesorted(queue).b = adoes not copy a list. It gives the same list a second name.
a = [1, 2, 3]
b = a # alias
b.append(4)
print(a) # [1, 2, 3, 4]
c = a[:] # a real copy
c.append(5)
print(a, c) # [1, 2, 3, 4] [1, 2, 3, 4, 5]
append adds one item, while extend adds each item of another list. Appending [3] to [1, 2] leaves [1, 2, [3]], whereas extending with [3] leaves [1, 2, 3].
When you build a long string piece by piece, collect the parts in a list and call "".join(parts) at the end. It is faster than repeatedly adding to a string, because each + makes a fresh copy.
Formatting output
An f-string evaluates the expressions in its braces, and a format spec after a colon controls the look:
price = 1234567.891
print(f"{price:,.2f}") # 1,234,567.89
print(f"{'tea':>8}|") # ' tea|'
print(f"{0.256:.1%}") # 25.6%
print(f"{42:05d}") # 00042
Read >8 as “right-align in 8 characters”, , as “thousands separators” and .2f as “fixed point, two decimals”.
Files
Text files are the simplest way to keep data between runs.
with open("watering.txt", "w") as outfile:
outfile.write("fern,3\n")
outfile.write("cactus,1\n")
- The mode is
"r"to read (the default),"w"to write and erase, and"a"to append. withcloses the file for you, even if an error interrupts the block. Prefer it to callingclose()yourself.writedoes not add a newline. You must include\n.
Reading is usually a loop over the lines. Each line keeps its trailing newline, so strip() it:
totals = {}
with open("watering.txt") as infile:
for line in infile:
plant, litres = line.strip().split(",")
totals[plant] = float(litres)
print(totals) # {'fern': 3.0, 'cactus': 1.0}
If the file might not exist, wrap the open in a try and catch OSError. Part 5 shows how.
Files with a header
Real files often start with a header naming the columns. Rather than assume a column order, look the positions up by title:
header = "plant,room,litres".split(",")
litres_at = header.index("litres") # 2
row = "fern,kitchen,3".split(",")
print(float(row[litres_at])) # 3.0
That makes your code survive someone reordering the columns.
Where to practise
In Set A, try “Programme codes”, “The echo riddle” and the final file drill, “Tallying the stalls”. In Set B, try “The RSVP list” and “The ticket checker”. Part 5 covers dictionaries, sets and exceptions.