Skip to content
VLSI Mentor

Python Part I · Module 4 · Strings

Indexing: Reaching One Character

A string holds its characters in a definite order, and Python gives every position a number so you can name one. That sounds trivial and mostly is, but three details cause nearly all the confusion beginners have with text: the numbering starts at zero rather than one, the negative numbers reach backwards from the end in a way that is not the mirror image people expect, and what you get back from an index is not a character in the sense other languages mean — it is a perfectly ordinary one-character string. This chapter settles all three, and meets the IndexError you will eventually cause, so that when you do you already know what it means.

Foundation12 min readPythonPart IFundamentalsStringsIndexing

Part I · Module 4 · Chapter 4.4 · Indexing: Reaching One Character

1. Asking for One Character

Sometimes the question is about a single character rather than the whole string. Does this path end in a separator? Is this signal name prefixed with an underscore? Both need a way to say the character at that position.

Python's notation for it is square brackets after the string, with a position number inside:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
name = "axi_smoke"
print(name[0])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a

name[0] is read aloud as "name at zero" or "name sub zero". It is an expression, like everything in Module 3 — it evaluates to a value, so it can be printed, named or compared.

And the value it evaluates to is the first character, even though the number is zero. That is the first thing to get straight.

2. Positions Count From Zero

Python numbers the positions in a string starting at 0. A string of five characters therefore has positions 0, 1, 2, 3 and 4 — and no position 5.

Here is the whole picture for a short string. The numbers above are the ordinary indexes; the numbers below are the negative ones from Section 5:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
               0    1    2    3    4
            +----+----+----+----+----+
  "CLOCK"   | C  | L  | O  | C  | K  |
            +----+----+----+----+----+
              -5   -4   -3   -2   -1

Read a column at a time. The C at the front sits in the cell labelled 0 above and -5 below, so both t[0] and t[-5] reach it. The K at the end sits in the cell labelled 4 and -1.

Check it against Python:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
t = "CLOCK"
print(t[0], t[1], t[2], t[3], t[4])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
C L O C K

The same numbering on a longer, more realistic string:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
name = "axi_smoke"
print(name[0])
print(name[1])
print(name[3])
print(name[8])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a
x
_
e

Position 3 is the underscore, and position 8 is the final e. Nine characters, positions 0 through 8.

3. What Indexing Gives You Back

This section is short but it is the conceptual heart of the chapter.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
name = "axi_smoke"
first = name[0]
print(first)
print(type(first))
print(len(first))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
a
<class 'str'>
1

Look at what type() reported: str. Not a character type — Python does not have one. Indexing a string gives you back a string, one character long.

That is not a technicality; it is what makes indexing easy to work with. Because the result is an ordinary string, everything you already know applies to it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
name = "axi_smoke"
first = name[0]
print(first + first)
print(name[0] == "a")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
aa
True

You concatenated it with + from Chapter 4.3 and compared it with == from Chapter 3.4, and neither needed anything special. A one-character string is just a string.

4. Reaching the Last Character the Long Way

You often want the last character without knowing how long the string is. One way uses len():

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
name = "axi_smoke"
print(len(name))
print(name[len(name) - 1])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
9
e

Nine characters, so the last position is 8, which is len(name) - 1. That expression is correct and you should be able to read it, because the - 1 is exactly the zero-based numbering showing up again: the last valid index is always one less than the length.

It is also clumsy, which is why Python offers something better.

5. Negative Indexes Reach From the End

A negative index counts backwards from the end of the string:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
name = "axi_smoke"
print(name[-1])
print(name[-2])
print(name[-9])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
e
k
a

name[-1] is the last character. name[-2] is the one before it. name[-9], on a nine-character string, is the first.

The two numbering systems address the same characters, so for any string the first character can be reached both ways:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
name = "axi_smoke"
print(name[0] == name[-9])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
True

Both reached the same a. The ruler in Section 2 shows why: the two rows of numbers label the same cells.

Negative indexing is genuinely useful. This tells you whether a path ends in a separator without measuring anything:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
p = "runs/axi_smoke/"
print(p[-1])
print(p[-1] == "/")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
/
True

Compare that with p[len(p) - 1]. Same answer, more noise.

6. Escapes Occupy One Position

A detail that follows from Chapter 4.2 and catches people out. An escape sequence is one character, so it occupies exactly one position:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
e = "a\nb"
print(len(e))
print(e[1] == "\n")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
3
True

Three positions — 0, 1, 2 — and position 1 is the newline. It is not two positions, and e[1] is not a backslash. If you predicted that e[1] would be \ and e[2] would be n, re-read Chapter 4.2 Section 3 before going on to slicing, because every slice boundary in the next chapter depends on getting this right.

7. Broken on Purpose

Five failures, all executed. The messages are exactly what Python prints.

Asking for a position that does not exist. A nine-character string has no position 9:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
name = "axi_smoke"
print(name[9])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
IndexError: string index out of range

This is the error the canonical curriculum wants you to be able to read, so read it literally: the index was out of the range of valid positions. For a string of length 9 the valid range is 0 to 8, and 9 is one past the end. Whenever you see IndexError, the first thing to check is an off-by-one against the length.

Going too far negative fails the same way, because -10 is one before the start of a nine-character string:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
name = "axi_smoke"
print(name[-10])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
IndexError: string index out of range

An empty string has no valid index at all — not even 0, because there is no first character to reach:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
print(""[0])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
IndexError: string index out of range

That one matters in practice. Text that arrives from a file or a tool can legitimately be empty, and line[0] on an empty line raises rather than returning something blank.

The index must be a whole number. A float is refused even when it looks whole:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
print("abc"[1.0])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
TypeError: string indices must be integers, not 'float'

Note this is a TypeError, not an IndexError — the position was not out of range, it was the wrong kind of value entirely. Chapter 2.6 explains why 1.0 and 1 are different types. This shows up when a position is calculated with /, which as Chapter 3.2 established always produces a float; // keeps it an integer.

Assigning to a position is refused. This is the promised evidence for Section 3:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
text = "PASS"
text[0] = "F"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
TypeError: 'str' object does not support item assignment

Reading a position works; writing one does not exist as an operation. "Does not support item assignment" is Python saying: this kind of value has no way to have one of its items replaced. Chapter 4.11 shows what to do instead.

8. Predict, Run, Observe

Given this string, predict all six before reading on:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sig = "clk_en"
print(sig[0])
print(sig[3])
print(sig[-1])
print(sig[-6])
print(len(sig))
print(sig[0] == sig[-6])

The verified output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
c
_
n
c
6
True

Position 3 is the underscore — count c, l, k, then _ at index 3. sig[-1] is the final n. sig[-6] is the first character of a six-character string, so it equals sig[0], which is why the last line is True.

Now one more, and this is the one to be careful about:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sig = "clk_en"
print(sig[6])

That is an IndexError. The string has six characters, so the last valid index is 5. If you predicted n you were thinking one-based; sig[6] is one past the end for exactly the same reason name[9] was in Section 7.

9. Exercises

Exercise 1 — read the ruler. For word = "RESET", give the character at each of these without running anything, then check: [0], [2], [4], [-1], [-5].

Exercise 2 — both ways. Write two different expressions that each produce the last character of path = "sim.log". Say in one sentence which you would prefer in real code and why.

Exercise 3 — what type? Predict the output of all three lines, then run them:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
c = "PASS"[0]
print(c)
print(type(c))
print(len(c))

Exercise 4 — find the off-by-one. This is meant to print the last character of the name but raises IndexError. Say what the valid index range is for this string and fix the line:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
name = "axi_smoke"
print(name[len(name)])

Exercise 5 — the empty line. Explain in one sentence why line[0] is risky for text that came from a file, and name the error it raises when the text is empty.

Exercise 6 — prove it is not a cell. Write the two lines that attempt to change the first character of "FAIL" to "P", run them, and write down the exact error. Then say in one sentence what that error tells you about how Python stores text.

Exercise 7 — count the escape. For line = "PASS\n", predict len(line), line[-1], and whether line[-1] == "\n" is True. Explain your answer for the length in terms of Chapter 4.2.

10. Summary

  • name[i] reaches the character at position i, and it is an ordinary expression that evaluates to a value.
  • Positions start at 0. name[0] is the first character; name[1] is the second.
  • The last valid index is len(name) - 1. That - 1 is the zero-based numbering reappearing.
  • Indexing gives back a one-character string, of type str. Python has no separate character type, so everything you know about strings applies to the result.
  • A Python string is not an array of writable character cells. Reading a position works; assigning to one is a TypeError.
  • -1 is the last character, -2 the one before it, -len(name) the first. There is no -0.
  • An escape sequence occupies one position. In "a\nb", position 1 is the newline.
  • IndexError: string index out of range means the position does not exist — check it against the length. An empty string has no valid index, not even 0.
  • TypeError: string indices must be integers is a different failure — the index was the wrong type, typically a float from /.

Reaching one character is useful; reaching a run of them is more useful. The next chapter takes pieces out of a string.

Where this fits

Part of the Python curriculum.