Chapter 4 of 24 · Python for Beginners — Complete Free Tutorial
Strings and f-Strings
Text is the raw material of almost every program you will write, and the Python for Beginners series reaches it in chapter 4. A string in Python is a sequence of Unicode characters, immutable and surprisingly well equipped: this chapter covers the literal forms, indexing and slicing, the methods worth memorising, why strings refuse to change, and f-strings — the formatting syntax that makes readable output nearly free.
String literals: quotes, triple quotes and raw strings
Single and double quotes are interchangeable, so choose whichever lets you avoid escaping. Triple quotes capture many lines exactly as typed, and a raw string (r"…") switches escaping off entirely — indispensable for Windows paths and regular expressions:
triple = """Line one
Line two"""
print(triple)
print(r"C:\new\temp")
print(len(r"\d"))
Expected output:
Line one
Line two
C:\new\temp
2
Notice the last two lines: without r, "\n" would be a newline and "\d" a plain d of length 1. Raw strings turn the backslash back into an ordinary character; they cannot make an unterminated quote harmless, so a raw string still cannot end in a single backslash.
Indexing and slicing
Strings are ordered sequences, so positions are numbers. Indexing starts at 0 from the left, and negative indexes count from the right — -1 is the last character. Slicing with [start:stop:step] takes a substring, where stop is always excluded:
text = "Python"
print(text[0])
print(text[-1])
print(text[0:3])
print(text[:2])
print(text[2:])
print(len(text))
Expected output:
P
n
Pyt
Py
thon
6
An index outside the string is an error, not a clamp — text[99] raises IndexError: string index out of range, which is Python refusing to guess what you meant. Slicing is forgiving by comparison: out-of-range bounds simply produce a shorter (or empty) string.
Methods: upper, split, join, strip, replace
A method is a function attached to a value, called with a dot. The string family you will type most often:
line = " python,for,beginners "
cleaned = line.strip()
print(cleaned)
print(cleaned.upper())
print(cleaned.split(","))
print(" | ".join(["python", "for", "beginners"]))
print(cleaned.replace(",", " + "))
Expected output:
python,for,beginners
PYTHON,FOR,BEGINNERS
['python', 'for', 'beginners']
python | for | beginners
python + for + beginners
Two of these deserve emphasis. split() turns a string into a list of substrings (chapter 8), and join() runs the operation backwards: it is called on the separator and given the list. And strip() removes whitespace from both ends — the single most useful method for cleaning user input.
Strings are immutable
None of those methods changes the string it is called on; each returns a new one. That is why cleaned = line.strip() was an assignment: without it, line would still be padded. Trying to edit characters in place is refused outright:
name = "ada"
name[0] = "A"
Expected output:
Traceback (most recent call last):
File "greeting.py", line 2, in <module>
name[0] = "A"
~~~~^^^
TypeError: 'str' object does not support item assignment
The fix is always the same: build the new string and rebind the name — name = name.capitalize(). Immutability is also why strings can be dictionary keys, shared freely and cached without anybody sneaking a change in behind you.
f-strings: formatting with expressions
An f-string is a normal string prefixed with f in which { … } is replaced by the value of whatever expression you write inside it — function calls, arithmetic and method chains are all allowed:
name = "Ada"
price = 49.5
print(f"Hello, {name}!")
print(f"Total: {price * 1.2:.2f}")
print(f"List price: {1000000:,}")
Expected output:
Hello, Ada!
Total: 59.40
List price: 1,000,000
After the colon comes a format specification: .2f means two decimal places as a fixed-point number, , asks for thousands separators, the d type code pads integers (f"{42:05d}" gives 00042), and >, < and ^ align a value in its field, so f"{name:>6}" right-aligns Ada inside a six-character field.
Two features make f-strings genuinely powerful. The debug specifier = appends the expression and its value, so you never write the label twice:
name = "Ada"
price = 49.5
print(f"{name=}")
print(f"{price * 2 =:.2f}")
Expected output:
name='Ada'
price * 2 =99.00
And format specifications can themselves be nested — a width and precision supplied as variables, with the braces packed inside braces:
width, digits, value = 12, 3, 3.14159
print(f"|{value:{width}.{digits}f}|")
# since Python 3.12 the same quote style may be reused inside
print(f"{"nested"}")
Expected output:
| 3.142|
nested
Escaping and multi-line text
Backslashes reserve characters that would otherwise end the string early, and triple quotes keep the line breaks you typed. f-strings work in all of these forms, which is how receipts, logs and templates get written:
print("She said \"hi\"")
print("Tab:\there")
item = "Course bundle"
price = 49.5
receipt = f"""Item: {item}
Total: {price:.2f}"""
print(receipt)
Expected output:
She said "hi"
Tab: here
Item: Course bundle
Total: 49.50
If a backslash appears anywhere inside an f-string expression, the string must be triple quoted — a restriction that existed before 3.12 and is the main reason the newer quote-reuse rule matters. For raw text you never want interpreted, prefix with r, rf or fr.
Common mistakes
- Forgetting the
fprefix —"Total: {price}"prints the braces literally; the placeholder only lives in f-strings. - Expecting methods to modify in place —
line.strip()returns a new string and discards it if you do not assign the result. - Counting indexes from one —
text[1]is the second character, and the stop bound of a slice is excluded. - Escaping a raw string —
r"C:\new"keeps the backslash; if you need a newline, do not user. - Using
+to build long output — concatenation in a loop is quadratic and hard to read; reach for an f-string orjoin().
Practice checklist
- Print your full name padded to 12 characters using
>,<and^, and note which alignment you prefer. - Take a messy string of your own (
" one, two, three ") and clean it withstrip(),split()andjoin(). - Prove immutability: call
upper()without assigning it, print the original, then reassign and print again. - Write a receipt with a triple-quoted f-string showing an item, quantity and a total formatted to two decimals.
- Use the debug specifier on three different expressions, including one method call, and read the output carefully.
What's next?
Text is handled; numbers are next, and Python has a few surprises there — floor division, operator precedence, the math module and the conversions that fail loudly: Numbers, Operators and Type Conversion.
Frequently Asked Questions
Python is a general-purpose language first released in 1991, designed around one idea: code is read far more often than it is written. English keywords and indentation instead of braces make it readable, the standard library covers files, JSON, dates and testing, and pip adds thousands more packages. The same language handles scripts, web APIs, data work and machine learning.
None at all. Python is widely considered the friendliest first language: no declaration keywords, no braces, no build step, and error messages that name the problem. This series begins at your first print() call. If you already read JavaScript or TypeScript you will adjust quickly, but no previous language is assumed anywhere in these 24 chapters.
Python 3 only, any release from 3.12 onwards. Python 2 reached end of life in 2021 and its print "hi" statements and xrange() no longer run. One historical quirk remains: installers ship the interpreter as python3 on macOS and Linux, while Windows usually offers python or the py launcher, and every command in this series works with that one-word swap.
More than you might expect. Everyday automation such as renaming files, cleaning spreadsheets or scraping pages; web backends with Django, Flask or FastAPI; data analysis and machine learning with pandas, NumPy and PyTorch; DevOps tooling, desktop apps and games. Python is also the classic glue language, calling C, Rust or Fortran libraries from short readable scripts.
Pure Python can run 10 to 100 times slower than C, yet that rarely blocks you: heavy numeric work lives in C extensions such as NumPy, which Python coordinates. Dynamic typing does push errors to runtime, surfacing as a TypeError on the line that finally uses the bad value. Type hints checked by mypy move those mistakes back into your editor.
Only the interpreter. Install CPython from python.org, version 3.12 or newer, confirm it with python3 --version, and open a plain text editor; VS Code with the Python extension is the recommendation here. The early chapters use no third-party packages, so there is nothing else to set up until virtual environments and pip arrive.
A dict is a real runtime mapping: keys may be any hashable value rather than only strings, insertion order has been guaranteed since Python 3.7, and a missing key raises KeyError unless you ask with get(). JavaScript objects coerce every key to a string and carry a prototype chain. A TypeScript interface is erased at compile time and describes shape only; TypedDict is Python's checked equivalent.
Reach for a list when items will be added, removed or reordered: it is ordered, mutable and rich with methods. Pick a tuple for fixed records and function returns, because it is immutable and hashable, so it can be a dictionary key. Choose a set for unique values and fast membership checks; it has no order and de-duplicates on creation.
Yes, gradually. Annotate public function signatures first; the interpreter ignores hints at runtime, so a checker such as mypy is what gives them teeth. Add tests the same way: pytest collects any test_*.py file and plain assert statements, and a suite that finishes in a tenth of a second is one you will run after every change. Both habits cost little now and much more later.
JavaScript and TypeScript keep the browser, so anything rendered to the user stays theirs. Python takes the server: Django for full-featured applications, Flask for small ones, FastAPI for typed async APIs where your annotations become automatic documentation. Your TypeScript habits transfer directly, and asyncio mirrors the event loop you already know, just with different keywords.