Python Strings: A Complete Guide (Updated 2022)

In Python, a string is a data type that is used to represent text.

A string is one of the basic data types you are going to deal with in every Python project.

In Python, a string is created by wrapping characters into single or double quotation marks:

str1 = "Hello world"
str2 = 'This is a test'

This is a complete guide on Python strings.

In this guide, you learn how to create strings and work with them. This includes learning:

  • How to create strings.
  • How string indexing works.
  • How to work with substrings.
  • How to use string methods to make string manipulation easier.

And much more.

How to Create a String in Python

In Python, you can create a string by using double quotation marks or single quotation marks.

For instance:

name = "Alice"
hobby = 'golf'

When it comes to using single quotes vs using double quotes, you need to be consistent. In other words, pick one and use it throughout your project.

Feel free to read Python Single Quotes vs Double Quotes to learn more.

In Python, you are going to use strings a lot.

Because of this, you are going to be good at string manipulation and working with strings in general.

You can do many things with strings, such as joining, slicing, modifying, finding substrings, and much more.

Next, let’s talk about string operators in Python.

String Operators

In the previous chapter, you learned about the basic math operators in Python.

Neatly enough, you can use the addition and multiplication operators on strings as well.

  • With the addition operator, you can add two or more strings together.
  • With the multiplication operator, you can multiply strings.

In addition to these two operators, there is a third useful operator called the in operator. You can use it to check if a string contains a substring, such as a character or another string.

Let’s take a closer look at how these operators work.

The + Operator

In Python, you can combine two or more strings using the addition operator (+).

For instance:

part1 = "some"
part2 = "where"

word = part1 + part2

print(word)

Output:

somewhere

You can also use the shorthand assignment addition on Python strings to add more strings to an existing one.

For instance:

word = "some"
word += "where"

print(word)

Output:

somewhere

Learn more about the + operator with Python strings.

The * Operator

To multiply strings in Python, use the multiplication operator (*).

For instance:

word = "hello"
threeWords = word * 3

print(threeWords)

Output:

hellohellohello

The ‘in’ Operator

In Python, you can use the in operator to check if a string consists of a substring.

In other words, you can use it to find if there is a specific word or character inside a string.

The in operator returns True if there is a matching string inside the string. Otherwise, it returns False.

For instance:

sentence = "This is just a test"
testFound = "test" in sentence

print(testFound)

Output:

True

Let’s see another example.

This time, let’s check if there is a particular character in a word:

word = "Hello"
eFound = "e" in word

print(eFound)

Output:

True

Next, let’s talk about the indexing of a string.

String Indexing

In Python, a string is a collection of characters.

Each character of a string goes along with an associated index.

This index is useful because you can access a particular character using the index.

Python string indexing is zero-based.

This means the 1st character of a string has an index of 0, the 2nd character has 1, and so on.

To access a character with a specific index, use the square brackets as follows:

string[i]

This returns the i-1th character of a string.

For example:

word = "Hello"
c1 = word[0]

print(c1)

Output:

H

If the index overshoots the length of the string, you are going to see an index-out-of-bounds error.

Let’s see an example.

As a beginner, it can be hard to remember that the indexing starts at 0. For instance, the last character of a string with 5 characters has an index of 4. But let’s see what happens if you accidentally think it is 5:

word = "Hello"
last = word[5]

print(last)

Output:

IndexError: string index out of range

As you can see, the result is an error that states the index is out of range.

This is because with index 5 you are trying to access the 6th element. But because here the string only has 5 characters, accessing the 6th one fails miserably.

Thus, you need to be careful not to access an index that is not present in the string.

String Slicing

In the previous section, you learned how to use an index to access a character in a string.

Sometimes you want to access a range of characters in a string.

You can do this with slicing.

The most basic variation of slicing requires you to specify a start index and an end index:

string[start:end]

The result is a slice of characters (a string) where the character corresponding to the end index is excluded!

For instance, let’s grab the first three letters of a string:

word = "Hello"
firstThree = word[0:3]

print(firstThree)

Output:

Hel
In a slice 0:3 the 1st, 2nd, and the 3rd element are included. But the 4th (index of 3) is excluded.

The full syntax of slicing includes the step parameter as follows:

string[start:end:step]

The step parameter specifies how many characters to jump over. By default, it is 1 as you may have guessed.

For example, let’s every second character in the first 8 characters of a string:

word = "Hello world"
firstThree = word[0:8:2]

print(firstThree)

Output:

Hlow

Now you understand the basics of slicing strings in Python.

However, there is much more when it comes to slicing, such as reverse slicing or omitting the slicing parameters.

To keep it in scope, we are not going to go further down into the details here.

If you are interested to learn more about slicing, feel free to read this complete guide on slicing in Python. If you do so, make sure you understand how to work with lists in Python.

Embed Variables Into Strings

It is quite common for you to want to add a variable into a string in Python.

In this case, you can use the F-strings.

The F-string stands for formatted string.

It allows you to neatly place code into a string.

To create an F-string, add the character f in front of a string and specify the embedded code as an empty set of curly brackets into the string.

For instance:

name = "Alice"
greeting = f"Hello, {name}! How are you doing?"

print(greeting)

Output:

Hello, Alice! How are you doing?

As you can see, this neatly embeds the name into the string. This way, you do not have to split the string into three parts which would be inconvenient.

Let’s see another example to demonstrate that you can run code inside an F-string as well:

calculation = f"5 * 6 = {5 * 6}."
print(calculation)

Output:

5 * 6 = 30.

To learn more about string formatting, feel free to read F-strings in Python.

Modifying Strings

In Python, you cannot modify a string.

This is because a string is an immutable collection of characters.

This can be confusing for beginners.

However, it is not that common for you to need to modify a string in the first place. Thus, this is not a problem.

If you need to modify a string, you have to take a copy of the existing string and make the desired copy there. This creates a new string which is a modified version of the original one.

As a matter of fact, there is a whole bunch of useful built-in string methods you can use to “modify” strings. These methods have got your back in most common situations.

The rest of this tutorial focuses on these methods.

Python String Methods

The built-in string comes with a bunch of useful methods you can use to work with strings.

This makes your life as a programmer easier.

These methods are designed to solve common tasks related to string manipulation and working with strings in general.

For instance, you can replace a character in a string using the replace() method. Doing this would be clumsy without the replace() method. This is because you would need to implement your own logic for replacing a character because modifying a string directly is not possible.

There are quite a few string methods in Python, 47 to be precise.

Do not even try to remember all of them.

Instead, I suggest you read through each method and spend a minute or two playing with the examples.

You can bookmark this guide and come back to it later on.

This table is a cheat sheet of Python string methods.

Below this table, you find a more detailed description of each method. In addition, you find a useful example or two on each method.

Method Name Method Description
capitalize Converts the first character of a string to upper case
casefold Converts a string to a lowercase string. Supports more conversions than the lower method. Use when localizing or globalizing strings.
center Returns a centered string
count Returns how many times a character or substring occurs in a string
encode Returns an encoded version of a string
endswith Checks if a string ends with the specific character or substring
expandtabs Specifies a tab size for a string and returns it
find Searches for a specific character or substring. Returns the position of where it was first encountered.
format The old-school way to add variables inside of a string. Formats a string by embedding values into it and returning the result
format_map Formats specific values in a string
index Searches for a character or substring in a string. Returns the index at which it was first encountered.
isalnum Checks if all the characters of the string are alphanumeric
isalpha Checks if all the characters of the string are found in the alphabetical
isascii Checks if all the characters of the string are ASCII values
isdecimal Checks if all the characters of the string are decimal numbers
isdigit Checks if all the characters of the string are numeric digits
isidentifier Checks if a string is an identifier
islower Checks if all characters of a string are lower case
isnumeric Checks if all characters of a string are numeric
isprintable Checks if all characters of a string are printable
isspace Checks if all characters of a string are white spaces
istitle Checks if a string follows title capitalization rules (every word begins with a capital letter)
isupper Checks if all characters of a string are upper case
join Joins items of an iterable (such as a list) to the end of a string
ljust Returns a left-justified version of a string
lower Convert a string to lowercase
lstrip Returns a left trim version of a string
maketrans Returns a translation table of a string for translations
partition Breaks a string to parts of three. The desired center part is specified as an argument
removeprefix Removes a prefix from the beginning of a string and returns the string without it.
removesuffix Removes a suffix from the end of a string and returns the string without it.
replace Returns a string where a specific character or substring is replaced with something else
rfind Searches a string for a specific character or substring. Returns the last index at which it was found
rindex Searches a string for a specific character or substring. Returns the last index at which it was found.
rjust Returns a right-justified version of a string
rpartition Breaks a string to parts of three. The desired center part is specified as an argument
rsplit Splits the string at a specific separator, and returns the parts as a list
rstrip Creates and returns a right trim version of a string
split Splits the string at the specified separator, and returns the parts as a list
splitlines Splits a string at line breaks and returns the lines as a list
startswith Checks if a string starts with the specified character or a substring
strip Returns a trimmed version of a string
swapcase Swaps cases. All uppercase characters become lowercases and vice versa
title Converts each word of a string to start with an uppercase letter
translate Returns a translated string
upper Converts a string to upper case
zfill Prefills a string with 0 values

All Python String Methods with Examples

This section shows a more detailed description of each string method there is.

In addition to theory, you are going to see a bunch of useful examples of each method.

Let’s get started with the list!

capitalize

To capitalize the first letter, use the built-in capitalize() method.

"hello, world".capitalize() # Hello, world

This method creates a new string by running through each letter of a string and capitalizing it.

casefold

To convert a Python string to lowercase, use the built-in casefold() method.

For example:

"HELLO, WORLD".casefold() # hello world

This method creates a new string by running through the string and switching each character to lowercase.

The difference between casefold() and lower() is that casefold() knows how to convert other languages’ special letters. In other words, using casefold() is meaningful when your app needs to support globalization or localization.

For example, let’s case fold a German sentence to get rid of the big doubles (ß) sign. This is not possible using the lower() method:

"Eine Großes bier".lower()    # eine großes bier"
"Eine Großes bier".casefold() # eine grosses bier"

center

Use the center() method to surround a string with characters.

The syntax for using this method is:

string.center(num_characters, separator)

When you call this method, you need to specify the number of total characters, and the character that will surround the string.

For example, let’s add dashes around a word and make it 20 characters long.

txt = "banana"
x = txt.center(20, "-")
print(x)

Output:

-------banana-------

count

The count() method counts how many times a character or a substring occurs in a string.

For example, let’s count the number of letters l’s in a sentence:

"Hello, world".count("l") # Returns 3

Or let’s count how many times the word “Hello” occurs in the sentence:

"Hello, world".count("Hello") # Returns 1

encode

To encode a string, use the built-in encode() method.

The syntax of using this method is:

string.encode(encoding=encoding, errors=errors)

By default, this method encodes the string to UTF-8. If it fails, it throws an error.

For example, let’s convert a string to UTF-8 encoding:

title = 'Hello world'
print(title.encode())

Output:

b'Hello world'

You can call this method with two optional parameters:

  • encoding. This specifies which encoding type is used.
  • errors. This specifies the response when encoding fails.

There are 6 types of errors:

  • strict
  • ignore
  • replace
  • xmlcharrefreplace
  • backslashreplace
  • namereplace

Here are some ASCII encoding examples with different error character types:

txt = "My name is Kimi Räikkönen"

print(txt.encode(encoding="ascii", errors="ignore"))
print(txt.encode(encoding="ascii", errors="replace"))
print(txt.encode(encoding="ascii", errors="xmlcharrefreplace"))
print(txt.encode(encoding="ascii", errors="backslashreplace"))
print(txt.encode(encoding="ascii", errors="namereplace"))

Output:

b'My name is Kimi Rikknen'
b'My name is Kimi R?ikk?nen'
b'My name is Kimi Räikkönen'
b'My name is Kimi R\\xe4ikk\\xf6nen'
b'My name is Kimi R\\N{LATIN SMALL LETTER A WITH DIAERESIS}ikk\\N{LATIN SMALL LETTER O WITH DIAERESIS}nen'

endswith

To check if a string or a proportion of it ends with a character or a substring, use the endswith() method.

For example:

sentence = "Hello, welcome to my world."
print(sentence.endswith("."))

Output:

True

expandtabs

You can specify the tab size with the expandtabs() method. This method is useful if your strings contain tabs.

For example

sentence = "H\te\tl\tl\to"

print(sentence.expandtabs(1))
print(sentence.expandtabs(2))
print(sentence.expandtabs(3))
print(sentence.expandtabs(4))
print(sentence.expandtabs(5))

Output:

H e l l o
H e l l o
H  e  l  l  o
H   e   l   l   o
H    e    l    l    o

find

The find() method finds the index of the first occurrence of a character or substring in a string. If there is no match, it returns -1.

You can also specify a range from where the search is done. The complete syntax for using this method is:

string.find(value, start, end)

For example:

sentence = "Hello world"
x = sentence.find("world")

print(x)

Output:

6

This says that the string "world" can be found at the index of 6 from the sentence.

As another example, let’s search for a substring in a given range:

sentence = "This is a test"
x = sentence.find("is", 3, 8)

print(x)

Output:

5

format

The format() method formats a string by embedding values into it. This is an old-school approach to string formatting in Python.

The format() method is used with the following syntax:

string.format(value1, value2, ... , valuen)

Where the values are variables that are embedded into the string. In addition to this, you need to specify a placeholder for these variables in the string.

Let’s see an example:

name = "John"
age = 21

sentence = "My name is {} and I am {} years old".format(name, age)

print(sentence)

Output:

My name is John and I am 21 years old

format_map

To directly format a dictionary of values into a string, use the format_map() method.

The syntax:

string.format_map(dict)

To make this work, you need to specify placeholders in the string where the values of the dictionary are embedded.

For example:

data = {"name": "John", "age": 21}

sentence = "My name is {name} and I am {age} years old".format_map(data)

print(sentence)

Output:

My name is John and I am 21 years old

index

The index() method finds the index of the first occurrence of a character or substring in a string. It raises an exception if it does not find the value.

You can also specify a range from where the search is done.

The syntax for using this method is:

string.index(value, start, end)

For example:

sentence = "Hello world"
x = sentence.index("world")

print(x)

Output:

6

This says that the string "world" can be found at the index of 6 from the sentence.

As another example, let’s search for a substring in a given range:

sentence = "This is a test"
x = sentence.index("is", 3, 8)

print(x)

Output:

5

The difference between index() and find() methods is that index() raises an error if it does not find a value. The find() method returns -1.

isalnum

To check if all the characters of a string are alphanumeric, use the isalnum() method.

For example:

sentence = "This is 50"
print(sentence.isalnum())

Output:

False

isalpha

To check if all the characters of a string are letters, use the isalpha() method.

For example:

sentence = "Hello"
print(sentence.isalpha())

Output:

True

isascii

To check if all the characters of a string are ASCII characters, use the isascii() method.

For example:

sentence = "Hello"
print(sentence.isalpha())

Output:

True

isdecimal

To check if all the characters of a string are decimal characters, use the isdecimal() method.

For example:

sentence = "\u0034"
print(sentence.isdecimal())

Output:

True

isdigit

To check if all the characters of a string are numbers, use the isdigit() method.

For example:

sentence = "34"
print(sentence.isdigit())

Output:

True

isidentifier

To check if all the characters of a string are identifiers, use the isidentifier() method.

  • A string is considered an identifier if it contains alphanumeric letters (a-z, 0-9) or underscores.
  • An identifier cannot start with a number.

For example:

sentence = "Hello"
print(sentence.isidentifier())

Output:

True

islower

To test if a string is in lowercase, use the built-in islower() method:

For example:

"Hello, world".islower() # False

This method checks if all the characters of a string are in lowercase. If they are, it returns True. Otherwise, False.

isnumeric

To check if all the characters of a string are numeric, use the isnumeric() method.

For example:

sentence = "5000"
print(sentence.isnumeric())

Output:

True

isprintable

To check if all the characters of a string are printable, use the isprintable() method.

For example:

sentence = "This a is test"
print(sentence.isprintable())

Output:

True

isspace

To check if all the characters of a string are white spaces, use the isspace() method.

For example:

sentence = "             "
print(sentence.isspace())

Output:

True

istitle

To check if all the first characters of each word in a string start with a capital letter, use the istitle() method.

For example:

sentence = "Hello There This Is A Test"
print(sentence.istitle())

Output:

True

isupper

To test if a string is in uppercase, use the built-in isupper() method:

For example:

"HELLO, WORLD".isupper() # True

This method checks if all the characters of a string are in uppercase. If they are, it returns True. Otherwise, False.

join

To join a group of items to one string, use the join() method.

For example:

words = ["This", "is", "a", "test"]
sentence = " ".join(words)

print(sentence)

Output:

This is a test

ljust

Add specific characters, such as empty spaces, to the right of a string with the ljust() method.

The syntax:

string.ljust(num_characters, separator)
  • num_characters is the total length of the strings plus the added characters
  • separator is the character that is added n times to the string.

For example, let’s add dashes to the right of a string:

print("Hello".ljust(20, "-"))

Output:

Hello---------------

lower

To convert a Python string to lowercase, use the built-in lower() method.

For example:

"HELLO, WORLD".lower() # hello world

This method creates a new string by running through the string and switching each character to lowercase.

lstrip

To remove leading characters from a string, use lstrip() method.

By default, this method removes all the white spaces from the beginning of a string.

For example:

print("    test".lstrip())

Output:

test

As another example, let’s remove all the dashes from the beginning of this string:

print("---test---".lstrip("-"))

Output:

test---

maketrans

To create a string mapping table, use the maketrans() method. This mapping table is used with the translate() method.

The syntax:

string.maketrans(a, b, c)

Where:

  • The first parameter a is required. If only this parameter is specified, it has to be a dictionary that specifies how strings are replaced. Otherwise, a needs to be a string that specifies which characters to replace.
  • The second parameter b is optional. It must be the same length with a. Each character in a will be replaced by a the corresponding character in b
  • The third parameter c is also optional. It describes which characters to remove from the original string.

For instance, let’s replace “o” characters with “x” in a sentence:

sentence = "Hello World"
trans_table = sentence.maketrans("o", "x")

print(sentence.translate(trans_table))

Output:

Hellx Wxrld

partition

To split a string into three parts, use the partition() method. It works by splitting a string around a specific character or substring.

For example, you can partition a sentence around a given word:

sentence = "This is a test"
parts = sentence.partition("a")

print(parts)

Output:

('This is', 'a', 'test')

removeprefix

To remove a string from the beginning, use the removeprefix() method. Notice that this is quite a new method and only works for Python 3.9+.

For example, let’s remove the word “This” from a sentence:

sentence = "This is a test"

print(sentence.removeprefix("This"))

Output:

is a test

removesuffix

To remove a string from the end, use the removesuffix() method. This is quite a new method and only works for Python 3.9+.

For example, let’s remove the word “test” from the end of a sentence:

sentence = "This is a test"

print(sentence.removesuffix("test"))

Output:

This is a

replace

To replace a string with another inside a string, use the replace() method.

The syntax:

string.replace(old, new, count)

Where:

  • old is the string you want to replace.
  • new is the new string you want to replace occurrences of old.
  • count is an optional parameter. It specifies how many occurrences you want to replace.

For example:

sentence = "This is a test"
updated_sentence = sentence.replace("This", "It")

print(updated_sentence)

Output:

It is a test

rfind

Find the last occurrence of a substring or character inside a string using the rfind() method. If no occurrences are found, this method returns -1.

The syntax:

string.rfind(val, start, end)

Where:

  • val is the string you are looking for
  • start is an optional parameter. It defines the start index for a search
  • end is also an optional parameter. It defines the end index for the search.

For example:

sentence = "This car is my car"
last_index = sentence.rfind("car")

print(last_index)

Output:

15

rindex

Find the last occurrence of a substring or character inside a string using the rindex() method. If no occurrences are found, this method returns and raises an exception.

The syntax:

string.rindex(val, start, end)

Where:

  • val is the string you are looking for
  • start is an optional parameter. It defines the start index for a search
  • end is also an optional parameter. It defines the end index for the search.

For example:

sentence = "This car is my car"
last_index = sentence.rindex("car")

print(last_index)

Output:

15

rjust

Add specific characters, such as empty spaces, to the left of a string with the rjust() method.

The syntax:

string.rjust(num_characters, separator)
  • num_characters is the total length of the strings plus the added characters
  • separator is the character that is added n times to the string.

For example, let’s add dashes to the beginning of a string:

print("Hello".rjust(20, "-"))

Output:

---------------Hello

rpartition

Split a string into three parts based on the last occurrence of a specific string. These parts are returned as a tuple.

For example:

sentence = "This car is my car, you cannot drive it"

parts = sentence.rpartition("car")
print(parts)

Output:

('This car is my ', 'car', ', you cannot drive it')

rsplit

To split a string into a list from the right, use rsplit() method.

The syntax:

string.rsplit(separator, max)

Where:

  • separator is the separator based on which the string is split
  • max is the maximum number of times the split is done. It is an optional parameter. If you don’t specify max, this method works like split().

For example, let’s split a comma-separated string from the right. But let’s only split at the first comma it encounters:

items = "apple, banana, cherry"
split_items = items.rsplit(",", 1)

print(split_items)

Output:

['apple, banana', ' cherry']

Notice how there are only two strings in this array.

rstrip

Remove trailing characters at the end of a string using the rstrip() method.

If no argument is specified, this method tries to remove empty strings at the end of a string.

For example, let’s remove dashes from a string:

print("Hello-----".rstrip("-"))

Output:

Hello

split

Split a string into a list using the split() method. This is one of the most commonly used Python string methods.

By default, this method splits the string based on empty spaces.

The syntax:

string.split(separator, max)

Where:

  • separator is an optional parameter. It is the character or substring based on which the string is split into parts.
  • max is also an optional parameter. It defines how many splits the method is going to do.

For example:

sentence = "This is a test"

# split by white spaces 
parts = sentence.split()

print(parts)

Output:

['This', 'is', 'a', 'test']

As another example, let’s split a comma-separated list two times for the first two commas encountered:

sentence = "Apple, Banana, Orange, Mango, Pineapple"

parts = sentence.split(",", 2)

print(parts)

Output:

['Apple', ' Banana', ' Orange, Mango, Pineapple']

Notice how there are only 3 elements in the split list.

splitlines

To split a string at line breaks, use the splitlines() method.

sentence = "Apple\nBanana\nOrange\nMango\nPineapple"

parts = sentence.splitlines()

print(parts)

Output:

['Apple', 'Banana', 'Orange', 'Mango', 'Pineapple']

You can also keep the line breaks at the end of each string. To do this, pass True into the call:

sentence = "Apple\nBanana\nOrange\nMango\nPineapple"

parts = sentence.splitlines(True)

print(parts)

Output:

['Apple\n', 'Banana\n', 'Orange\n', 'Mango\n', 'Pineapple']

startswith

To check if a string starts with a specific value, use the startswith() method.

The syntax:

string.startswith(val, start, end)

Where:

  • val is the value you want to check if the string starts with.
  • start is an optional parameter. It is an integer that specifies where to start the search.
  • end is also an optional parameter. It is an integer that specifies where to end the search.

For instance:

sentence = "Hello world"

print(sentence.startswith("Hello"))

Output:

True

Let’s take a look at another example. This time let’s see if the 5th character starts with “wor”:

sentence = "Hello world"

print(sentence.startswith("wor", 6))

Output:

True

strip

To remove specific characters or substrings from a string, use the strip() method. By default, this method removes blank spaces. You can also specify an argument that determines what to remove.

For instance, let’s remove all the dashes from a string:

word = "--------test---"

stripped_word = word.strip("-")

print(stripped_word)

Output:

test

swapcase

To make lowercase letters upper and uppercase letters lower, use the swapcase() method.

For example:

"HELLO, world".swapcase() # hello, WORLD

This method creates a string by looping through the string and swapping all uppercase letters to lowercase and vice versa.

title

To capitalize the first letter of each word in a string, use the title case converter by calling title() method.

For example:

"hello, world".title() # Hello, World

This method creates a new string by looping through a string and capitalizing each word’s first letter.

translate

To replace specific characters of a string with another, use the translate() method.

Before you do that, create a mapping table with the maketrans() method.

For example, let’s replace the “o” letters with “x”:

sentence = "Hello World"
trans_table = sentence.maketrans("o", "x")

print(sentence.translate(trans_table))

upper

To convert a Python string to uppercase, use the built-in upper() method.

For example:

"Hello, world".upper() # HELLO WORLD

This method creates a new string by looping through a string and changing each character to upper case.

zfill

Add leading zeros to a string with zfill() method.

Give this method the desired length of the string. It fills the string with zeros until the desired length is achieved.

For example:

word = "Test"

filled_word = word.zfill(10)

print(filled_word)

Output:

000000Test

Notice how the total length of this string is now 10.

Conclusion

That is a lot of string methods!

Thanks for reading. I hope you find it useful.

Happy coding!

Further Reading