Python Part I · Module 4 · Strings
Length and Membership
Two questions come up about text more than all the others combined: how long is it, and does it contain this. Python answers the first with len() and the second with the membership operator you already met in Module 3. Neither is complicated, which is exactly why this chapter can afford to be precise about the details that matter — that len() counts characters and not keystrokes, that membership on text looks for a whole run of characters rather than a single one, that it is case-sensitive, and that the empty string is contained in everything. This is also the chapter that explains why len() is written differently from every string operation you have met so far.
Foundation11 min readPythonPart IFundamentalsStringsMembership
Part I · Module 4 · Chapter 4.6 · Length and Membership
1. len(): How Many Characters
Four chapters have used len() as evidence and promised to explain it here. This is that explanation, and it is short, because the idea is exactly what it looks like: len() gives the number of characters in a string.
line = "UVM_ERROR: scoreboard mismatch"
print(len(line))30Thirty characters, counting the letters, the colon and the two spaces. Everything between the quotes counts, and nothing else does — the quotes themselves are boundary markers, not characters, as Chapter 1.8 established.
The edges are worth seeing because they come up in practice:
print(len(""))
print(len("P"))
print(len(" "))
print(len("a b"))0
1
2
3The empty string has length 0. That is not an error or a special case — it is a string with no characters, and asking how many characters it has sensibly gives zero. Spaces are characters and are counted; " " is genuinely two characters long.
And, as Chapters 4.2 and 4.4 insisted, an escape sequence is one character:
print(len("PASS\n"))
print(len("a\tb"))5
3"PASS\n" is five characters: P, A, S, S, newline. If you counted six you counted keystrokes rather than characters.
2. Why len(x) and Not x.len()
Chapter 4.1 taught you value.method(), and you have since seen raw.strip() in that form. So why is length written len(line) rather than line.len()?
Try the other way and Python is clear:
print("abc".len())AttributeError: 'str' object has no attribute 'len'AttributeError means: you asked this value for something it does not have. There is no len operation attached to strings, because len() is a built-in function rather than a string method.
The practical difference is about generality. A method belongs to one kind of value — strip is a string operation and only strings have it. len() is written to work on anything with a countable number of items, which is why the same len() will measure the lists in Module 5 and the dictionaries in Module 8 without you learning anything new.
So the rule is just a matter of remembering which is which:
| Form | What it is | Examples |
|---|---|---|
len(text) | a built-in function, value in the parentheses | len, print, str, int, type |
text.strip() | a method, value before the dot | strip, lower, split, replace |
You already know every function in that first row from Modules 1 and 2.
len() does refuse values that have no sensible count:
print(len(4210))TypeError: object of type 'int' has no len()A number is a single quantity, not a collection of things, so there is nothing to count. Note this is different from len("4210"), which is 4 — the text has four characters. That distinction is Chapter 2.9's point about "4210" not being 4210, showing up again.
3. Membership on Text
Chapter 3.6 taught the membership operators, so you already know that in produces a bool. What this section adds is what membership means when the thing on the right is a string.
line = "UVM_ERROR: scoreboard mismatch"
print("UVM_ERROR" in line)
print("mismatch" in line)True
TrueThe important detail: the left operand can be a whole run of characters, not just one. "UVM_ERROR" in line is asking "do these nine characters appear consecutively somewhere in line" — and they do, at the start.
A run of characters appearing inside a larger string is called a substring, and that is the term worth adopting: in tests whether one string is a substring of another.
It works for a single character too, because a single character is just a very short substring:
line = "UVM_ERROR: scoreboard mismatch"
print("_" in line)
print("z" in line)True
FalseAnd not in asks the opposite question, which often reads better than negating:
line = "UVM_ERROR: scoreboard mismatch"
print("UVM_FATAL" not in line)TrueThere is no UVM_FATAL in the line, so not in is True. Chapter 3.5 would let you write not ("UVM_FATAL" in line) instead; not in means the same thing and reads as English.
4. The Details That Bite
It is case-sensitive
line = "UVM_ERROR: scoreboard mismatch"
print("ERROR" in line)
print("error" in line)True
False"ERROR" is there. "error" is not, because E and e are different characters. This is the most common surprise in the chapter and it is not a quirk — it follows from strings being exact sequences of characters.
It also matters in practice, because tools are not consistent about case. Chapter 4.7 is largely about normalising text so that comparisons like this stop depending on it.
Order matters
A substring must appear as a consecutive run, in order:
print("board" in "scoreboard")
print("scoreboard" in "board")True
FalseThe first is True — board really is the tail of scoreboard. The second is False: scoreboard is longer than board, so it cannot possibly fit inside it. Membership is not symmetric, and getting the operands the wrong way round is a real bug — it just quietly answers a different question.
The empty string is in everything
line = "UVM_ERROR: scoreboard mismatch"
print("" in line)
print("" in "")True
TrueBoth True, including the empty string containing itself. This looks like a puzzle but is the only consistent answer: the empty string is a run of zero characters, and a run of zero characters appears at every position of every string — including at position 0 of an empty one.
It matters in one practical way. If the marker you are searching for came from somewhere else and happens to be empty, the test will pass on every line, and nothing will look wrong.
The left operand has to be a string
print(42 in "4210")TypeError: 'in <string>' requires string as left operand, not intThe message is unusually direct. When the right-hand side is a string, the left must be too — Python will not convert the number for you and go looking for its digits. Chapter 2.11's str() is the fix if that is genuinely what you want: str(42) in "4210" is True.
5. Both Are Just Expressions
Neither of these is a special construct. Both are ordinary expressions from Module 3 and combine like any others.
A membership test evaluates to a bool you can name:
line = "UVM_ERROR: scoreboard mismatch"
found = "ERROR" in line
print(found)
print(type(found))True
<class 'bool'>And both combine with the operators from Module 3:
line = "UVM_ERROR: scoreboard mismatch"
print(("ERROR" in line) and (len(line) > 10))TrueChapter 3.8 would tell you the brackets are not strictly required there, and would also tell you to keep them — a reader should not have to work out whether in binds tighter than and.
len() also works on the results of Chapter 4.5, because a slice is a string:
line = "UVM_ERROR: scoreboard mismatch"
print(len(line[0:9]))9Wall 0 to wall 9, nine characters — which is the stop - start rule confirming itself.
6. Predict, Run, Observe
Predict all six before reading on.
line = " PASS: axi_smoke "
print(len(line))
print("PASS" in line)
print("pass" in line)
print(" " in line)
print(len(line[2:6]))
print("axi_smoke" in line)The verified output:
19
True
False
True
4
TrueThe length is 19, not 15 — the two leading and two trailing spaces are characters like any others. If you predicted 15 you counted only the visible text, which is exactly the mistake Chapter 4.7 exists to prevent.
"pass" in line is False on case alone. " " in line is True because a single space is a substring like any other. And len(line[2:6]) is 4 by the stop - start rule, without needing to know what those characters are.
7. Exercises
Exercise 1 — count precisely. Without running anything, give the length of each, then verify:
(a) "FAIL" (b) "" (c) " " (d) "a\nb\nc" (e) "C:\\runs" (f) "4210"
Exercise 2 — function or method. For each, say whether it is written as a function or a method, and correct the ones that are written the wrong way:
(a) len(name) (b) name.len() (c) name.strip() (d) strip(name)
Exercise 3 — predict membership. Given entry = "WARNING: hold violation at U12", say True or False for each and explain the two that surprise you:
(a) "WARNING" in entry (b) "warning" in entry (c) "violation" in entry (d) "U12" in entry (e) "" in entry (f) "hold violation" in entry (g) "violation hold" in entry
Exercise 4 — the wrong way round. This is meant to test whether the line mentions an error but always prints False. Say what question it actually asks, and fix it:
line = "UVM_ERROR: mismatch"
print(line in "UVM_ERROR")Exercise 5 — the empty marker. Explain in two sentences why a search for an empty marker succeeds on every line, and why that makes it a dangerous default.
Exercise 6 — measure a slice. Given row = "axi_smoke PASS", predict len(row), len(row[:9]) and len(row[9:]), then verify. Say in one sentence why the last two must add up to the first.
Exercise 7 — number or text. Predict the result of each, then run them. One of them raises; say which and why:
(a) len("4210") (b) len(4210) (c) "42" in "4210" (d) 42 in "4210"
8. Summary
len(text)is the number of characters in the value — not the number of keystrokes in the literal.- The empty string has length 0, and spaces are characters that count.
- An escape sequence is one character, so
len("PASS\n")is 5. len()is a built-in function, not a method."abc".len()is anAttributeError. It is a function so that it can measure lists, dictionaries and more later on with no new syntax.len()refuses a number —TypeError: object of type 'int' has no len(). Butlen("4210")is 4."sub" in texttests whether one string is a substring of another, and the left operand can be a whole run of characters.- Membership on text is case-sensitive.
"error" in "ERROR: x"isFalse. - Membership is not symmetric.
"board" in "scoreboard"isTrue; the reverse isFalse. - The empty string is a substring of every string, including itself.
- The left operand must be a string —
42 in "4210"is aTypeError. Convert withstr(). - Both are ordinary expressions and combine with everything from Module 3.
You can now ask about text. The next chapter starts changing it — beginning with the stray whitespace that made the length 19 rather than 17.
Where this fits
Part of the Python curriculum.
