Chapter 21: Python: Strings

Meera helps at the front desk of her school office on Saturday mornings. Visitors write their name, their phone number and the reason for their visit in a register, and someone types all of it into the computer.

One Saturday the office started using a new visitor form on screen. Meera typed a name with an extra space at the end, and the form quietly removed it. It joined the first name and the last name into one line for the printed pass. It counted the letters in the name to check that they would fit inside the small box on the pass. It turned the heading into capital letters. When a letter was typed by mistake into the phone number, a warning appeared.

None of this is the computer understanding anything. Every one of those actions is a piece of text being measured, checked, cut, or stuck to another piece of text. In Python, a piece of text is called a string, and Python comes with a ready-made set of tools for exactly this kind of work (see Fig. 21.0-1).

Diagram

Fig. 21.0-1 – a school visitor pass being built up from typed text; on the left, three boxes labelled “First name”, “Last name” and “Reason”; an arrow to the right leading to a printed pass card showing the joined full name in capitals, with a small counter beside it showing the number of letters

NoteCuriosity Corner

Q1. Why does a computer treat a person’s name and a phone number typed into a form as the same kind of data? Q2. If a piece of text cannot be changed once it is made, how does a program correct a spelling mistake in it? Q3. How does a program pull out just the pin code from a full address? Q4. Why does a computer say that “Ravi” and “ravi” are not the same? Q5. How do you put a line break or a tab inside a piece of text, when you cannot press the Enter key inside quotes?

This chapter will answer each of these five questions as you work through it.

21.1 What a String Is

A computer program does not only work with numbers. Think of the software at a school office. It stores names, addresses, subject names, the reason a visitor came, the message printed at the top of a report card. None of these can be added or multiplied. They are text, and a program needs a way to hold text just as safely as it holds a number.

Python gives you exactly that. A piece of text in Python is a string: a run of characters kept together in one value. A character is a single letter, digit, space, or symbol — R, 7, a space, or ! are each one character.

You make a string by putting the text inside quotes. The quotes are not part of the text. They are only a signal to Python saying “everything between these marks is text, so do not try to read it as a number or as an instruction.”

NoteDefinition

(A string is a value made of a sequence of characters, written inside quotes, that Python stores and works with as text.)

Notice that a phone number typed into a form is also text. The digits in it are never added or divided; they are only stored, shown, and checked. So a program keeps a phone number as a string, in the same way it keeps a name.

Q1. Why does a computer treat a person’s name and a phone number typed into a form as the same kind of data?

A. Because both are typed in as characters and neither is used for arithmetic. A phone number is never added to anything — it is only stored, displayed, and checked digit by digit. So a program keeps both of them as strings, which is the type meant for text.

Q. Is the digit 7 inside a string the same as the number 7?

A. No. 7 on its own is a number you can do sums with. "7" is a string holding one character. Python will not add 1 to "7" — it treats it as text until you convert it.

21.1.1 Characters and Their Positions

A string is not one solid lump. It is a sequence, which means the characters sit in a fixed order, one after another, and each one has its own position. That position is called its index.

Python numbers these positions starting from 0, not from 1. So in the string "PYTHON", the letter P is at index 0, Y is at index 1, and the last letter N is at index 5. See Fig. 21.1-1.

Diagram

Fig. 21.1-1 – six joined boxes in a row holding the letters P, Y, T, H, O, N; below each box, its index number 0, 1, 2, 3, 4, 5; a label under the row reading “6 characters, indexes 0 to 5”

Counting from 0 feels odd at first, but it is worth getting used to early. Almost everything you do with strings later — picking one character, cutting out a piece, searching for a word — is described using these index numbers.

There is one small trap. A string of 6 characters has indexes 0 to 5, so the last index is always one less than the number of characters. Asking for index 6 in "PYTHON" is asking for a seventh character that does not exist, and Python stops with an error.

Spaces count as characters too. "Ravi Sharma" holds 11 characters, not 10, because the space between the two words sits at index 4 and takes up a position of its own.

21.1.2 A String Cannot Be Changed

Once a string exists, its characters are fixed. You cannot reach into "Meeta" and swap the a for an r. In Python, strings are said to be immutable, which simply means “cannot be changed after being made”.

NoteDefinition

(Immutable means that once a value is created its contents cannot be altered; any operation that appears to change it actually produces a new value.)

This sounds like a limitation, and students often trip over it the first time.

trying to change one character of a string What goes wrong: A student writes name = "Meeta" and then name[4] = "r", expecting the last letter to change. Python refuses and reports TypeError: 'str' object does not support item assignment. The fix: Build the corrected text as a new string and store it in the variable, as shown below. A variable is just a name that points at a value, so pointing it at a new string is allowed even though editing the old one is not.

correcting a misspelt name Problem: A name has been stored with a spelling mistake. Show that the mistake is fixed by storing a new string in the variable, not by editing the old one.

name = "Meeta"
print(name)

# The old string is not edited. A new string is stored in the same variable.
name = "Meera"
print(name)

Output:

Meeta
Meera

Every string tool you meet in this chapter behaves this way. A tool that changes case, or replaces a word, or trims a piece out, always hands you back a fresh string and leaves the original exactly as it was. If you want to keep the result, you must store it somewhere.

Q2. If a piece of text cannot be changed once it is made, how does a program correct a spelling mistake in it?

A. It does not edit the old string. It builds a corrected string as a new value and stores that new value in the variable. The original string is simply left behind and no longer used.

The honest cost of immutability is memory and time. Every correction makes another string rather than adjusting the one you have, so a program that changes a very long piece of text thousands of times does more work than you might expect. The gain is safety: once a string is handed to another part of a program, nothing can quietly alter it behind your back.

21.2 Creating a String

Before a program can do anything with text, the text has to get into the program. You cannot simply type the words on a line and hope Python understands, because Python reads everything as an instruction unless you tell it otherwise. Quotes are how you tell it otherwise.

Python accepts three ways of quoting text: single quotes, double quotes, and triple quotes. All three make a string. Which one you pick depends on what is inside the text and whether it runs over more than one line.

21.2.1 Single Quotes and Double Quotes

Text wrapped in single quotes ('...') and text wrapped in double quotes ("...") give exactly the same result. 'Hello' and "Hello" are the same string of five characters. Python does not remember which style you used.

So why have two? Because sometimes the text itself contains a quote mark. An apostrophe is the common case. If you write 'Today's timetable', Python sees the apostrophe after Today as the end of the string and then cannot make sense of the rest of the line. Wrapping the same text in double quotes solves it neatly.

storing school text in strings Problem: Store the name of a school, a short notice, and a sentence containing an apostrophe, then display all three.

school = 'Government Senior Secondary School'
notice = "The library is closed today."
message = "Today's timetable has changed."

print(school)
print(notice)
print(message)

Output:

Government Senior Secondary School
The library is closed today.
Today's timetable has changed.

The rule that matters is this: the quote mark you open with is the quote mark you must close with. Python is looking for a matching partner, not for a particular style.

an apostrophe inside single quotes What goes wrong: A student writes message = 'Today's timetable has changed.'. Python treats the apostrophe as the closing quote, so the string ends after Today, and the line fails with a syntax error. The fix: Wrap the text in double quotes instead, as in the program above. Section 21.6 shows a second way, using an escape sequence, for the times when both kinds of quote appear in the same sentence.

Q. Does Python store 'Hello' and "Hello" as two different strings?

A. No. Both are the same five characters. The quote style is only about how you typed it, and it disappears once the string is made.

21.2.2 Triple Quotes

Some text does not sit on one line. An address has three or four lines. A notice pinned to a board has a heading and then a message. Single and double quotes cannot hold text like this, because pressing Enter inside them ends the line before the closing quote is reached.

Triple quotes solve that. You open with three quote marks and close with three quote marks, and everything in between — including the line breaks you press — becomes part of the string.

address = """12, Station Road
Jaipur
302001"""

print(address)

Output:

12, Station Road
Jaipur
302001

Both '''...''' and """...""" work in the same way. Text written like this is called a multiline string, and Section 21.4 looks at it more closely.

Table 21.1 sums up when each style is the right choice.

Table 21.1 – Single or double quotes vs triple quotes

Feature Single or double quotes Triple quotes
Written as 'text' or "text" '''text''' or """text"""
Number of lines One only One or many
Line breaks you press Not allowed inside Kept as part of the string
Typical use A name, a word, a short message An address, a notice, a block of text

21.2.3 The Empty String

A string is allowed to hold nothing at all. Write two quote marks with nothing between them and you get an empty string — a real string value that happens to contain zero characters.

This is not a useless oddity. It is the usual starting point when a program builds text up piece by piece, in the same way a shopkeeper starts a bill with a total of zero before adding any item. It is also what a form field holds when the visitor has typed nothing yet, which lets the program check whether an answer was actually given.

answer = ""

print(answer)
print("(the line above is empty)")

Output:


(the line above is empty)

An empty string is not the same as a space. " " contains one character — the space — while "" contains none. A program that checks whether a form field was filled in must keep this difference in mind, or a field containing only a spacebar press will pass as a proper answer.

21.3 Traversing a String

Plenty of jobs cannot be done by looking at a string as a whole. Counting how many vowels are in a name, checking that every character typed into a phone number field is a digit, printing a word letter by letter on a display — each of these needs the program to visit one character, deal with it, then move to the next.

Going through a string one character at a time, from the first to the last, is called traversing it. See Fig. 21.3-1.

Diagram

Fig. 21.3-1 – the letters M, e, e, r, a in five boxes with indexes 0 to 4 below them; a curved arrow above the boxes moving left to right, touching each box in turn, labelled “one character at a time”

NoteDefinition

(Traversing a string means visiting each of its characters in order, usually with a loop, so that the same action can be carried out on every character.)

Python offers four common ways to do this. They all end up visiting the same characters; they differ in how much extra information you get and how much control you have.

21.3.1 Traversing with a for Loop

This is the shortest way. A for loop can walk through a string directly, and on each pass a variable holds the next character. You do not have to mention index numbers at all.

printing a name letter by letter Problem: Display every character of a stored name on a line of its own.

name = "Meera"

for letter in name:
    print(letter)

Output:

M
e
e
r
a

Read the loop as a sentence: for each letter in the name, print that letter. The loop stops on its own when there are no characters left, so you never have to work out where the string ends.

Q. What does this loop do if the string is empty?

A. Nothing. There are no characters to visit, so the body of the loop never runs and the program simply carries on with the next line.

The one thing this form does not give you is the position of the character. If you need to know where a character sits, use one of the next two methods.

21.3.2 Traversing with range() and Indexing

Here you loop over the index numbers instead of the characters, and pick out each character yourself using name[i].

To do that you need to know how many characters the string has. The len() function reports exactly that — len("Meera") is 5. It is looked at properly in Section 21.8.1; for now, treat it as the count of characters.

range(5) produces the numbers 0, 1, 2, 3, 4 — which are precisely the valid indexes of a five-character string. That is why range(len(name)) is such a common pairing.

showing each character with its position Problem: Display every character of a name along with the index it sits at.

name = "Meera"

for i in range(len(name)):
    print(i, name[i])

Output:

0 M
1 e
2 e
3 r
4 a

Because range stops one short of the number given to it, the last index used is 4, not 5. That matches the string, whose last index is one less than its length.

21.3.3 Traversing with a while Loop

A for loop always runs to the end of the string. Sometimes you want to stop partway — at the first space, say, to pull out only the first name from a full name. A while loop suits that, because you write the stopping condition yourself.

printing only the first name Problem: A full name is stored as one string. Print its characters one by one, stopping as soon as a space is reached.

full_name = "Ravi Sharma"
i = 0

while full_name[i] != " ":
    print(full_name[i])
    i = i + 1

Output:

R
a
v
i

The counter i starts at 0 and moves forward by one each time. The loop checks the condition before every pass, so when i reaches 4 — the position of the space — the condition is false and the loop ends.

Be honest about the weakness of this loop: it depends on a space actually being there. If the stored name were "Ravi" with no space at all, i would keep growing past the last index and Python would stop with an index error. A real program would also check that i has not gone past the end of the string.

forgetting to move the counter forward What goes wrong: The line i = i + 1 is left out. The condition then tests the same character forever, the same letter is printed over and over, and the program never stops on its own. The fix: Inside every while loop, make sure something changes each pass that will eventually make the condition false — here, moving i one step forward.

21.3.4 Traversing with enumerate()

Writing range(len(...)) and then name[i] works, but it is easy to get wrong. enumerate() does both jobs at once: on each pass it hands you the index and the character sitting at that index.

listing positions with enumerate() Problem: Display each character of the word ROBOT together with its position, without using range or len.

word = "ROBOT"

for position, letter in enumerate(word):
    print(position, letter)

Output:

0 R
1 O
2 B
3 O
4 T

Two variables are written after for because enumerate supplies two values on each pass. The first is the position, the second is the character. Table 21.2 compares this with the earlier method.

Table 21.2 – range() with indexing vs enumerate()

Feature range(len(s)) with s[i] enumerate(s)
What the loop variable holds The index only The index and the character
Getting the character You write s[i] yourself Given to you directly
Chance of an index mistake Higher Lower
When it is still useful When you need to skip or jump indexes When you simply want every character in order

Replace ROBOT in the last program with your own school’s name and predict, before you run it, what the largest position printed will be. Check whether your prediction matches the output.

21.4 Multiline Strings

A notice on a school board is never one line. It has a heading, then a date, then a place, then an instruction. If a program has to store that whole notice, storing it as four separate strings and printing them one after another is clumsy — the four pieces belong together, and any part of the program that uses them has to remember to keep them in order.

Triple quotes let you keep the whole block as a single value. Text written this way, running across more than one line, is called a multiline string.

NoteDefinition

(A multiline string is a string written inside triple quotes so that it can span several lines, with the line breaks stored as part of the string itself.)

The important idea is that the line breaks are not decoration. Every place you press Enter between the opening and closing triple quotes becomes a real character inside the string, and that is what makes the text come out on separate lines when it is printed. See Fig. 21.4-1.

Diagram

Fig. 21.4-1 – on the left, four lines of typed notice text between opening and closing triple quotes, with a small return-arrow symbol drawn at the end of each of the first three lines; an arrow pointing right to a printed notice showing the same four lines; a label under the return symbols reading “each one is stored as a line-break character”

storing a school notice as one string Problem: Keep a four-line club notice in a single variable and display it exactly as written.

notice = """Science Club Meeting
Friday, 3:30 p.m.
Physics Lab
Bring your notebook."""

print(notice)

Output:

Science Club Meeting
Friday, 3:30 p.m.
Physics Lab
Bring your notebook.

Only one print() was needed for four lines of output, because all four lines are inside one string.

Three single quotes '''...''' do the same job as three double quotes """...""". Pick one style and stay with it through a program, so the code is easy to read.

Q. Do the line breaks count as characters in the string?

A. Yes. Each break is one character stored between the two lines it separates, which is why the text prints on separate lines instead of running together.

an unwanted blank line at the start What goes wrong: A student presses Enter immediately after the opening """ to make the code look tidy. That Enter is stored too, so the printed notice begins with a blank line. The fix: Begin the text on the same line as the opening triple quotes, as in the program above. If you prefer the tidier layout, remember that the blank first line is part of the string and will show up in the output.

The same warning applies to spaces. Whatever you type inside the triple quotes is kept exactly — including indentation. If a multiline string is written inside a loop and its lines are indented to match the code around them, those spaces appear in the output as well.

Programmers often place a triple-quoted block of text at the very top of a program or a function to explain what it does. Python reads it as a string, does nothing with it, and moves on — which makes it a convenient place to keep a note for whoever reads the code next.

Multiline strings are not the only way to get text onto separate lines. Section 21.6 shows a second method that works inside ordinary single or double quotes, and which is easier when you only need one break in the middle of a sentence.

21.5 Concatenating Strings

A school office keeps a visitor’s first name and last name in two separate fields, and it has good reason to. The list of visitors is sorted by last name; the greeting on screen uses only the first name. But the printed pass needs the whole name on one line. Somewhere in the program, two pieces of text must be stuck together into one.

Joining strings end to end like this is called concatenation, and in Python it is done with the + operator.

NoteDefinition

(Concatenation means joining two or more strings end to end to produce a single new string.)

The + sign does two quite different jobs depending on what sits beside it. Between two numbers it adds. Between two strings it joins. Nothing is calculated when strings are joined — the characters of the second string are simply placed after the characters of the first. See Fig. 21.5-1.

Diagram

Fig. 21.5-1 – three small boxes in a row holding “Ravi”, a single space, and “Sharma”, joined by two plus signs; an arrow pointing down to one longer box holding “Ravi Sharma”, labelled “a new string”

building a full name for a visitor pass Problem: A first name and a last name are stored separately. Join them into one full name with a single space between them.

first_name = "Ravi"
last_name = "Sharma"

full_name = first_name + " " + last_name
print(full_name)

Output:

Ravi Sharma

Look carefully at the middle piece. The space between the two names had to be supplied as a string of its own, " ". Python does not put anything between the joined pieces by itself.

expecting a space to appear on its own What goes wrong: A student writes full_name = first_name + last_name and the pass prints RaviSharma. Nothing has gone wrong in Python — it joined exactly what it was given. The fix: Add the separator yourself: first_name + " " + last_name. If the two pieces should touch, as in a code like "AI" + "101", leave it out.

Q. After joining, have first_name and last_name changed?

A. No. Strings cannot be changed, so + builds a brand new string and leaves both originals exactly as they were.

Joining text and numbers

A pass usually carries a number as well as a name. That is where students meet a firm rule: + will not join a string to a number.

adding a number to a string What goes wrong: A student writes "Pass " + 17. Python cannot tell whether this means arithmetic or joining, so it refuses with TypeError: can only concatenate str (not "int") to str. The fix: Turn the number into text first with str(), which produces a string holding those digits. Then both sides of the + are strings and the join is allowed.

printing a pass line that includes a number Problem: Print one line giving a pass number and the name of the visitor it belongs to.

name = "Ravi Sharma"
pass_number = 17

line = "Pass " + str(pass_number) + " issued to " + name
print(line)

Output:

Pass 17 issued to Ravi Sharma

After str(17), the value is the two characters 1 and 7, not the quantity seventeen. It can be joined and printed, but it can no longer be used in a sum.

Building text piece by piece

Concatenation is also how a program grows a string inside a loop. It starts from an empty string and adds one piece on each pass, the way a shopkeeper starts a bill at zero and adds each item.

listing several names on one line Problem: Three student names are stored in a list. Build a single line that holds all three, separated by commas.

students = ["Amit", "Neha", "Farah"]
line = ""

for student in students:
    line = line + student + ", "

print(line)

Output:

Amit, Neha, Farah,

The result is not quite right, and the reason is worth understanding. The separator is added after every name, including the last one, so the line ends with a stray comma and space. Fixing that by hand means adding a check inside the loop. Section 21.8.8 shows join(), a tool made for this exact job that puts the separator only between the pieces.

There is a second, quieter cost. Because strings cannot be changed, each pass of the loop throws away the old line and builds a completely new one. For three names that is nothing at all. For a program stitching together thousands of pieces, it is real work, and join() is the better choice there too.

21.6 Escape Sequences

Some characters cannot simply be typed inside quotes. You cannot press Enter in the middle of a single-quoted string, because that ends the line before the closing quote is reached. You cannot put a double quote inside a double-quoted string, because Python reads it as the end of the string. And there is no key that produces a neat column-aligning tab inside text.

Python’s answer is to write these characters as a pair: a backslash (\) followed by one ordinary character. The backslash tells Python “do not read the next character normally — it stands for something else”. Such a pair is called an escape sequence.

NoteDefinition

(An escape sequence is a backslash followed by a character, used inside a string to stand for a character that cannot be typed directly.)

Table 21.3 lists the ones you will use most often.

Table 21.3 – common escape sequences and what each one produces

Escape sequence Stands for Typical use
\n A line break Starting a new line inside one string
\t A tab Lining up text in columns
\\ One backslash Showing a file path or a backslash itself
\' A single quote An apostrophe inside a single-quoted string
\" A double quote A quoted phrase inside a double-quoted string

a notice printed with line breaks and quote marks Problem: Print a two-line notice from a single string, then print two sentences that contain quote marks of their own.

message = "Science Club Meeting\nFriday, 3:30 p.m."
print(message)

print("The board said, \"Bring your notebook.\"")
print('It\'s in the Physics Lab.')

Output:

Science Club Meeting
Friday, 3:30 p.m.
The board said, "Bring your notebook."
It's in the Physics Lab.

The first string was written on one line of code but printed on two, because \n inside it stands for a line break. The last two lines show the escape sequences for quote marks doing the job that a change of quote style did in Section 21.2.1. Both approaches are correct; escaping is what you need when the sentence contains an apostrophe and a double quote, so no choice of quote style would avoid the clash.

Q. How many characters does the string "\n" hold?

A. One. The backslash and the n are two keys you press, but Python stores them as a single line-break character.

Q5. How do you put a line break or a tab inside a piece of text, when you cannot press the Enter key inside quotes?

A. You write an escape sequence instead of pressing the key. \n stands for a line break and \t stands for a tab, and each is stored in the string as one real character. Triple quotes are the other way of getting line breaks, and they suit long blocks of text better.

The tab escape is useful whenever printed output needs to line up.

printing pass details in two columns Problem: Print three labelled details so that the values line up under one another.

print("Name:\tRavi Sharma")
print("Class:\tX-B")
print("Pass:\t17")

Output:

Name:   Ravi Sharma
Class:  X-B
Pass:   17

A tab moves the text across to the next tab stop rather than adding a fixed number of spaces. That is why the values line up here even though the three labels are of different lengths. It is also the limitation: if one label were very long, it would push past the tab stop and that row would no longer match the others.

a backslash in a file path being read as an escape What goes wrong: A student writes path = "D:\notes\table.txt". Python reads \n as a line break and \t as a tab, so the printed path is broken across lines with a gap in the middle. The fix: Write each backslash twice — "D:\\notes\\table.txt" — so that each pair stands for one real backslash. Forward slashes also work in file paths and avoid the problem entirely.

21.7 String Operators

An operator is a symbol that does something to the values on either side of it. You have already used one on strings: +, which joins them. Python gives strings a small set of others, and between them they cover most of the everyday questions a program asks about text — repeat this, is that word in there, which of these two comes first, give me the character at this position, give me the piece between these positions.

Each of these operators leaves the original string untouched and hands back a new value, exactly as concatenation does.

21.7.1 Repetition

The * operator repeats a string a given number of times. Between two numbers it multiplies; between a string and a number it repeats.

The obvious use is drawing a line across a printed report. Typing twenty dashes by hand is tedious and easy to get wrong, and changing the width later means retyping them.

a heading with a ruled line Problem: Print a report heading with a line of dashes above and below it.

line = "-" * 20

print(line)
print("Attendance Report")
print(line)

Output:

***
Attendance Report
***

The number must be a whole number, and it must be the count of repeats, not another string — "ab" * "3" is meaningless to Python and fails. Repeating by 0, or by a negative number, is allowed and quietly gives the empty string, which occasionally surprises people whose count came out as zero.

21.7.2 Membership: in and not in

Often a program needs to know only whether a piece of text appears somewhere inside a larger piece — does the visitor’s reason mention fees, does this sentence contain the word urgent. The in operator answers that with True or False, and not in answers the opposite question.

The piece being looked for is called a substring: any run of characters taken from inside a string.

checking what a subject name contains Problem: Report whether certain words appear inside a stored subject name.

subject = "Robotics and Artificial Intelligence"

print("Artificial" in subject)
print("Python" in subject)
print("python" not in subject)

Output:

True
False
True

The last two lines are worth pausing on. "Python" is not inside the subject name at all, so the second line is False. The third asks whether "python" is absent, and the answer is True.

in looks for the characters exactly as given, capital letters included. "artificial" in subject would be False, even though the word is plainly there, because the stored word begins with a capital A. Case matters, and the next sub-section explains why.

21.7.3 Comparing Two Strings

Strings can be compared with the same operators used for numbers: ==, !=, <, >, <= and >=. Testing whether two strings are equal is straightforward. Testing which one is smaller needs an explanation.

Every character a computer stores has a number behind it, fixed by a worldwide standard called Unicode. Capital A is 65, capital Z is 90, small a is 97, small z is 122. Digits sit lower still, and the space is lower than all of them.

To compare two strings, Python looks at the first character of each. If they differ, the one with the smaller Unicode number is the smaller string and the comparison stops there. If they match, it moves to the next character and repeats. This is how a program sorts a list of names into order.

comparing names Problem: Show the result of comparing pairs of names for equality and for order.

print("Ravi" == "Ravi")
print("Ravi" == "ravi")
print("Amit" < "Neha")
print("Zoya" < "amit")

Output:

True
False
True
True

The second line shows that "Ravi" and "ravi" are simply not the same string. The fourth line is the one that catches people out: "Zoya" counts as smaller than "amit" because capital Z is 90 and small a is 97. Sorted this way, every name beginning with a capital comes before every name beginning with a small letter.

Q4. Why does a computer say that “Ravi” and “ravi” are not the same?

A. Because a string is compared character by character, and each character is stored as a Unicode number. Capital R is 82 and small r is 114, so the very first characters differ and the two strings are judged unequal. Nothing about the meaning of the name is considered — only the stored characters.

comparing typed input directly with a stored answer What goes wrong: A program checks reply == "Yes". A user types yes, the comparison is False, and the program behaves as though the user refused. The fix: Put both sides into the same case before comparing — reply.lower() == "yes" — using the tool covered in Section 21.8.2.

The same trap appears when a program looks up a person’s record by name. A record stored as "Farah Khan" will not match a search for "farah khan", and the person is told, wrongly, that no record exists. Matching text about people should be done in a single case, and a failure to match should be reported as “not found” rather than treated as proof that the person does not exist.

21.7.4 Indexing

Square brackets after a string pull out the single character sitting at a given index. code[0] is the first character, because indexes start at 0.

Python also allows counting backwards. Index -1 means the last character, -2 the one before it, and so on. This saves you from working out the length first when all you want is the end of the string. See Fig. 21.7-1.

Diagram

Fig. 21.7-1 – six boxes in a row holding the characters A, I, 2, 0, 2, 6; above the boxes the positive indexes 0 to 5; below the boxes the negative indexes -6 to -1; a caption reading “the same character can be reached from either end”

picking single characters out of a code Problem: A project code is stored as one string. Display its first character, the character at index 2, and its last character.

code = "AI2026"

print(code[0])
print(code[2])
print(code[-1])

Output:

A
2
6

Asking for an index that does not exist stops the program. For this six-character string, code[6] fails with IndexError: string index out of range, because the last valid index is 5.

Q. How do you get the last character without knowing the length?

A. Use index -1. It always means the last character, whatever the length of the string, and it stays correct if the string later changes.

21.7.5 Slicing: string[start:end:step]

Indexing gives one character. Slicing gives a whole piece — a substring — and that is what most real work needs: the pin code at the end of an address, the year inside a project code, the part of a sentence before the first comma.

NoteDefinition

(Slicing means taking a piece of a string by giving a starting index, a stopping index, and optionally a step, written as string[start:end:step].)

The rule to fix in your mind is that the character at start is included and the character at end is not. So word[0:5] gives you five characters — those at indexes 0, 1, 2, 3 and 4.

Either number may be left out. Leaving out the start means “from the beginning”, and leaving out the end means “to the end”. The step says how far to jump each time, and defaults to 1.

slicing a word in different ways Problem: From the word ROBOTICS, produce the first five letters, the rest of the word, every second letter, and the word reversed.

word = "ROBOTICS"

print(word[0:5])
print(word[5:])
print(word[:5])
print(word[::2])
print(word[::-1])

Output:

ROBOT
ICS
ROBOT
RBTC
SCITOBOR

Lines one and three give the same answer, because leaving out the start means the same thing as writing 0. The fourth line starts at the beginning and jumps two at a time, collecting the characters at indexes 0, 2, 4 and 6. The last line uses a step of -1, which walks the string from the end towards the start and so produces it backwards.

what does word[2:6] give?

Given: word = "ROBOTICS", with characters R, O, B, O, T, I, C, S at indexes 0 to 7.

Find: The substring produced by word[2:6].

Working: Start at index 2 and stop before index 6, so the indexes used are 2, 3, 4 and 5. Index 2 holds B, index 3 holds O, index 4 holds T, index 5 holds I.

BOTI — a substring of 4 characters, which is 6 − 2.

That last line gives a quick check you can use anywhere: for an ordinary slice with a step of 1, the number of characters you get is the end index minus the start index.

Slicing is also how a program pulls a fixed part out of longer text.

reading the pin code from an address Problem: An address is stored with the six-digit pin code at the end. Display just the pin code.

address = "12, Station Road, Jaipur 302001"

print(address[-6:])

Output:

302001

Counting back six from the end is safer here than counting forward, because the street name and city may be of any length while the pin code is always six digits.

Q3. How does a program pull out just the pin code from a full address?

A. By slicing. If the pin code is the last six characters, address[-6:] takes the piece starting six characters from the end and running to the end. The program never has to know how long the rest of the address is.

expecting the end index to be included What goes wrong: A student wants the first three characters and writes word[0:2], getting only two of them, or writes word[0:3] expecting four. The fix: Remember that the slice stops before the end index. To get characters 0, 1 and 2, write word[0:3].

Table 21.4 – slice forms and what each gives for word = “ROBOTICS”

Slice written Meaning Result
word[0:5] Index 0 up to but not including 5 ROBOT
word[5:] Index 5 to the end ICS
word[:5] Beginning up to but not including 5 ROBOT
word[-3:] Last three characters ICS
word[::2] Whole word, every second character RBTC
word[::-1] Whole word, backwards SCITOBOR

Q. What happens if the slice numbers go past the end of the string?

A. Nothing breaks. word[3:50] simply gives everything from index 3 onwards, and a slice with no characters in it, such as word[6:2], gives the empty string. Indexing is the strict one — slicing is forgiving.

21.8 Built-in String Functions

Operators cover the simple questions. The harder ones — how long is this, is every character a digit, where does this word begin, put these names on one line with commas between them — come up so often that Python ships ready-made tools for them. Writing your own loop for each would be slow to do and easy to get wrong.

Most of these tools are methods. A method is a function that belongs to a value and is called by writing a dot after that value: name.upper(). The value in front of the dot is the string being worked on, and the round brackets hold anything extra the method needs.

Two things stay true throughout this section. A method never changes the string it was called on; it hands back a new value that you must store or print. And a method that asks a yes-or-no question gives back True or False.

21.8.1 Finding the Length: len()

The number of characters in a string is needed constantly — to check that a name fits inside a printed box, that a phone number has ten digits, that a form field is not blank.

len() is the odd one out in this section. It is a plain function, not a method, so the string goes inside the brackets: you write len(name), not name.len().

checking the length of stored details Problem: Report how many characters a full name holds, and check whether a stored phone number has exactly ten digits.

name = "Ravi Sharma"
phone = "9812345678"

print(len(name))
print(len(phone) == 10)

Output:

11
True

The name counts as 11 characters, not 10, because the space between the two words takes a position of its own. A length check like the second one is the usual first test on a typed phone number, though on its own it proves very little — ten letters would pass it just as easily. Section 21.8.7 supplies the missing half of that check.

21.8.2 Changing Case: upper() and lower()

upper() returns the string with every letter turned into a capital, and lower() returns it with every letter turned small. Headings on a printed report are the visible use. The quieter and more important use is making two pieces of text comparable, as Section 21.7.3 warned.

producing both cases of a name Problem: Display a stored name in capitals, in small letters, and then display the original again.

name = "Ravi Sharma"

print(name.upper())
print(name.lower())
print(name)

Output:

RAVI SHARMA
ravi sharma
Ravi Sharma

The third line is the point of the program. Neither method touched name. If you want the capitalised version kept, you must store it: heading = name.upper().

Characters that are not letters — spaces, digits, punctuation — are passed through unchanged by both methods.

21.8.3 Checking the Start and the End: startswith() and endswith()

Sometimes only the beginning or the end of a string matters. Roll numbers in a school may begin with the class code. Data files are recognised by what they end with. Both methods answer with True or False.

checking a roll number and a file name Problem: Check whether a roll number begins with the class letter, and whether a file name ends with .csv.

roll = "X-B-17"
file_name = "marks.csv"

print(roll.startswith("X"))
print(roll.startswith("x"))
print(file_name.endswith(".csv"))

Output:

True
False
True

The middle line is the familiar warning in a new place: these methods match characters exactly, so a small x does not match a capital X.

Both methods can also be given a longer piece of text — roll.startswith("X-B") is True — which makes them useful for spotting a whole prefix rather than a single letter.

21.8.4 Searching Inside a String: find()

in tells you whether a substring is present. find() tells you where it is, giving the index at which the substring begins. If the substring is not there at all, find() gives -1 rather than stopping the program with an error.

locating a word inside a notice Problem: Report the position at which the word Club begins in a notice heading, and what happens when a missing word is searched for.

text = "Science Club Meeting"

print(text.find("Club"))
print(text.find("Lab"))

Output:

8
-1

Count it through to see why. Science fills indexes 0 to 6, the space sits at index 7, and so C sits at index 8. The word Lab never appears, so the answer is -1.

The -1 is the reason find() is normally followed by a test. A program that slices from the returned position without checking would happily slice from index -1, which means the last character — quietly wrong, rather than obviously broken.

Q. What does find() give if the word appears more than once?

A. The index of the first appearance only — the lowest one. It stops looking as soon as it finds a match.

21.8.5 Counting Occurrences: count()

count() reports how many times a substring appears in a string. It is the tool behind questions like “how many times does this word repeat in the notice” or “how many of these characters are vowels”.

counting a word and a letter Problem: Count how many times the word your and the letter o appear in a notice.

notice = "Bring your notebook and your pen."

print(notice.count("your"))
print(notice.count("o"))

Output:

2
5

The word appears twice. The letter o appears once in the first your, three times in notebook, and once in the second your, giving five in all.

One honest limitation: count() does not count overlapping appearances. In the string "aaa", counting "aa" gives 1, not 2, because after matching the first two characters it carries on from the third.

21.8.6 Replacing Part of a String: replace()

replace() takes the piece to look for and the piece to put in its place, and returns a new string with the swap made everywhere. It is the everyday answer to the question raised back in Section 21.1.2 — how do you correct text that cannot be changed.

updating a notice Problem: Change the day named in a notice, show that the original is untouched, and then replace only the first of three repeated words.

text = "The library is closed on Monday."
seats = "empty, empty, empty"

print(text.replace("Monday", "Tuesday"))
print(text)
print(seats.replace("empty", "taken", 1))

Output:

The library is closed on Tuesday.
The library is closed on Monday.
taken, empty, empty

The second line proves once more that nothing was altered — the new text exists only in what was printed, because it was not stored anywhere.

The third argument, when you give one, limits how many replacements are made. Written as 1, it changes the first occurrence and leaves the rest alone. Left out, every occurrence is changed.

expecting replace() to change the variable What goes wrong: A student writes text.replace("Monday", "Tuesday") on a line by itself, prints text, and finds the old day still there. The fix: Store the result — text = text.replace("Monday", "Tuesday"). The same applies to upper(), lower() and every other method that returns a string.

21.8.7 Checking What a String Contains: islower(), isalpha(), isdigit(), isalnum()

When a form is filled in, the program has to decide whether what was typed is sensible before storing it. Four methods answer the common questions, each returning True or False.

  • isalpha() — are all the characters letters?
  • isdigit() — are all the characters digits from 0 to 9?
  • isalnum() — are all the characters letters or digits, with nothing else mixed in?
  • islower() — are all the letters small letters?

checking typed form entries Problem: Check a phone number, a first name, a user id, a full name, and a name typed entirely in small letters.

phone = "9812345678"
first_name = "Ravi"
user_id = "ravi17"

print(phone.isdigit())
print(first_name.isalpha())
print(user_id.isalnum())
print("Ravi Sharma".isalpha())
print("ravi sharma".islower())

Output:

True
True
True
False
True

The fourth line is the one to study. "Ravi Sharma" fails isalpha() because of the space, which is not a letter. The fifth line passes, because islower() ignores anything that is not a letter and only checks the letters it finds. Table 21.5 gathers the behaviour in one place.

Table 21.5 – what each checking method reports for different strings

String isalpha() isdigit() isalnum() islower()
"Ravi" True False True False
"ravi" True False True True
"ravi17" False False True True
"9812345678" False True True False
"Ravi Sharma" False False False False
"" False False False False

Two rows repay a second look. The empty string fails every one of these checks, because each of them needs at least one character to judge. And "9812345678" fails islower() — there are no letters in it at all, so there is nothing that could be small.

It is tempting to reject any name that fails isalpha(). Real names contain spaces, full stops and hyphens, so such a check would refuse to register perfectly ordinary people while claiming their name is invalid. Keep strict character checks for fields with a fixed format, such as a phone number or a pin code, and let name fields accept what people actually write.

21.8.8 Joining a List into a String: join()

Section 21.5 built a line of names with a loop and ended up with a stray comma at the end. join() is the tool made for that job. It takes a collection of strings — a list or a tuple — and returns one string with a separator placed between the items, never after the last one.

The way it is written surprises everyone at first. The separator is the string you call the method on, and the collection goes inside the brackets.

joining names and number groups Problem: Build one line from a list of student names separated by commas, and one phone number from three groups of digits separated by hyphens.

students = ["Amit", "Neha", "Farah"]
groups = ["9812", "345", "678"]

print(", ".join(students))
print("-".join(groups))

Output:

Amit, Neha, Farah
9812-345-678

There is no trailing comma, and there was no loop to write. With three names the difference is small; with three hundred it is the difference between one clear line of code and a loop with a special case inside it.

joining a list that holds numbers What goes wrong: A student writes "-".join([9812, 345, 678]) with plain numbers instead of strings. Python stops with TypeError: sequence item 0: expected str instance, int found. The fix: Every item must already be a string. Store them in quotes, or convert each one with str() before joining.

Change the separator in the first line of the program above from ", " to " | " and predict the output before running it. Then try joining the same list with the empty string "" as the separator and explain what you get.

21.9 Putting Strings to Work

Each tool in this chapter does one small job. Real programs use several of them together, and choosing the right combination is most of the skill. This section works through three short problems of the kind that come up in school software, and in each one the method is worked out in words before any code is written.

Checking for a palindrome

A palindrome is a word that reads the same forwards and backwards, like malayalam. You already have everything needed to test one: a slice with a step of -1 produces the reversed string, and == compares two strings.

The only extra care needed is case. "Madam" reversed is "madaM", and those are not equal, even though the word plainly is a palindrome. Putting the word into one case first removes that problem.

checking whether a word is a palindrome Input: A single word stored as a string. Output: A message saying whether the word is a palindrome. Steps:

1. Take the word and make a version of it in small letters.
2. Make a reversed copy of that version using a slice with step -1.
3. Compare the two.
4. If they are equal, report that the word is a palindrome.
5. Otherwise, report that it is not.

Why it works: A palindrome is defined by being identical to its own reversal, so comparing the two is a direct test of the definition.

testing a word for a palindrome Problem: Report whether a stored word reads the same in both directions.

word = "Malayalam"
cleaned = word.lower()

if cleaned == cleaned[::-1]:
    print(word, "is a palindrome.")
else:
    print(word, "is not a palindrome.")

Output:

Malayalam is a palindrome.

Notice that word itself is printed in the message, not cleaned. The lowered version was only a working copy for the comparison; the original is what the user typed and what they expect to see.

This test works on a single word. Given a whole sentence, it would fail, because spaces and full stops appear in different places when the text is reversed. Handling those means removing them first, which is a longer job than it looks.

Q. Why compare cleaned with cleaned[::-1] rather than with word[::-1]?

A. Both sides of the comparison must be in the same case. Reversing the original would put a capital letter at the end, and the comparison would fail for a word that really is a palindrome.

Counting vowels and consonants

A common exercise in text work is finding out what a sentence is made of. Here the string has to be traversed, because every character must be examined on its own.

Two decisions are made for each character. Is it one of the five vowels? If not, is it a letter at all? Spaces and punctuation must be counted as neither.

counting vowels and consonants in a sentence Input: A sentence stored as a string. Output: The number of vowels and the number of consonants in it. Steps:

1. Set the vowel count and the consonant count to 0.
2. Take the sentence in small letters, so that A and a are treated alike.
3. Visit each character in turn.
4. If the character is one of a, e, i, o, u, add 1 to the vowel count.
5. Otherwise, if the character is a letter, add 1 to the consonant count.
6. After the last character, report both counts.

Why it works: Every character is tested once, and the two tests together cover exactly the letters, leaving spaces and punctuation uncounted.

counting vowels and consonants Problem: Count how many vowels and how many consonants a stored sentence contains.

sentence = "Robotics and Artificial Intelligence"
vowels = 0
consonants = 0

for character in sentence.lower():
    if character in "aeiou":
        vowels = vowels + 1
    elif character.isalpha():
        consonants = consonants + 1

print("Vowels:", vowels)
print("Consonants:", consonants)

Output:

Vowels: 14
Consonants: 19

Three tools from earlier sections are doing the work together here. lower() makes the test case-blind, in checks membership against a short string of the five vowels, and isalpha() keeps the three spaces out of the consonant count. The two counts add up to 33, which is the number of letters in the sentence.

The order of the two tests matters. If isalpha() were checked first, every vowel would be counted as a consonant, because a vowel is a letter too. The elif is only reached when the vowel test has already failed.

Counting the words in a sentence

Counting words looks harder than counting letters, but one observation makes it simple: in a normally typed sentence, the words are separated by single spaces, so the number of words is one more than the number of spaces. count() supplies the number of spaces directly, with no loop at all.

counting words in a sentence Problem: Report how many words a stored sentence contains.

sentence = "Every robot needs a sensor"
word_count = sentence.count(" ") + 1

print("Words:", word_count)

Output:

Words: 5

Four spaces were found, so five words are reported, which is right.

Be clear about what this method assumes. If the sentence has two spaces between a pair of words, or begins or ends with a space, the count comes out too high. If it is empty, the answer comes out as 1 when it should be 0. Simple methods like this one are fine for tidy input and need extra checks the moment the input is typed by hand.

21.10 Summary

  1. A string is a value made of a sequence of characters, written inside quotes, and it is how a program stores text such as names, addresses and messages.
  2. A phone number typed into a form is kept as a string, because its digits are stored and checked rather than used in arithmetic.
  3. Every character in a string has a position called its index, counted from 0, so the last index is always one less than the number of characters.
  4. Strings are immutable: once made, their contents cannot be altered. Any tool that seems to change a string actually returns a new string and leaves the original untouched.
  5. Single quotes and double quotes make identical strings. Having both styles lets you put a quote mark of one kind inside a string made with the other.
  6. Triple quotes make a multiline string, keeping every line break you press as a real character inside the string.
  7. An empty string, written as two quote marks with nothing between them, holds no characters and is the usual starting point when text is built up piece by piece.
  8. Traversing a string means visiting its characters one at a time. A for loop is the shortest way, range() with indexing or enumerate() also gives the position, and a while loop allows the traversal to stop early.
  9. Concatenation joins strings with +. No separator is added on its own, and a number must be turned into a string with str() before it can be joined.
  10. An escape sequence is a backslash followed by a character, used for characters that cannot be typed directly — \n for a line break, \t for a tab, \\ for a backslash, and \' and \" for quote marks.
  11. The * operator repeats a string, in and not in test whether a substring is present, and the comparison operators judge two strings character by character using their Unicode numbers.
  12. Because capital letters have lower Unicode numbers than small letters, text that differs only in case is never equal, and comparisons should be made in a single case.
  13. Indexing with square brackets gives one character; a negative index counts back from the end, with -1 meaning the last character.
  14. Slicing, written string[start:end:step], gives a substring. The start index is included and the end index is not, a missing number means “from the beginning” or “to the end”, and a step of -1 produces the string backwards.
  15. len() reports the number of characters and is written with the string inside the brackets, unlike the methods, which are written after a dot.
  16. upper() and lower() return the string in one case, which is how two pieces of text are made comparable.
  17. startswith() and endswith() report whether a string begins or ends with a given piece, matching characters exactly.
  18. find() gives the index where a substring begins, or -1 when it is absent, and count() reports how many times it appears, ignoring overlaps.
  19. replace() returns a new string with a substring swapped everywhere, or only a set number of times if a count is given.
  20. isalpha(), isdigit(), isalnum() and islower() check what a string is made of and are used to test what has been typed into a form. All four report False for an empty string.
  21. join() builds one string from a list or tuple of strings, placing the separator only between the items, and every item must already be a string.
  22. Strict character checks suit fields with a fixed format, such as a pin code, but should not be used to judge whether a person’s name is valid.

21.11 Key Terms

  1. Character – A single letter, digit, space, or symbol; the smallest piece a string is made of.

  2. Concatenation – Joining two or more strings end to end with the + operator to make one new string.

  3. Empty string – A string written as two quote marks with nothing between them, holding zero characters.

  4. Escape sequence – A backslash followed by a character, used inside a string to stand for a character that cannot be typed directly, such as \n for a line break.

  5. Immutable – Unable to be changed after being created; any operation on an immutable value produces a new value instead of altering the old one.

  6. Index – The position number of a character in a string, counted from 0 at the start, or from -1 at the end.

  7. Method – A function that belongs to a value and is called by writing a dot after it, as in name.upper().

  8. Multiline string – A string written inside triple quotes so that it can run across several lines, with the line breaks stored as part of it.

  9. Slicing – Taking a piece of a string by giving a start index, an end index, and optionally a step, written as string[start:end:step].

  10. String – A value made of a sequence of characters, written inside quotes, used by a program to store text.

  11. Substring – Any run of characters taken from inside a string.

  12. Traversing – Visiting each character of a string in order, usually with a loop, so that the same action can be carried out on every one.

  13. Unicode – The worldwide standard that gives every character a fixed number, which is what Python uses when it compares two strings.

21.12 Objective Questions

  1. Fill in the blank: In Python, the index of the first character of a string is ______.

  2. What is printed by the following code?

word = "SENSOR"
print(word[1:4])
  1. SEN
  2. ENS
  3. ENSO
  4. NSO
  1. State whether true or false: calling upper() on a string changes the original string stored in the variable.

  2. Which method reports whether every character in a string is a digit from 0 to 9?

    1. isalpha()
    2. islower()
    3. isdigit()
    4. isalnum()
  3. Write the exact output of the following code.

print("AI" * 3)
  1. A program searches a notice for a word that is not present in it, using find(). What value is returned?
    1. 0
    2. -1
    3. The length of the notice
    4. The program stops with an error
  2. Match each escape sequence in the first list with what it stands for in the second list.
Column A Column B
a. \n i. A line break
b. \t ii. A tab
c. \\ iii. One backslash
d. \" iv. A double quote mark

21.13 Concept Questions

  1. Define a string. Explain why a program stores a ten-digit phone number as a string rather than as a number.

  2. Explain what is meant by saying that strings in Python are immutable. State what happens when a program tries to change a single character of a string, and describe the correct way to make the change.

  3. Differentiate between indexing and slicing, giving one short example of each.

  4. Give a reason why the comparison "DELHI" < "delhi" gives True in Python.

  5. For the string word = "MACHINE", work out what word[2:5] produces. Show the index of each character you used, and state how many characters the result holds.

  6. A while loop traverses a string using a counter i. What would happen if the statement that moves i forward were left out? Explain why.

  7. Write a Python program that stores a sentence in a variable and prints the number of vowels it contains. The program should treat capital and small letters alike.

21.14 Case-Based Questions

  1. A school library keeps a member identity code for every student. One such code is stored as follows.
member_id = "LIB2026A"
  1. Write the expression that gives the last character of member_id, without counting how long the code is.
  2. Name the method that checks whether a code contains only letters and digits, and state what it would report for the code "LIB 2026".
  3. A librarian searches for the code by typing lib2026a, and the software reports that no such member exists, although the student is registered. Explain why the comparison failed and describe how the software should be changed.
  1. The school office prints a pass for every visitor. The details of one visitor are stored as shown.
first_name = "Neha"
last_name = "Verma"
pass_number = 8
address = "45, Mill Road, Jaipur 302015"
  1. Write a statement that joins the first name and the last name into one variable called full_name, with a single space between them.
  2. Write the expression that takes the six-digit pin code from the end of address, and explain why counting back from the end is safer here than counting forward.
  3. The line line = "Pass " + pass_number does not work. Explain why, and write the corrected line.