String Operations

By the Syncfusion DevTutorials team · Last updated

Creating and slicing strings, escape sequences, three ways to format, and the string methods grouped by job: case, search, validation, split and join.

Almost everything a program handles is text.

A name typed into a box. A line read from a file. A message shown on screen. A date before it becomes a date. Even numbers arrive as text — you met that with input() in the very first lesson.

What You Will Learn

In this lesson, you will learn:

  • Every way to write a string, and why Python has so many
  • How to reach one character, or a whole run of them
  • How to join, repeat, compare and search text
  • The characters you cannot type directly, and how to write them
  • Three ways to put values inside a sentence
  • The string methods worth knowing, grouped by the job they do

By the end of this lesson, you will be able to write strings every way Python allows, reach and change text through indexing and slicing, put values inside sentences with f-strings, and use the string methods to search, clean and reshape text.

Because strings appear everywhere in Python programs, the skills from this lesson will be used repeatedly in later topics.

Introduction to Strings

What Is a String?

Python's name for text is str, short for string, because it is a string of characters joined together. A string is a sequence of characters, and each character has a position, starting at 0.

Here is text doing a few of the things it knows how to do:

Text, and a few things it can do

student = "Asha"

print(student.upper())
print(len(student))
print(student[0])
print(student + " scored 78")

The text knows how to make itself uppercase, count its own characters, hand over any one of them, and join itself to another piece.

A string is a run of characters in order, and every one of them has a position.

You can walk through those positions one at a time, exactly as you did with arrays:

A string is a sequence

student = "Asha"

print(student[0])
print(student[3])
print(len(student))

for letter in student:
    print(letter)

Anything you learned about indexing an array applies here unchanged.

Creating Strings

You create a string by putting characters inside quote marks.

Three ways to write the same thing

a = "Asha"
b = 'Asha'
c = str("Asha")

print(a, b, c)
print(a == b == c)

str() also turns other values into text:

Making text out of other values

print(str(78))
print(str(78.5))
print(str(True))
print(str([78, 85]))

Single and Double Quotes

Single and double quotes do exactly the same thing. Python has both so you can put one kind inside the other.

Quotes inside quotes

remark = "Asha's work is good"
title = 'She said "well done" today'

print(remark)
print(title)

Using the wrong one stops the program:

A quote that ends the string early

remark = 'Asha's work is good'
print(remark)

The string ended at the second ', and what followed made no sense. Use double quotes around text containing an apostrophe.

Pick one style and keep to it. Double quotes are the more common default.

Triple Quotes

Three quote marks let a string run over several lines.

A string that spans lines

report = """Report card
Student: Asha
Marks: 78"""

print(report)
print(len(report))

The line breaks you typed are part of the string.

Triple quotes are also what docstrings use, which you met in the functions lesson.

Multiline Strings

There are two other ways to build text over several lines.

Three ways to get the same two lines

a = """Line one
Line two"""

b = "Line one\nLine two"

c = ("Line one\n"
     "Line two")

print(a == b == c)
print(a)

\n is a line break written as two characters. The third form joins neighbouring strings automatically, which is handy for a long line of text with no breaks in it:

Splitting a long line in the source only

message = (
    "This message is long enough to be awkward on one line, "
    "so it is written in pieces that Python joins back together."
)

print(message)
print("Line breaks in it:", message.count("\n"))

The text has no line break at all — only the source code is split.

A backslash at the end of a line inside triple quotes stops the first line break:

Stopping the first line break

report = """\
Report card
Student: Asha"""

print(report)

String Type (type())

Knowing the type matters because strings behave differently from numbers. Two values may look similar on the screen but act completely differently in code.

You can check what type a value is, which is how you confirm what you are holding is text:

Checking the type

student = "Asha"
marks = 78

print(type(student))
print(type(marks))
print(isinstance(student, str))
print(type(str(marks)))

Remember that "78" and 78 are different types, and "78" + 5 fails while "78" + "5" gives "785".

String Immutability

A string cannot be changed after it is made. This is called being immutable.

Trying to change one character

student = "Asha"

student[0] = "M"

TypeError: 'str' object does not support item assignment.

Every method that looks like it changes a string actually returns a new string:

The original never changes

student = "Asha"

upper = student.upper()

print("Original:", student)
print("New one:", upper)

So to change a string, you reassign the name:

Reassigning the name

student = "Asha"
student = student.upper()

print(student)

Immutability sounds like a limit and is mostly a protection: text handed to a function cannot be altered behind your back.

student.upper() on its own line does nothing useful. A string method returns a new string, so you have to store or use what comes back.

String Operations

String Indexing

Reaching a single character works exactly as it does in an array — counting from the front, or from the back:

Positions from the front and the back

student = "Asha"

print(student[0])
print(student[-1])
print(student[1], student[-3])

And going past the end raises, again exactly as it does in an array:

Past the end

student = "Asha"
print(student[4])

String Slicing

A slice takes a run of characters.

text[start:stop:step]

It starts at start and stops before stop, exactly like range().

Taking a run of characters

report = "Asha scored 78 in Maths"

print(report[0:4])
print(report[:4])
print(report[5:11])
print(report[-5:])
print(report[::2])
print(report[::-1])

Each part can be left out: no start means from the beginning, no stop means to the end, and a step of -1 reverses.

A slice never raises IndexError, even when the numbers are out of range:

A slice that is too big is simply trimmed

student = "Asha"

print(repr(student[0:100]))
print(repr(student[10:20]))

An out-of-range slice gives an empty string, while an out-of-range index raises. That difference catches people out.

String Concatenation (+)

+ joins two pieces of text into one:

Joining text

student = "Asha"
subject = "Maths"

print(student + " - " + subject)

+ only joins text to text:

Joining text to a number

student = "Asha"
marks = 78

print(student + " scored " + marks)

TypeError: can only concatenate str (not "int") to str. Convert with str(marks), or use an f-string, which does it for you.

Joining many strings with + in a loop is slow, because each + builds a whole new string. join() is the right tool, and it appears later in this lesson.

String Repetition (*)

* repeats a string a number of times:

Repeating text

print("-" * 30)
print("ab" * 3)
print("Report card".center(30, "="))

A separator line made with * is clearer than thirty typed dashes, and easy to change.

String Comparison

Strings compare character by character, using each character's number in the character set.

Comparing text

print("Asha" == "Asha")
print("Asha" == "asha")
print("Asha" < "Ravi")
print("apple" < "banana")

Capital letters come before small ones, which surprises everyone at first:

Why 'Z' is less than 'a'

print("Z" < "a")
print(ord("Z"), ord("a"))
print(sorted(["banana", "Apple", "cherry"]))

To compare fairly, lower both sides:

Comparing without the capitals getting in the way

first = "ASHA"
second = "asha"

print(first == second)
print(first.lower() == second.lower())
print(sorted(["banana", "Apple", "cherry"], key=str.lower))

Membership Operators (in, not in)

in answers whether one piece of text sits inside another:

Is it inside?

remark = "Good work this term"

print("work" in remark)
print("Work" in remark)
print("exam" not in remark)
print("work" in remark.lower())

in is the simplest way to search text, and the one to reach for when you only need a yes or no.

Special Characters

Some characters cannot be typed directly into a string: a quote that would end it, a tab, a line break.

Escape Sequences

A backslash followed by a letter is an escape sequence — two characters you type that mean one character in the string.

You typeYou get
\na new line
\ta tab
\\one backslash
\"a double quote
\'a single quote
\u00e9a character by its number, here é

Escape sequences in action

print("Line one\nLine two")
print("Name\tMarks")
print("Asha\t78")
print("A backslash: \\")
print("She said \"well done\"")
print("Caf\u00e9")

They are two characters in the source and one in the string:

Counting what is really there

text = "a\tb"

print(len(text))
print(repr(text))

repr() shows a string the way you would type it, which is the quickest way to see what is actually in it.

A path is where escapes bite:

A backslash that was not meant as an escape

print(len("C:\temp"))
print(repr("C:\temp"))
print(repr(r"C:\temp"))

\t in C:\temp became a tab. An r before the quote makes a raw string, where a backslash is just a backslash. Use raw strings for Windows paths and for regular expressions.

String Formatting

Putting values inside a sentence. Python has three ways, and they arrived in this order.

% Formatting

The oldest form, borrowed from C. You will meet it in old code.

Percent formatting

student = "Asha"
marks = 78
average = 78.456

print("%s scored %d" % (student, marks))
print("Average: %.1f" % average)
print("%s: %d (%.1f%%)" % (student, marks, average))

%s is text, %d a whole number, %.1f a decimal to one place, and %% a literal percent sign. Readable enough for one value, awkward for several, and it is easy to get the order wrong.

format()

The second form to arrive was a method on the string itself:

The format method

student = "Asha"
marks = 78

print("{} scored {}".format(student, marks))
print("{0} scored {1}, and {0} passed".format(student, marks))
print("{name} scored {score}".format(name=student, score=marks))
print("{:>10}|{:<10}|{:^10}|".format("right", "left", "centre"))
print("{:,.2f}".format(1234.5))

Numbers and names let you reuse a value or put things in any order. The part after : is a format spec, controlling width, alignment and decimal places.

f-Strings

The modern way, and the one to use. Put f before the quote and write the value inside curly brackets, where it belongs in the sentence.

f-strings

student = "Asha"
marks = 78
average = 78.456

print(f"{student} scored {marks}")
print(f"{student} needs {100 - marks} more")
print(f"Average: {average:.1f}")
print(f"{student:>10}|{marks:<5}|")
print(f"{1234.5:,.2f}")

You can do a small calculation inside the brackets, and the same format specs work.

= inside the brackets prints the expression and its value, which is excellent for checking what a program is doing:

The debugging form

marks = 78
average = 78.456

print(f"{marks=}")
print(f"{average=:.1f}")
print(f"{marks * 2=}")

Here are the three forms together:

FormLooks likeUse it
%"%s scored %d" % (a, b)only when reading old code
format()"{} scored {}".format(a, b)when the template is built separately
f-stringf"{a} scored {b}"everywhere else

Use f-strings. The values sit where they appear in the sentence, so there is no counting of placeholders and no chance of putting them in the wrong order.

So far you have used strings with operators such as + and *. Strings also come with many built-in methods that help you search, clean and transform text.

String Methods

Every string carries its methods with it. There are around forty; these are the ones worth knowing, grouped by what they do.

Remember throughout: every one of these returns a new string. None of them changes the original.

Case Conversion Methods

capitalize()

First character upper, everything else lower.

capitalize

print("asha SHARMA".capitalize())

casefold()

Like lower(), but more aggressive, for comparing text across languages.

casefold against lower

german = "STRASSE"
sharp_s = "ß"

print(german.lower())
print(sharp_s.lower())
print(sharp_s.casefold())

ß lowercases to itself but casefolds to ss, so casefolding makes the two German spellings match. Use casefold() for comparing, lower() for displaying.

lower() and upper()

These are the two you will use most, usually to make comparisons fair:

lower and upper

student = "Asha Sharma"

print(student.lower())
print(student.upper())
print(student.lower() == "ASHA SHARMA".lower())

swapcase()

swapcase

print("Asha Sharma".swapcase())

title()

Capitalises the first letter of every word.

title, and where it goes wrong

print("asha sharma".title())
print("asha's report".title())

Asha'S is wrong — title() treats the apostrophe as the end of a word. For names, capitalize() on each word is safer.

Search and Count Methods

find() and rfind()

find() gives the position of the first match, or -1 when there is none.

find and rfind

report = "maths 78, science 85, maths 60"

print(report.find("maths"))
print(report.rfind("maths"))
print(report.find("history"))

rfind() searches from the right. Neither one ever raises.

index() and rindex()

The same two searches, but they raise instead of returning -1.

index raises when it fails

report = "maths 78"

print(report.index("maths"))
print(report.index("history"))

Use find() when a miss is expected and normal; use index() when a miss means something is wrong.

count()

count() answers how many times one piece of text appears in another:

count

report = "maths 78, science 85, maths 60"

print(report.count("maths"))
print(report.count("m"))
print("Mississippi".count("ss"))

Counting does not overlap: "aaa".count("aa") is 1, not 2.

startswith() and endswith()

These answer whether a string begins or ends with a given piece:

startswith and endswith

filename = "report_card.py"

print(filename.startswith("report"))
print(filename.endswith(".py"))
print(filename.endswith((".py", ".txt")))

A tuple checks several endings at once, which is neater than three ors.

Validation Methods

These all answer True or False about what a string contains. They are how you check input before converting it.

The whole validation family at once

samples = ["Asha", "78", "78.5", "Asha78", "   ", "Asha Sharma", ""]

print(f"{'text':<12}{'alnum':<8}{'alpha':<8}{'digit':<8}{'space':<8}{'title':<8}")
for text in samples:
    print(f"{repr(text):<12}{text.isalnum()!s:<8}{text.isalpha()!s:<8}{text.isdigit()!s:<8}{text.isspace()!s:<8}{text.istitle()!s:<8}")

An empty string is False for all of them, which is usually what you want.

isalnum(), isalpha(), isascii()

Letters, digits and plain ASCII

print("Asha78".isalnum())
print("Asha 78".isalnum())
print("Asha".isalpha())
print("Asha".isascii())
print("café".isascii())

A space is neither a letter nor a digit, so isalnum() is False for anything with one in it.

isdecimal(), isdigit(), isnumeric()

Three methods that look identical and are not. They differ on unusual characters.

Where the three differ

for text in ["123", "²", "½", "12.3"]:
    print(f"{text!r:>8}  isdecimal={text.isdecimal()!s:<6} isdigit={text.isdigit()!s:<6} isnumeric={text.isnumeric()}")

² counts as a digit but not a decimal. ½ is numeric but neither of the others. And "12.3" fails all three, because the dot is not a digit.

Use isdecimal() when you are about to call int(). It is the strictest of the three and the one that matches what int() accepts.

Checking before converting

for text in ["78", "78.5", "seventy"]:
    if text.isdecimal():
        print(text, "->", int(text))
    else:
        print(text, "-> not a whole number")

islower(), isupper(), isspace(), istitle()

Case and whitespace checks

print("asha".islower())
print("ASHA".isupper())
print("   ".isspace())
print("Asha Sharma".istitle())
print("Asha sharma".istitle())

isidentifier() and isprintable()

Valid names, and characters you can see

print("student_name".isidentifier())
print("2nd_student".isidentifier())
print("class".isidentifier())
print("Asha".isprintable())
print("Asha\tSharma".isprintable())

isidentifier() checks the naming rules from lesson 2 — but note it says True for class, because keywords follow the same spelling rules. Use keyword.iskeyword() as well if that matters.

Modification Methods

replace()

replace() swaps every copy of one piece of text for another:

replace

report = "maths 78, maths 60"

print(report.replace("maths", "Mathematics"))
print(report.replace("maths", "Mathematics", 1))
print(report)

The third number limits how many are replaced. The original is untouched, as always.

strip(), lstrip(), rstrip()

Remove characters from the ends. With no argument, they remove whitespace — which is what user input is full of.

Cleaning up the ends

typed = "   Asha   "

print(repr(typed.strip()))
print(repr(typed.lstrip()))
print(repr(typed.rstrip()))

Given characters, they strip any of those characters from the ends:

Stripping particular characters

print("***Asha***".strip("*"))
print("www.school.com".strip("wcom."))

The second one is a trap: strip() removes any of those characters, in any order, not the whole word. That is removeprefix()'s job.

removeprefix() and removesuffix()

Removing exactly one piece

print("report_card.py".removesuffix(".py"))
print("Mr Ravi".removeprefix("Mr "))
print("Asha".removesuffix(".py"))
print("www.school.com".removeprefix("www."))

These remove the whole piece, once, and leave the string alone when it does not match. They are what most people mean when they wrongly reach for strip().

Split and Join Methods

split() and rsplit()

split() breaks a string into a list.

Breaking text apart

line = "Asha,78,Maths"

print(line.split(","))
print("Asha Sharma scored well".split())
print(line.split(",", 1))
print("a,b,c".rsplit(",", 1))

With no argument, split() breaks on any whitespace and ignores runs of it, which is what you want for a sentence. rsplit() counts its splits from the right.

splitlines()

splitlines() breaks text into lines:

Breaking into lines

report = """Report card
Student: Asha
Marks: 78"""

for line in report.splitlines():
    print(repr(line))

Better than split("\n"), because it handles the line endings used by other systems too.

join()

join() is split() backwards: it glues a list of strings together with a separator.

join

subjects = ["Maths", "Science", "English"]

print(", ".join(subjects))
print(" - ".join(subjects))
print("".join(subjects))

The separator is the string you call it on, which reads backwards at first and quickly becomes natural.

Every item must already be text:

join needs text

marks = [78, 85, 60]

print(", ".join(marks))

Converting first

marks = [78, 85, 60]

print(", ".join(str(mark) for mark in marks))

join() is also the right way to build a long string, because it works out the total size once instead of rebuilding on every +.

partition() and rpartition()

partition() splits at the first match and gives back three pieces: before, the separator, and after.

partition

line = "maths:78:A"

print(line.partition(":"))
print(line.rpartition(":"))
print("Asha".partition(":"))

You always get three pieces, even when the separator is missing, so nothing has to be checked before unpacking:

Three pieces, always

subject, found, rest = "maths:78".partition(":")

print(repr(subject), repr(found), repr(rest))

Alignment and Padding Methods

These make columns line up in printed output.

The four padding methods

print("Asha".center(20, "-"))
print("Asha".ljust(20, ".") + "|")
print("Asha".rjust(20, ".") + "|")
print("7".zfill(3))
print("-7".zfill(5))

zfill() pads with zeros and keeps a minus sign at the front, which is why it is not just rjust(n, "0").

A table built with padding

rows = [("Maths", 78), ("Science", 85), ("English", 60)]

print("Subject".ljust(12) + "Marks".rjust(6))
print("-" * 18)
for subject, marks in rows:
    print(subject.ljust(12) + str(marks).rjust(6))

expandtabs()

Turns tabs into spaces, using a tab stop you choose.

expandtabs

line = "Asha\t78"

print(repr(line))
print(repr(line.expandtabs(4)))
print(line.expandtabs(4))

An f-string with widths is usually clearer than relying on tabs.

Translation and Encoding Methods

encode()

Turns text into bytes, which is what files and networks actually carry.

encode

text = "café"

data = text.encode("utf-8")

print(data)
print(len(text), "characters ->", len(data), "bytes")
print(data.decode("utf-8"))

é takes two bytes in UTF-8, so four characters became five bytes. UTF-8 is the encoding to use unless something forces otherwise.

maketrans() and translate()

maketrans() builds a table of character swaps, and translate() applies it in one pass.

maketrans and translate

table = str.maketrans("aeiou", "12345")

print("Asha scored".translate(table))

remove_vowels = str.maketrans("", "", "aeiou")
print("Asha scored".translate(remove_vowels))

A third argument lists characters to delete. For a handful of single-character swaps this is faster and clearer than several replace() calls.

Built-in Functions with Strings

len()

len() counts the characters in a string:

len

student = "Asha Sharma"

print(len(student))
print(len(student.replace(" ", "")))
print(len(""))

len() counts characters, including spaces.

max() and min()

On a string, these compare character by character:

max and min on characters

print(max("Asha"))
print(min("Asha"))

On a list of strings they compare whole words:

max and min on words

subjects = ["Maths", "Science", "English"]

print(max(subjects))
print(min(subjects))
print(max(subjects, key=len))

key=len changes the question from "which comes last alphabetically" to "which is longest".

Before moving on, here are some of the mistakes beginners make most often when working with strings.

Common mistakes

Each row is one mistake. The middle column is what you will actually see on screen.

MistakeWhat you seeWhy it happensFix
Expecting a method to change the stringThe text never changesStrings are immutable; methods return a new onestudent = student.upper()
Assigning to one characterTypeError: 'str' object does not support item assignmentA string cannot be changed in placeBuild a new string, or use replace()
Joining text to a number with +TypeError: can only concatenate str+ needs both sides to be textstr(marks), or an f-string
An apostrophe inside single quotesSyntaxErrorThe second quote ends the stringUse double quotes around it
\t inside a Windows pathA tab appears in the middle of the path\t is an escape sequenceUse a raw string: r"C:\temp"
Comparing text without lowering it"Asha" == "asha" is FalseCapital and small letters are different charactersCompare .lower() on both sides
strip("www.") to remove a prefixMore characters vanish than expectedstrip() removes any of those charactersremoveprefix("www.")
index() when the text may be missingValueError: substring not foundindex() raises; find() returns -1Use find(), or check with in
join() on a list of numbersTypeError: sequence item 0: expected strEvery item must already be text", ".join(str(m) for m in marks)
isdigit() before int()ValueError on strings like "²"isdigit() accepts characters int() rejectsUse isdecimal()
Building a long string with + in a loopIt works, but slowlyEach + builds a whole new stringCollect the pieces and join() them
title() on a name with an apostropheAsha'S Reporttitle() treats ' as a word breakCapitalise each word yourself

What to Remember

  • A string is a run of characters in order, and every one has a position starting at 0.
  • Single and double quotes are the same; triple quotes span lines.
  • Strings are immutable. Every method returns a new string, so you must store what comes back.
  • A slice text[start:stop] stops before stop, and an out-of-range slice gives "" rather than an error.
  • + joins text to text only; * repeats it.
  • Comparison is character by character, and capital letters come before small ones.
  • A backslash starts an escape sequence; an r before the quote turns that off.
  • Use f-strings. % and format() are for reading older code.
  • find() returns -1 on a miss; index() raises.
  • strip() removes any of the given characters; removeprefix() removes one whole piece.
  • split() breaks text into a list and join() puts one back together — and join() needs every item to be text.
  • isdecimal() is the check to use before int(), not isdigit().
  • ljust(), rjust(), center() and zfill() line columns up; encode() turns text into bytes.

Next: Date and Time