HSHimanshu Sharma
Python Notes

Python strings: indexing, slicing, methods and formatting

Python strings: indexing, slicing, methods and formatting Advertisement

Text is everywhere in programs: names, messages, file contents, web addresses. Python's str type is rich, and learning its dozen most common operations gives you most of what you need. This post goes through them in the order you usually need them. Every output below was produced by running the code (Python 3.9; the same on newer versions).

What a string is

A string is an immutable sequence of characters. Each character is a Unicode code point, so "é" is one character, and strings can hold English, Hindi, emoji and anything else.

Create one with single, double or triple quotes. They are interchangeable, which lets you include the other kind of quote without escaping:

a = 'it\'s'          # escaped
b = "it's"           # no escape needed
c = """line1
line2"""             # triple quotes: multi-line
print(a, b, c.splitlines())     # it's it's ['line1', 'line2']

Triple-quoted strings keep their line breaks and are the usual choice for documentation text and long messages. A raw string r"C:\new" keeps backslashes as they are.

Indexing and slicing

Positions start at 0, and negative numbers count from the end.

s = "Hello, Python World"
print(s[0], s[-1], s[7:13], s[:5], s[-5:], s[::-1])
# H d Python Hello World dlroW nohtyP ,olleH

A slice is s[start:stop:step], with stop excluded. Slices are forgiving and never raise an error for out-of-range ends, but a single index does:

print("abc"[1:100], repr("abc"[5:]))      # bc ''
try:
    "abc"[5]
except IndexError as e:
    print(e)                               # string index out of range

Because strings are immutable, you can't assign to a position (s[0] = "J" is a TypeError). You create a new string instead: "J" + s[1:].

Operators and built-ins

print("abc" * 2, "a" in "abc", "abc" < "abd", len("hello"))     # abcabc True True 5
print(ord("a"), chr(97), "a" < "B", "Z" < "a")                  # 97 a False True

+ joins strings, * repeats them, in tests for a substring, and the comparison operators compare by character code, so all capitals sort before all lower-case letters ("Z" < "a" is true). For case-insensitive comparison use casefold():

print("apple" == "Apple", "apple".casefold() == "APPLE".casefold())    # False True

Do not build long strings with += in a loop; collect pieces in a list and use "".join(pieces).

Removing spaces

s = "  Hello, Python World  "
print(repr(s.strip()), repr(s.lstrip()), repr(s.rstrip()))
# 'Hello, Python World' 'Hello, Python World  ' '  Hello, Python World'

strip() removes whitespace from both ends, lstrip() from the left and rstrip() from the right. Pass characters to remove others: "--x--".strip("-"). They never touch spaces in the middle.

Searching

s = "Hello, Python World"
print(s.find("o"), s.rfind("o"), s.index("P"), s.find("zz"), s.count("o"), s.count("l"))
# 4 15 7 -1 3 3

find() returns the first position, rfind() the last, and -1 if the text is absent. index() does the same but raises ValueError: substring not found when it can't find the text. Use find when absence is normal, index when it is a bug. count() counts non-overlapping occurrences.

print(s.startswith("Hello"), s.endswith("d"))      # True True

Replacing, splitting and joining

s = "Hello, Python World"
print(s.replace("o", "0"), s.replace("o", "0", 1))
# Hell0, Pyth0n W0rld Hell0, Python World
print(s.split(), s.split(","), s.rsplit("o", 1))
# ['Hello,', 'Python', 'World'] ['Hello', ' Python World'] ['Hello, Python W', 'rld']

replace(old, new, count) returns a new string (the original is unchanged). split() with no argument splits on any run of whitespace and ignores empty pieces; with an argument it splits at every occurrence, which produces empty strings for repeats:

print("a,b,,c".split(","), "a b  c".split(" "), "a b  c".split())
# ['a', 'b', '', 'c'] ['a', 'b', '', 'c'] ['a', 'b', 'c']

join() is the reverse and is called on the separator:

print("-".join("abc"), ",".join(["1", "2"]), repr("x".join([])))     # a-b-c 1,2 ''

All items must be strings; ",".join([1, 2]) raises a TypeError, so convert with map(str, items) first. splitlines() splits on line breaks.

Case

s = "Hello, Python World"
print(s.upper(), s.lower(), s.title(), s.swapcase(), "hello world".capitalize(), sep="\n")
# HELLO, PYTHON WORLD
# hello, python world
# Hello, Python World
# hELLO, pYTHON wORLD
# Hello world

title() capitalises every word and capitalize() only the first letter of the whole string. The Case Converter does the same conversions without code, plus camelCase and snake_case.

Checking what characters are present

print("abc".isalpha(), "123".isdigit(), "ab1".isalnum(), " ".isspace(), "Ab".istitle(), "ab".islower(), "AB".isupper())
# True True True True True True True

All of these return False for an empty string. They are handy for validating input: if not name.isalpha(): .... Note that "3.5".isdigit() is False because of the dot.

print("".join(c for c in "a1b2c3" if c.isdigit()))      # 123

Formatting strings

Python has three styles. The modern one is the f-string, with an expression inside braces and an optional format after a colon.

name, age = "Asha", 30
print(f"{name} {age:>5} {age:<5}| {3.14159:8.2f}| {1234567.891:,.1f}| {0.256:.1%}| {name:*^11}")
# Asha    30 30   |     3.14| 1,234,567.9| 25.6%| ***Asha****

Reading the formats: >5 right-aligns in width 5, <5 left-aligns, 8.2f is a float with two decimals in width 8, , adds thousands separators, .1% shows a percentage, and *^11 centres in width 11 filled with *. More:

print("{:05d} {:+d} {:e}".format(42, 5, 12345.678))      # 00042 +5 1.234568e+04
print(f"{255:b} {255:o} {255:x} {255:#x}")                # 11111111 377 ff 0xff
print("Name: %s Age: %d" % (name, age))                  # Name: Asha Age: 30
print("{1} {0}".format("a", "b"))                         # b a

str.format() and the older % operator still work and appear in older code. Use f-strings in new code. For alignment helpers: "5".zfill(3) gives 005, and "ab".ljust(4, ".") gives ab...

repr and str

str(x) is for people, repr(x) is for programmers: it shows quotes and escapes, which makes hidden characters visible.

print("x=%r" % "a", repr("a\nb"), str("a\nb"))

When debugging whitespace problems, print repr(s), as in the strip() example above.

Unicode and bytes

print(len("é"), "caf\u00e9".encode("utf-8"), b"caf\xc3\xa9".decode("utf-8"), len("café".encode()))
# 1 b'caf\xc3\xa9' café 5

"é" is one character but takes two bytes in UTF-8, so len of the text is 4 but the encoded form is 5 bytes. Always decode bytes you read from files or networks with the right encoding (UTF-8 in almost all cases).

Short programs

w = "madam"
print(w == w[::-1])                                     # True   (palindrome)
print(" ".join("hello world".split()[::-1]))            # world hello   (reverse the words)
from collections import Counter
print(Counter("banana"))                                # Counter({'a': 3, 'n': 2, 'b': 1})
print("".join(sorted(set("banana"))))                   # abn   (unique letters, sorted)

Working with real text

Real text is messier than examples. A few habits save time. First, clean before you compare: strip spaces, normalise case with casefold(), and collapse repeated spaces with " ".join(text.split()). Second, look at invisible characters with repr(); a trailing newline or a non-breaking space explains many "why are these two equal-looking strings different?" bugs, and the Invisible Character Cleaner finds the sneakiest ones. Third, don't build text by gluing numbers into it with +; an f-string does the conversion and the layout in one step. Fourth, when you read a file, say which encoding it uses (open(path, encoding="utf-8")) instead of trusting a default that differs between computers.

Strings are also the usual input format: values from input(), files, web forms and APIs all arrive as text. Converting them early (int(...), float(...)) and validating them with isdigit() or a try/except ValueError block keeps the rest of the program free of surprises. For counting and searching in larger texts, the Word Counter is a quick way to check your results.

Common mistakes

  • Trying to modify a string in place. Build a new one.
  • Forgetting that methods return a new string: s.upper() alone does nothing; write s = s.upper().
  • Splitting with " " and getting empty items; use split() with no argument.
  • Using find() and then treating -1 as a valid index.
  • Comparing text with different case or accents without normalising.
  • Joining non-strings.

Continue

Next, learn how text patterns are searched with regular expressions, see the types around strings in the fundamentals post and use the Word Counter and Invisible Character Cleaner to inspect real text. The Python cheat sheet condenses the common methods.

Advertisement