Python RegEx

Python Tutorial › Modules › RegEx

Python RegEx

IntermediateLesson 40 of 4714 min read12 runnable examples

A regular expression (regex) is a small pattern language for finding and extracting text: every email address in a page, every number in a log, every date in a report. Python’s re module runs these patterns for you.

In this lesson
  • search, match, fullmatch, findall and sub
  • Character classes and quantifiers
  • Capture groups and named groups
  • Flags and compiled patterns

Your first regex

Write patterns as raw strings (r"...") so backslashes reach the regex engine unchanged.

Python
import re text = "Order 4521 shipped on 2026-11-02, order 4522 pending"match = re.search(r"\d+", text)      # first run of digitsprint(match.group(), match.start(), match.end()) print(re.findall(r"\d+", text))     # every run of digits
Output
4521 6 10
['4521', '2026', '11', '02', '4522']

The main functions

FunctionReturns
re.search(p, s)First match anywhere, or None
re.match(p, s)Match only at the start, or None
re.fullmatch(p, s)Match only if the whole string fits
re.findall(p, s)List of all matches (or of groups)
re.finditer(p, s)Iterator of match objects
re.sub(p, repl, s)String with matches replaced
re.split(p, s)List split on the pattern
Python
import re print(re.match(r"\d+", "123abc"))print(re.match(r"\d+", "abc123"))print(bool(re.fullmatch(r"\d{6}", "411001")), bool(re.fullmatch(r"\d{6}", "41100")))print(re.sub(r"\s+", " ", "too    many   spaces"))print(re.split(r"[,;]\s*", "a, b;c ,d"))
Output
<re.Match object; span=(0, 3), match='123'>
None
True False
too many spaces
['a', 'b', 'c ', 'd']
Tip

Always check for None before calling .group() on the result of search or match: if m := re.search(p, s): print(m.group()).

Character classes

PatternMatches
\d / \Da digit / not a digit
\w / \Wa word character (letter, digit, _) / not
\s / \Swhitespace / not
.any character except newline
[aeiou]one of the listed characters
[a-z0-9]a character in the ranges
[^0-9]any character NOT listed
Python
import re s = "Room B-12, floor 3; call +91 98765 43210"print(re.findall(r"[A-Z]", s))print(re.findall(r"[aeiou]", s))print(re.findall(r"[^\w\s]", s))
Output
['R', 'B']
['o', 'o', 'o', 'o', 'a']
['-', ',', ';', '+']

Quantifiers

PatternMeaning
*0 or more
+1 or more
?0 or 1 (optional)
{3}exactly 3
{2,4}2 to 4
Python
import re print(re.findall(r"colou?r", "color colour colr"))print(re.findall(r"\d{2,3}", "1 12 123 1234"))print(re.findall(r"go+gle", "ggle gogle google gooogle"))
Output
['color', 'colour']
['12', '123', '123']
['gogle', 'google', 'gooogle']

Quantifiers are greedy: they take as much as possible. Add ? to make them lazy:

Python
import re html = "<b>bold</b> and <i>italic</i>"print(re.findall(r"<.+>", html))     # greedy: one huge matchprint(re.findall(r"<.+?>", html))    # lazy: each tag
Output
['<b>bold</b> and <i>italic</i>']
['<b>', '</b>', '<i>', '</i>']

Anchors and boundaries

^ is the start, $ the end, and \b a word boundary:

Python
import re print(re.findall(r"\bcat\b", "cat catalog concat cat."))print(bool(re.search(r"^ERROR", "ERROR: disk full")), bool(re.search(r"\.py$", "test_login.py")))
Output
['cat', 'cat']
True True

Groups

Brackets capture part of the match. findall returns the groups when the pattern has them.

Python
import re log = "2026-11-02 ERROR db; 2026-11-03 WARN cache"for m in re.finditer(r"(\d{4})-(\d{2})-(\d{2}) (\w+)", log):    print(m.group(0), "->", m.group(1), m.group(4)) print(re.findall(r"(\d{4})-\d{2}-\d{2} (\w+)", log))
Output
2026-11-02 ERROR -> 2026 ERROR
2026-11-03 WARN -> 2026 WARN
[('2026', 'ERROR'), ('2026', 'WARN')]

Named groups make patterns self-documenting:

Python
import re m = re.search(r"(?P<user>[\w.]+)@(?P<domain>[\w.-]+)", "contact: asha.k@sqatools.in today")print(m.group("user"), m.group("domain"))print(m.groupdict())
Output
asha.k sqatools.in
{'user': 'asha.k', 'domain': 'sqatools.in'}

Use groups in a replacement with \1 or \g<name>:

Python
import reprint(re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\3/\2/\1", "Due 2026-11-02 and 2026-12-25"))
Output
Due 02/11/2026 and 25/12/2026

Flags

Python
import re text = "Python\npython\nPYTHON"print(re.findall(r"python", text, flags=re.IGNORECASE))print(re.findall(r"^p\w+", text, flags=re.I | re.M))     # ^ at every line start
Output
['Python', 'python', 'PYTHON']
['Python', 'python', 'PYTHON']

Compile reusable patterns

Python
import re EMAIL = re.compile(r"^[\w.+-]+@[\w-]+(\.[\w-]+)+$")for e in ["asha@sqatools.in", "bad@", "ravi.k+test@mail.co.uk", "no spaces@x.com"]:    print(e, bool(EMAIL.match(e)))
Output
asha@sqatools.in True
bad@ False
ravi.k+test@mail.co.uk True
no spaces@x.com False
Watch out

Regex is great for patterns in text, but do not use it to parse HTML or JSON. Use html.parser, BeautifulSoup or json instead.

Escape special characters

Characters like . * + ? ( ) [ ] { } ^ $ | and the backslash have special meanings. Escape them with a backslash, or let re.escape() do it:

Python
import re price = "Rs. 1,499.50 (incl. tax)"print(re.findall(r"\d[\d,]*\.\d+", price))print(re.escape("1+1=2?"))
Output
['1,499.50']
1\+1=2\?

Check your understanding

Predict the output

import res = "a1b22c333"print(re.findall(r"\d", s), re.findall(r"\d+", s), re.sub(r"\d+", "#", s))

Quick quiz

1. What is the difference between re.match and re.search?

2. What does \d{3} match?

3. Why write patterns as raw strings like r"\d+"?

4. What does re.findall return when the pattern has one group?

Exercise: extract phone numbers

Find all 10-digit Indian mobile numbers (starting with 6, 7, 8 or 9) in the text, allowing an optional +91 and a space or hyphen after the first 5 digits.

Input
"Call 98765-43210 or +91 91234 56789; old: 12345 67890"
Expected output
98765-43210 -> 9876543210
+91 91234 56789 -> 9123456789
Hint 1

Start with (?:\+91\s?)? for the optional country code; (?: ) groups without capturing.

Hint 2

Then [6-9]\d{4}[\s-]?\d{5}.

Program Practice

Python regex FAQ

What is the re module in Python?

re is the standard-library module for regular expressions. It provides search, match, findall, sub, split and compile.

How do I extract numbers from a string in Python?

re.findall(r"\d+", text) returns every run of digits as strings. Use r"-?\d+(?:\.\d+)?" to include negatives and decimals.

What does re.compile do?

It turns a pattern into a reusable object. It makes code clearer when the same pattern is used many times; Python also caches recently used patterns automatically.

How do I make a regex case-insensitive?

Pass flags=re.IGNORECASE (or re.I), or start the pattern with (?i).

Python Modules & pip

Python Tutorial › Modules › Modules & pip

Python Modules & pip

IntermediateLesson 35 of 4712 min read6 runnable examples

A module is simply a .py file whose code you can reuse with import. Python ships with hundreds of modules (the standard library), and pip installs thousands more from PyPI. This lesson shows how to use both, and how to organise your own code into modules.

In this lesson
  • The different import styles
  • Create and import your own module
  • What if __name__ == “__main__” means
  • Install packages with pip in a virtual environment

Import a module

Python
import mathprint(math.sqrt(81)) from math import pi, floorprint(round(pi, 4), floor(7.9)) import statistics as statsprint(stats.mean([4, 8, 15, 16, 23, 42]))
Output
9.0
3.1416 7
18
StyleUse it asWhen
import mathmath.sqrt()Default; clear where names come from
from math import sqrtsqrt()You use a few names often
import numpy as npnp.array()Long names with a standard alias
from math import *sqrt()Avoid: hides where names come from

Explore a module

Python
import randomnames = [n for n in dir(random) if not n.startswith("_")]print(len(names) > 20)print("choice" in names, "shuffle" in names)print(random.choice.__doc__.splitlines()[0])
Output
True
True True
Choose a random element from a non-empty sequence.

Create your own module

Any .py file is a module. Save this as pricing.py:

pricing.py
TAX_RATE = 0.18 def with_tax(amount):    return round(amount * (1 + TAX_RATE), 2) def discount(amount, percent):    return round(amount * (1 - percent / 100), 2)

Then import it from another file in the same folder:

shop.py
import pricingfrom pricing import discount print(pricing.with_tax(1000))print(discount(1000, 15))print(pricing.TAX_RATE)
Output
1180.0
850.0
0.18

if __name__ == “__main__”

When a file is run directly, its __name__ is "__main__". When it is imported, __name__ is the module’s name. This lets a file be both a reusable module and a runnable script:

greeter.py
def greet(name):    return f"Hello, {name}" print("__name__ is", __name__) if __name__ == "__main__":    print(greet("from the command line"))
Output
__name__ is __main__
Hello, from the command line
app.py
import greeterprint(greeter.greet("from app.py"))
Output
__name__ is greeter
Hello, from app.py

When app.py imports greeter, the top-level print runs with __name__ set to greeter, but the code under the if does not.

Packages

A package is a folder of modules. Import modules from it with dots:

Terminal
myshop/    __init__.py    pricing.py    cart.pymain.py          ->  from myshop.pricing import with_tax

The standard library

ModuleUse it for
os, pathlib, shutilFiles, folders and paths
sysInterpreter details, command-line arguments
datetime, timeDates, times and timers
json, csvReading and writing data files
reRegular expressions
collections, itertoolsExtra containers and iterator tools
random, math, statisticsNumbers
loggingLog messages
unittestTesting (pytest is the popular third-party choice)

Install packages with pip

pip downloads packages from PyPI. Always run it through the Python you are using: python -m pip install requests.

Terminal
$ python -m pip --versionpip 26.2.2 from C:\Python3\Lib\site-packages\pip (python 3.15)

Use a virtual environment

A virtual environment is a private folder of packages for one project, so different projects can use different versions. Create one and check that its Python is used:

Terminal
$ python -m venv .venv$ .venv\Scripts\python -c "import sys; print(sys.prefix.endswith('.venv'))"True

Activate it so python and pip use it automatically:

  • Windows: .venv\Scripts\activate
  • macOS and Linux: source .venv/bin/activate

Then install and record your packages:

  • pip install requests pytest
  • pip freeze > requirements.txt saves the exact versions
  • pip install -r requirements.txt recreates them on another machine
Tip

Add .venv/ to .gitignore. Commit requirements.txt (or pyproject.toml) instead, so others can rebuild the environment.

Check your understanding

Quick quiz

1. What is a Python module?

2. What is __name__ when a file is run directly?

3. Why use python -m pip instead of plain pip?

4. Which file lists a project’s packages for others to install?

Program Practice

Python modules FAQ

What is the difference between a module and a package?

A module is one .py file. A package is a folder that groups modules (traditionally with an __init__.py file).

What does if __name__ == “__main__” do?

It runs the code below it only when the file is executed directly, not when it is imported by another file.

What is pip in Python?

pip is the package installer. It downloads packages from the Python Package Index (PyPI): python -m pip install package-name.

Why should I use a virtual environment?

It isolates each project’s packages, so upgrading a package for one project cannot break another, and requirements.txt stays accurate.

Python Polymorphism & Encapsulation

Python Tutorial › Object-Oriented Python › Polymorphism & Encapsulation

Python Polymorphism & Encapsulation

IntermediateLesson 33 of 4711 min read6 runnable examples

Polymorphism means “many forms”: different objects respond to the same method call in their own way. Encapsulation means keeping an object’s data behind its methods so it cannot be put into an invalid state. Together with inheritance they are the core ideas of object-oriented programming.

In this lesson
  • Polymorphism with overriding and duck typing
  • Operator overloading with special methods
  • Private attributes and name mangling
  • Getters and setters with @property

Polymorphism through overriding

Each class implements the same method name. Code that calls the method does not need to know which class it is dealing with.

Python
class Shape:    def area(self):        raise NotImplementedError class Circle(Shape):    def __init__(self, r):        self.r = r    def area(self):        return round(3.14159 * self.r ** 2, 2) class Square(Shape):    def __init__(self, side):        self.side = side    def area(self):        return self.side ** 2 for shape in [Circle(1), Square(3), Circle(2)]:    print(type(shape).__name__, shape.area())
Output
Circle 3.14
Square 9
Circle 12.57

Duck typing

Python does not require a shared parent class. If an object has the method you call, it works. “If it walks like a duck and quacks like a duck, it is a duck.”

Python
class PdfReport:    def export(self):        return "report.pdf" class CsvReport:    def export(self):        return "report.csv" def save_all(reports):    for r in reports:        print("saved", r.export()) save_all([PdfReport(), CsvReport()])
Output
saved report.pdf
saved report.csv

Built-in functions are polymorphic too: len() works on strings, lists and dicts because each type implements __len__.

Python
for thing in ["abc", [1, 2], {"a": 1}, range(10)]:    print(type(thing).__name__, len(thing))
Output
str 3
list 2
dict 1
range 10

Operator overloading

Special methods let your own classes work with +, ==, < and friends:

Python
class Money:    def __init__(self, amount):        self.amount = amount     def __add__(self, other):        return Money(self.amount + other.amount)     def __lt__(self, other):        return self.amount < other.amount     def __repr__(self):        return f"Money({self.amount})" a, b = Money(250), Money(100)print(a + b)print(a < b, sorted([a, b]))
Output
Money(350)
False [Money(100), Money(250)]
OperatorSpecial method
a + b__add__
a - b__sub__
a * b__mul__
a == b__eq__
a < b__lt__
len(a)__len__
str(a)__str__

Encapsulation: protecting data

Python has no true private attributes, but it has two conventions:

  • _name (one underscore): “internal, please do not touch from outside”.
  • __name (two underscores): Python renames it to _ClassName__name (name mangling), so it is hard to reach by accident.
Python
class Wallet:    def __init__(self):        self._currency = "INR"      # internal by convention        self.__balance = 0          # name-mangled     def add(self, amount):        self.__balance += amount     def balance(self):        return self.__balance w = Wallet()w.add(500)print(w.balance(), w._currency)print(w._Wallet__balance)          # still reachable if you insistprint(w.__balance)
Output
500 INR
500
Traceback (most recent call last):
  File "main.py", line 16, in <module>
    print(w.__balance)
          ^^^^^^^^^^^
AttributeError: 'Wallet' object has no attribute '__balance'. Did you mean '.balance' instead of '.__balance'?

Getters and setters with @property

@property lets you validate values while keeping simple attribute syntax for the caller:

Python
class Product:    def __init__(self, name, price):        self.name = name        self.price = price          # goes through the setter below     @property    def price(self):        return self._price     @price.setter    def price(self, value):        if value < 0:            raise ValueError("price cannot be negative")        self._price = value     @property    def price_with_tax(self):       # read-only computed attribute        return round(self._price * 1.18, 2) p = Product("Mouse", 349)p.price = 399print(p.price, p.price_with_tax) try:    p.price = -5except ValueError as e:    print("Error:", e)
Output
399 470.82
Error: price cannot be negative
Tip

Start with plain public attributes. Switch to @property only when you need validation or a computed value; callers do not need to change their code.

Check your understanding

Predict the output

class Cat:    def speak(self): return "meow"class Robot:    def speak(self): return "beep" print([x.speak() for x in (Cat(), Robot())])

Quick quiz

1. What does duck typing mean?

2. What does __add__ enable?

3. What happens to self.__secret inside class Box?

Exercise: temperature with validation

Create a Thermostat with a target property that only accepts values from 16 to 30 and raises ValueError otherwise.

Input
set target to 24, then try 35
Expected output
24
Rejected: 35 is outside 16-30
24
Hint 1

Store the real value in self._target inside the setter.

Program Practice

48 Python OOP Programs

Every program has a step-by-step explanation, colour-coded code and its real output.

Start practising →
Related sets:All practice sets

Polymorphism and encapsulation FAQ

What is polymorphism in Python with an example?

It is the ability of different objects to respond to the same call. len("abc") and len([1, 2]) both work, and shape.area() returns the right result for a Circle or a Square.

Does Python have private variables?

Not strictly. A leading underscore marks an attribute as internal, and a double underscore triggers name mangling. Both are conventions that discourage outside access.

What is @property in Python?

A decorator that turns a method into an attribute-like getter. Add @name.setter to run validation when the attribute is assigned.

Does Python support method overloading?

Not by signature. Define one method with default or variable arguments (*args) instead. Operator overloading is supported through special methods such as __add__.

Python Inheritance

Python Tutorial › Object-Oriented Python › Inheritance

Python Inheritance

IntermediateLesson 32 of 4711 min read8 runnable examples

Inheritance lets a new class reuse and extend an existing one. The child class gets all the parent’s attributes and methods, and can add its own or change how some of them behave. Page Object frameworks use this to share common page code in a BasePage.

In this lesson
  • Create a child class
  • Extend __init__ with super()
  • Override methods
  • Multiple inheritance and the MRO

A parent and a child

Put the parent class in brackets after the child’s name. The child inherits everything from the parent.

Python
class Vehicle:    def __init__(self, brand):        self.brand = brand     def start(self):        return f"{self.brand} engine started" class Car(Vehicle):        # Car inherits from Vehicle    pass c = Car("Tata")print(c.start())print(isinstance(c, Car), isinstance(c, Vehicle))
Output
Tata engine started
True True

Add attributes with super()

To add new attributes, define __init__ in the child and call the parent’s __init__ with super(), so the parent’s setup still happens.

Python
class Vehicle:    def __init__(self, brand):        self.brand = brand class Car(Vehicle):    def __init__(self, brand, seats):        super().__init__(brand)     # let Vehicle set brand        self.seats = seats          # then add Car's own data c = Car("Mahindra", 7)print(c.brand, c.seats)
Output
Mahindra 7
Watch out

If the child defines __init__ and forgets super().__init__(...), the parent’s attributes are never set and you get AttributeError later.

Override methods

A method with the same name in the child replaces the parent’s version. Call super().method() to reuse the parent’s code inside the new one.

Python
class Employee:    def __init__(self, name, salary):        self.name = name        self.salary = salary     def annual_pay(self):        return self.salary * 12     def describe(self):        return f"{self.name} earns {self.annual_pay()} a year" class Manager(Employee):    def __init__(self, name, salary, bonus):        super().__init__(name, salary)        self.bonus = bonus     def annual_pay(self):                     # override        return super().annual_pay() + self.bonus staff = [Employee("Asha", 50000), Manager("Ravi", 80000, 100000)]for person in staff:    print(person.describe())
Output
Asha earns 600000 a year
Ravi earns 1060000 a year

describe() is defined only in Employee, yet for a Manager it calls the overridden annual_pay(). Python always looks up the method on the actual object.

A real example: page objects

Python
class BasePage:    url = "/"     def __init__(self, base_url):        self.base_url = base_url     def open(self):        return f"GET {self.base_url}{self.url}" class LoginPage(BasePage):    url = "/login"     def login(self, user):        return f"{self.open()} -> sign in as {user}" class CartPage(BasePage):    url = "/cart" print(LoginPage("https://shop.test").login("asha"))print(CartPage("https://shop.test").open())
Output
GET https://shop.test/login -> sign in as asha
GET https://shop.test/cart

Multilevel inheritance

A child can itself be a parent:

Python
class Animal:    def breathe(self):        return "breathing" class Mammal(Animal):    def feed(self):        return "feeding milk" class Dog(Mammal):    def bark(self):        return "woof" d = Dog()print(d.breathe(), "|", d.feed(), "|", d.bark())
Output
breathing | feeding milk | woof

Multiple inheritance and the MRO

A class can inherit from several parents. When two parents define the same method, Python follows the method resolution order (MRO): the child first, then the parents from left to right.

Python
class Flyer:    def move(self):        return "flying" class Swimmer:    def move(self):        return "swimming" class Duck(Flyer, Swimmer):    pass print(Duck().move())print([cls.__name__ for cls in Duck.__mro__])
Output
flying
['Duck', 'Flyer', 'Swimmer', 'object']
Tip

Multiple inheritance is mostly used for small “mixin” classes that add one feature, such as logging. Keep hierarchies shallow.

Checking relationships

Python
class A: passclass B(A): pass print(issubclass(B, A), issubclass(A, B))print(isinstance(B(), A))
Output
True False
True

Abstract base classes

An abstract class defines methods that every child must implement. You cannot create an object from it directly.

Python
from abc import ABC, abstractmethod class Shape(ABC):    @abstractmethod    def area(self):        ... class Square(Shape):    def __init__(self, side):        self.side = side    def area(self):        return self.side ** 2 print(Square(4).area())Shape()
Output
16
Traceback (most recent call last):
  File "main.py", line 15, in <module>
    Shape()
    ~~~~~^^
TypeError: Can't instantiate abstract class Shape without an implementation for abstract method 'area'

Check your understanding

Predict the output

class A:    def hello(self):        return "A"class B(A):    def hello(self):        return "B+" + super().hello()class C(B):    pass print(C().hello())

Quick quiz

1. How do you call the parent’s __init__ from a child?

2. What is method overriding?

3. For class D(B, C), where is a method looked up after D?

Exercise: savings account

Create SavingsAccount that inherits from Account, adds an interest_rate, and has an add_interest() method.

Input
SavingsAccount("Meena", 1000, 0.05)
Expected output
Meena 1050.0
Hint 1

Call super().__init__(owner, balance) first.

Program Practice

48 Python OOP Programs

Every program has a step-by-step explanation, colour-coded code and its real output.

Start practising →
Related sets:All practice sets

Python inheritance FAQ

What does super() do in Python?

super() gives access to methods of the parent class (strictly, the next class in the MRO), most often to call the parent’s __init__ or an overridden method.

Does Python support multiple inheritance?

Yes. A class can list several parents. Python resolves name clashes with the method resolution order (C3 linearisation), which you can inspect with ClassName.__mro__.

What is the difference between inheritance and composition?

Inheritance models “is a” (a Manager is an Employee). Composition models “has a” (a Car has an Engine) by storing another object as an attribute. Prefer composition when the relationship is not truly “is a”.

Python Iterators

Python Tutorial › Functions › Iterators

Python Iterators

IntermediateLesson 28 of 479 min read8 runnable examples

Every for loop in Python uses an iterator behind the scenes. Knowing how iterators work explains why some objects can be looped over only once, and lets you write your own objects that work with for.

In this lesson
  • Iterables vs iterators
  • iter(), next() and StopIteration
  • Build a custom iterator class
  • Useful tools from itertools

Iterable vs iterator

  • An iterable is anything you can loop over: a list, string, tuple, dict, set, file or range.
  • An iterator is the object that actually hands out the items, one at a time, and remembers its position.

iter() gets an iterator from an iterable, and next() asks it for the next item:

Python
colors = ["red", "green", "blue"]it = iter(colors) print(next(it))print(next(it))print(next(it))
Output
red
green
blue

When there are no items left, next() raises StopIteration:

Python
it = iter([1])print(next(it))print(next(it))
Output
1
Traceback (most recent call last):
  File "main.py", line 3, in <module>
    print(next(it))
          ~~~~^^^^
StopIteration

Pass a default to next() to avoid the error:

Python
it = iter([])print(next(it, "no more items"))
Output
no more items

How a for loop works

A for loop is a while loop that calls iter() once and next() until StopIteration:

Python
items = ["a", "b", "c"] it = iter(items)while True:    try:        item = next(it)    except StopIteration:        break    print(item)
Output
a
b
c

Iterators are used up

An iterator moves forward only. Once it is exhausted it stays empty. A list can be looped over many times because each for loop asks it for a fresh iterator.

Python
squares = map(lambda x: x * x, [1, 2, 3])    # map returns an iteratorprint(list(squares))print(list(squares))      # already used up nums = [1, 2, 3]print(sum(nums), sum(nums))   # a list gives a new iterator each time
Output
[1, 4, 9]
[]
6 6

Write your own iterator

A class becomes an iterator by defining __iter__() (returning itself) and __next__() (returning the next item or raising StopIteration):

Python
class Countdown:    def __init__(self, start):        self.current = start     def __iter__(self):        return self     def __next__(self):        if self.current <= 0:            raise StopIteration        value = self.current        self.current -= 1        return value for n in Countdown(3):    print(n)print(list(Countdown(5)))
Output
3
2
1
[5, 4, 3, 2, 1]
Tip

Writing a class like this takes a dozen lines. A generator function does the same job in three; see the next lesson, Generators.

Infinite iterators and itertools

Iterators can be infinite because they produce values only when asked. The itertools module has many ready-made ones:

Python
import itertools counter = itertools.count(start=100, step=10)print([next(counter) for _ in range(3)]) print(list(itertools.islice(itertools.cycle("AB"), 5)))print(list(itertools.chain([1, 2], (3, 4), "56")))print(list(itertools.combinations("ABC", 2)))print(list(itertools.product([0, 1], repeat=2)))
Output
[100, 110, 120]
['A', 'B', 'A', 'B', 'A']
[1, 2, 3, 4, '5', '6']
[('A', 'B'), ('A', 'C'), ('B', 'C')]
[(0, 0), (0, 1), (1, 0), (1, 1)]

Built-ins that return iterators

map(), filter(), zip(), enumerate(), reversed() and open() all return iterators. They are lazy: no work happens until you ask for items.

Python
pairs = zip(["a", "b", "c"], [1, 2, 3])print(next(pairs))print(list(pairs))       # continues from where next() left off
Output
('a', 1)
[('b', 2), ('c', 3)]

Check your understanding

Predict the output

it = iter("abc")next(it)print(list(it), list(it))

Quick quiz

1. Which function gets the next item from an iterator?

2. What happens when an iterator has no more items?

3. Which two methods make a class an iterator?

Exercise: even number iterator

Write a class Evens(limit) that iterates over the even numbers from 0 up to and including limit.

Input
Evens(10)
Expected output
[0, 2, 4, 6, 8, 10]
Hint 1

Store the next value in self.n and add 2 each time.

Program Practice

20 Python Iterator & Generator Programs

Every program has a step-by-step explanation, colour-coded code and its real output.

Start practising →
Related sets:All practice sets

Python iterators FAQ

What is the difference between an iterable and an iterator?

An iterable can produce an iterator (lists, strings, dicts). An iterator produces the items one at a time with next() and remembers its position. Every iterator is also iterable.

Why can I loop over a list twice but not over map()?

A list creates a new iterator for each loop. map() is itself an iterator, so once its items are consumed it is empty.

How do I make a class iterable in Python?

Define __iter__() returning an iterator. The easiest way is to make __iter__ a generator method using yield.

Python Recursion

Python Tutorial › Functions › Recursion

Python Recursion

IntermediateLesson 27 of 4710 min read10 runnable examples

A recursive function is a function that calls itself to solve a smaller version of the same problem. Recursion is natural for problems with a repeating structure, such as nested folders, trees and many classic interview questions.

In this lesson
  • The base case and the recursive case
  • Trace how recursive calls unwind
  • Avoid the recursion limit
  • Speed up recursion with memoization

How recursion works

Every recursive function needs two parts:

  1. A base case that answers directly and stops the recursion.
  2. A recursive case that calls the function on a smaller input.
Python
def countdown(n):    if n == 0:              # base case        print("Lift off!")        return    print(n)    countdown(n - 1)        # recursive case: a smaller problem countdown(3)
Output
3
2
1
Lift off!

Factorial

5! means 5 × 4 × 3 × 2 × 1. Notice that 5! = 5 × 4!, which is a recursive definition.

Python
def factorial(n):    if n <= 1:        return 1    return n * factorial(n - 1) print(factorial(5))print(factorial(20))
Output
120
2432902008176640000

Trace the calls

Indenting each call by its depth shows how the calls go down to the base case and the results come back up:

Python
def factorial(n, depth=0):    pad = "  " * depth    print(f"{pad}factorial({n})")    if n <= 1:        result = 1    else:        result = n * factorial(n - 1, depth + 1)    print(f"{pad}-> {result}")    return result factorial(4)
Output
factorial(4)
  factorial(3)
    factorial(2)
      factorial(1)
      -> 1
    -> 2
  -> 6
-> 24

Sum of digits and reversing a string

Python
def digit_sum(n):    if n < 10:        return n    return n % 10 + digit_sum(n // 10) def reverse(text):    if len(text) <= 1:        return text    return reverse(text[1:]) + text[0] print(digit_sum(40721), reverse("python"))
Output
14 nohtyp

Recursion on nested data

Recursion shines when data contains smaller copies of itself, such as lists inside lists:

Python
def flatten(items):    flat = []    for item in items:        if isinstance(item, list):            flat.extend(flatten(item))     # recurse into the sub-list        else:            flat.append(item)    return flat print(flatten([1, [2, [3, 4], 5], [[6]]]))
Output
[1, 2, 3, 4, 5, 6]

The recursion limit

Each call uses memory on the call stack. Python stops runaway recursion with a RecursionError (the default limit is about 1000 calls). A missing base case causes it immediately:

Python
def forever(n):    return forever(n + 1) forever(0)
Output
Traceback (most recent call last):
  File "main.py", line 4, in <module>
    forever(0)
    ~~~~~~~^^^
  File "main.py", line 2, in forever
    return forever(n + 1)
  File "main.py", line 2, in forever
    return forever(n + 1)
  File "main.py", line 2, in forever
    return forever(n + 1)
  [Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded
Python
import sysprint(sys.getrecursionlimit())
Output
1000
Watch out

Do not just raise the limit with sys.setrecursionlimit() to make a deep recursion work. Rewrite it as a loop instead.

Fibonacci and memoization

The naive Fibonacci function recalculates the same values again and again, so it becomes very slow:

Python
calls = 0def fib(n):    global calls    calls += 1    return n if n < 2 else fib(n - 1) + fib(n - 2) print(fib(20), "calls:", calls)
Output
6765 calls: 21891

functools.lru_cache remembers results, so each value is computed only once:

Python
from functools import lru_cache @lru_cache(maxsize=None)def fib(n):    return n if n < 2 else fib(n - 1) + fib(n - 2) print(fib(80))print(fib.cache_info().misses, "distinct calls")
Output
23416728348467685
81 distinct calls

Recursion or a loop?

Python
def factorial_loop(n):    result = 1    for i in range(2, n + 1):        result *= i    return result print(factorial_loop(5), factorial_loop(5) == 120)
Output
120 True
RecursionLoop
Best forTrees, nested data, divide-and-conquerSimple repetition
ReadabilityMatches recursive definitionsMatches step-by-step thinking
LimitsRecursion depth (about 1000)None
Speed in PythonSlower (function call overhead)Faster

Check your understanding

Predict the output

def power(base, exp):    if exp == 0:        return 1    return base * power(base, exp - 1) print(power(2, 10), power(5, 0))

Quick quiz

1. What stops a recursive function from calling itself forever?

2. What error does Python raise for recursion that is too deep?

3. What does @lru_cache do for a recursive function?

Exercise: count files in nested folders

A folder is a dict mapping names to either a file size (int) or another folder (dict). Write a recursive function that returns the total size.

Input
{"a.txt": 10, "img": {"b.png": 200, "old": {"c.png": 50}}, "d.md": 5}
Expected output
265
Hint 1

If a value is a dict, call the function on it; otherwise add the number.

Program Practice

Python recursion FAQ

What is recursion in Python?

Recursion is when a function calls itself to solve a smaller instance of the same problem, until it reaches a base case that it can answer directly.

What is the maximum recursion depth in Python?

The default limit is 1000 frames. sys.getrecursionlimit() shows it. Deep problems should use a loop instead.

Is recursion faster than iteration in Python?

No. Python function calls are relatively expensive, so a loop is usually faster. Use recursion when it makes the code clearer.

Python Scope

Python Tutorial › Functions › Scope

Python Scope

IntermediateLesson 26 of 479 min read8 runnable examples

Scope decides where a name can be seen. A variable created inside a function exists only inside that function, while a variable created at the top of a file can be read everywhere. Understanding scope explains several confusing errors.

In this lesson
  • Local and global variables
  • The LEGB lookup rule
  • When to use global and nonlocal
  • Closures that remember values

Local scope

A variable assigned inside a function is local: it is created when the function runs and disappears when it returns.

Python
def make_report():    title = "Daily report"     # local    print(title) make_report()print(title)
Output
Daily report
Traceback (most recent call last):
  File "main.py", line 6, in <module>
    print(title)
          ^^^^^
NameError: name 'title' is not defined. Did you mean: 'tuple'?

Global scope

A variable assigned at the top level of a file is global. Functions can read it.

Python
BASE_URL = "https://sqatools.in" def page_url(path):    return BASE_URL + path      # reading a global is fine print(page_url("/python-tutorials/"))
Output
https://sqatools.in/python-tutorials/

Same name, different variables

Assigning inside a function creates a new local variable, even if a global has the same name. The global is untouched.

Python
count = 10 def reset():    count = 0                   # a new local count    print("inside:", count) reset()print("outside:", count)
Output
inside: 0
outside: 10

The LEGB rule

When Python looks up a name, it searches four scopes in order and uses the first match:

OrderScopeExample
LLocal: the current functionvariables assigned in the function
EEnclosing: outer functionsvariables of a function that contains this one
GGlobal: the moduletop-level variables and functions
BBuilt-inprint, len, max
Python
x = "global" def outer():    x = "enclosing"    def inner():        print(x)          # not local, found in the enclosing function    inner() outer()print(len("built-in lookup"))
Output
enclosing
15

The global keyword

To assign to a global variable inside a function, declare it with global:

Python
counter = 0 def increment():    global counter    counter += 1 increment()increment()print(counter)
Output
2
Watch out

Functions that change globals are hard to test and reason about. Prefer passing values in and returning results: counter = increment(counter).

UnboundLocalError

If a function assigns to a name anywhere, that name is local for the whole function. Reading it before the assignment fails:

Python
total = 100 def add_tax():    total = total * 1.18      # total is local here, but has no value yet    return total add_tax()
Output
Traceback (most recent call last):
  File "main.py", line 7, in <module>
    add_tax()
    ~~~~~~~^^
  File "main.py", line 4, in add_tax
    total = total * 1.18      # total is local here, but has no value yet
            ^^^^^
UnboundLocalError: cannot access local variable 'total' where it is not associated with a value

The fix is to pass the value in: def add_tax(total): return total * 1.18.

nonlocal and closures

nonlocal lets an inner function assign to a variable of its enclosing function. The inner function keeps that variable alive even after the outer function has returned. This is called a closure.

Python
def make_counter():    count = 0    def next_value():        nonlocal count        count += 1        return count    return next_value ticket = make_counter()print(ticket(), ticket(), ticket()) other = make_counter()        # a separate countprint(other())
Output
1 2 3
1

Closures are useful for creating customised functions:

Python
def make_multiplier(factor):    def multiply(x):        return x * factor    return multiply double = make_multiplier(2)triple = make_multiplier(3)print(double(5), triple(5))
Output
10 15

Check your understanding

Predict the output

x = 1def f():    x = 2    def g():        nonlocal x        x = 3    g()    return xprint(f(), x)

Quick quiz

1. In what order does Python look up a name?

2. What is needed to change a module-level variable inside a function?

3. Why does UnboundLocalError happen?

Exercise: running average

Write make_averager() that returns a function. Each call adds a number and returns the average so far.

Input
10, then 20, then 60
Expected output
10.0 15.0 30.0
Hint 1

Keep total and count in the outer function and use nonlocal.

Program Practice

Python scope FAQ

What is the difference between local and global variables in Python?

A local variable is created inside a function and exists only while it runs. A global variable is created at the top level of a module and can be read anywhere in it.

When should I use the global keyword?

Rarely. Use it only when a function really must rebind a module-level variable. Passing values in and returning results is usually clearer.

What does nonlocal do in Python?

It lets a nested function assign to a variable of the enclosing function instead of creating a new local one.

Python Lambda

Python Tutorial › Functions › Lambda

Python Lambda

IntermediateLesson 25 of 478 min read10 runnable examples

A lambda is a small function written in a single expression, without a name. You will mostly use lambdas as short “key” or “rule” functions passed to sorted(), max(), map() and filter().

In this lesson
  • Write a lambda expression
  • Sort data with a lambda key
  • Transform and filter with map() and filter()
  • Know when a normal def is better

Lambda syntax

lambda parameters: expression. The expression’s value is returned automatically; there is no return keyword.

Python
square = lambda x: x * xadd = lambda a, b: a + b print(square(6), add(2, 3))
Output
36 5

The two lines above are equivalent to:

Python
def square(x):    return x * x print(square(6))
Output
36
Note

Assigning a lambda to a name, as in the first example, works but PEP 8 recommends def for that. Lambdas are meant to be used inline, where a function is needed only once.

Sort with a lambda key

key tells sorted() what to compare. The lambda receives each item and returns the value to sort by.

Python
employees = [("Asha", 52000), ("Ravi", 61000), ("Meena", 48000)] by_salary = sorted(employees, key=lambda e: e[1])print(by_salary) by_name_desc = sorted(employees, key=lambda e: e[0], reverse=True)print([name for name, _ in by_name_desc])
Output
[('Meena', 48000), ('Asha', 52000), ('Ravi', 61000)]
['Ravi', 'Meena', 'Asha']

Sort dictionaries, or sort by several things at once by returning a tuple:

Python
users = [{"name": "ravi", "age": 31}, {"name": "asha", "age": 28}, {"name": "meena", "age": 31}]print([u["name"] for u in sorted(users, key=lambda u: (-u["age"], u["name"]))])
Output
['meena', 'ravi', 'asha']

max() and min() with a key

Python
words = ["python", "is", "readable"]print(max(words, key=lambda w: len(w))) prices = {"pen": 10, "bag": 650, "book": 120}print(min(prices, key=lambda k: prices[k]))
Output
readable
pen

map(): transform every item

map(function, iterable) applies the function to each item and returns an iterator. Wrap it in list() to see the results.

Python
celsius = [0, 25, 37.5, 100]fahrenheit = list(map(lambda c: c * 9 / 5 + 32, celsius))print(fahrenheit) print(list(map(str.upper, ["get", "post"])))   # any function works, not just lambdas
Output
[32.0, 77.0, 99.5, 212.0]
['GET', 'POST']

filter(): keep matching items

filter(function, iterable) keeps the items for which the function returns a truthy value.

Python
scores = [45, 82, 67, 91, 38]print(list(filter(lambda s: s >= 50, scores)))
Output
[82, 67, 91]
Tip

A list comprehension is often clearer than map or filter with a lambda: [c * 9 / 5 + 32 for c in celsius] and [s for s in scores if s >= 50] do the same jobs. Use whichever reads better.

Lambdas with default values

Python
apply_discount = lambda price, pct=10: round(price * (1 - pct / 100), 2)print(apply_discount(500), apply_discount(500, 25))
Output
450.0 375.0

Limits of lambda

A lambda can contain only one expression: no statements, no if blocks (a conditional expression is fine), no loops, no assignments.

Python
grade = lambda m: "pass" if m >= 50 else "fail"print(grade(72), grade(31))
Output
pass fail
Python
bad = lambda x: y = x + 1
Output
  File "main.py", line 1
    bad = lambda x: y = x + 1
          ^^^^^^^^^^^
SyntaxError: cannot assign to lambda

If the logic needs more than one line, write a normal def.

Check your understanding

Predict the output

pairs = [(1, "b"), (3, "a"), (2, "c")]print(sorted(pairs, key=lambda p: p[1]), list(map(lambda p: p[0] * 10, pairs)))

Quick quiz

1. What does (lambda x, y: x * y)(3, 4) return?

2. Which is NOT allowed in a lambda?

3. What does map() return in Python 3?

Exercise: sort products

Sort the products by price, cheapest first, then print the names of products costing more than 100 using filter.

Input
[("pen", 10), ("bag", 650), ("book", 120), ("tape", 35)]
Expected output
[('pen', 10), ('tape', 35), ('book', 120), ('bag', 650)]
['bag', 'book']
Hint 1

key=lambda p: p[1] sorts by price.

Program Practice

Python lambda FAQ

What is a lambda function in Python?

A lambda is an anonymous function written as a single expression: lambda x: x * 2. It is used for short functions passed to other functions.

Can a lambda have multiple lines?

No. A lambda holds exactly one expression. Use def for anything longer.

When should I use lambda instead of def?

Use a lambda for a short throwaway function passed as an argument, such as a key for sorted(). Use def when the function is reused, needs a name, or has more than one step.

Python List Comprehension

Python Tutorial › Data Structures › List Comprehension

Python List Comprehension

IntermediateLesson 19 of 4710 min read8 runnable examples

A list comprehension builds a new list from an existing one in a single, readable line. It replaces the common “empty list, loop, append” pattern and is one of the features that makes Python code short.

In this lesson
  • Turn a for loop into a comprehension
  • Filter items with if
  • Choose values with if-else
  • Build sets and dicts the same way

From loop to comprehension

Here is the familiar pattern: start with an empty list, loop, and append.

Python
numbers = [1, 2, 3, 4, 5]squares = []for n in numbers:    squares.append(n * n)print(squares)
Output
[1, 4, 9, 16, 25]

The comprehension says the same thing in one line. Read it as “n times n, for each n in numbers“:

Python
numbers = [1, 2, 3, 4, 5]squares = [n * n for n in numbers]print(squares)
Output
[1, 4, 9, 16, 25]

The general form is [expression for item in iterable].

Filter with if

Add if condition at the end to keep only some items.

Python
scores = [45, 82, 67, 91, 38, 74]passed = [s for s in scores if s >= 50]print(passed) words = ["api", "", "ui", "  ", "db"]clean = [w for w in words if w.strip()]print(clean)
Output
[82, 67, 91, 74]
['api', 'ui', 'db']

Choose a value with if-else

To transform every item one of two ways, put a conditional expression at the front. Note the different position from the filter above.

Python
scores = [45, 82, 67, 91, 38]labels = ["pass" if s >= 50 else "fail" for s in scores]print(labels)
Output
['fail', 'pass', 'pass', 'pass', 'fail']
Tip

if at the end filters (fewer items). if ... else at the front maps (same number of items). You can use both: [x * 2 if x > 0 else 0 for x in data if x is not None].

Work with strings

Python
sentence = "Python makes testing simple"print([w.upper() for w in sentence.split()])print([len(w) for w in sentence.split()])print([ch for ch in "a1b2c3" if ch.isdigit()])
Output
['PYTHON', 'MAKES', 'TESTING', 'SIMPLE']
[6, 5, 7, 6]
['1', '2', '3']

Nested comprehensions

Two for clauses work like nested loops, read from left to right:

Python
pairs = [(x, y) for x in [1, 2, 3] for y in "ab"]print(pairs) matrix = [[1, 2, 3], [4, 5, 6]]flat = [n for row in matrix for n in row]print(flat) transposed = [[row[i] for row in matrix] for i in range(3)]print(transposed)
Output
[(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b'), (3, 'a'), (3, 'b')]
[1, 2, 3, 4, 5, 6]
[[1, 4], [2, 5], [3, 6]]
Watch out

If a comprehension needs more than two for clauses or a long condition, write a normal loop. Readability counts more than saving lines.

Set and dict comprehensions

Use { } to build a set, and {key: value ...} to build a dict.

Python
emails = ["a@x.com", "b@y.com", "c@x.com"]domains = {e.split("@")[1] for e in emails}print(sorted(domains)) prices = {"pen": 10, "book": 120, "bag": 650}with_tax = {item: round(p * 1.18, 2) for item, p in prices.items()}print(with_tax) expensive = {k: v for k, v in prices.items() if v > 100}print(expensive)
Output
['x.com', 'y.com']
{'pen': 11.8, 'book': 141.6, 'bag': 767.0}
{'book': 120, 'bag': 650}

Generator expressions

With round brackets you get a generator expression. It produces values one at a time instead of building a list, which saves memory when you only need to consume the values once, for example in sum():

Python
print(sum(n * n for n in range(1, 101)))print(any(s < 40 for s in [45, 82, 38]))
Output
338350
True

The Generators lesson explains how this works.

Check your understanding

Predict the output

data = [3, -1, 0, 8, -5]print([abs(x) for x in data if x != 0], [x if x > 0 else 0 for x in data])

Quick quiz

1. What does [x * 2 for x in range(3)] produce?

2. Where does a filtering if go?

3. Which builds a set?

Exercise: even squares and word lengths

Build a list of the squares of the even numbers from 1 to 10, and a dict mapping each word to its length.

Input
range 1 to 10; words = ["login", "cart", "checkout"]
Expected output
[4, 16, 36, 64, 100]
{'login': 5, 'cart': 4, 'checkout': 8}
Hint 1

Filter with if n % 2 == 0.

Hint 2

A dict comprehension looks like {w: len(w) for w in words}.

Program Practice

Many list programs can be solved in one line with a comprehension. Solve each one with a loop first, then rewrite it.

Python list comprehension FAQ

Is list comprehension faster than a for loop?

Usually a little, because the append happens inside the interpreter. Choose it for readability first; the speed gain is small.

Can I use else in a list comprehension?

Yes, as part of a conditional expression at the front: [a if cond else b for x in items]. A filtering if at the end cannot have an else.

What is the difference between a list comprehension and a generator expression?

A list comprehension [...] builds the whole list in memory. A generator expression (...) yields items one at a time, which is better for large data that you only loop over once.

Python String Formatting & f-strings

Python Tutorial › Strings › String Formatting & f-strings

Python String Formatting & f-strings

BeginnerLesson 17 of 4710 min read8 runnable examples

Formatting turns values into neat, readable text: prices with two decimals, columns that line up, percentages, dates. f-strings are the modern way to do it, and the format codes you learn here also work with str.format().

In this lesson
  • Put values into text with f-strings
  • Control decimals, width and alignment
  • Format numbers, percentages and dates
  • Recognise format() and % formatting in older code

f-string basics

Put an f before the opening quote and write any expression inside { }.

Python
name = "Asha"tests, passed = 40, 37 print(f"{name} ran {tests} tests")print(f"Pass rate: {passed / tests}")print(f"Upper: {name.upper()}, failed: {tests - passed}")
Output
Asha ran 40 tests
Pass rate: 0.925
Upper: ASHA, failed: 3

Format specifiers

After the expression, a colon starts the format specifier: {value:spec}.

Python
pi = 3.14159265print(f"{pi:.2f}")        # 2 decimal placesprint(f"{pi:.4f}")print(f"{1234567.891:,.2f}")   # thousands separatorprint(f"{0.8125:.1%}")    # percentageprint(f"{42:05d}")        # pad with zeros to width 5print(f"{255:x} {255:b}") # hex and binary
Output
3.14
3.1416
1,234,567.89
81.2%
00042
ff 11111111
SpecMeaningExampleResult
.2ffixed-point, 2 decimalsf"{3.14159:.2f}"3.14
,thousands separatorf"{1500000:,}"1,500,000
.1%percentage, 1 decimalf"{0.256:.1%}"25.6%
05dzero-pad to width 5f"{42:05d}"00042
>10right-align in 10 charsf"{'hi':>10}" hi
<10 / ^10left / centref"{'hi':^10}" hi
escientificf"{12345.678:.2e}"1.23e+04

Width and alignment

Fixed widths make columns line up. < aligns left, > right and ^ centre; the character before the arrow is the fill.

Python
rows = [("Keyboard", 2, 1499.5), ("Mouse", 10, 349), ("Monitor", 1, 12999)] print(f"{'Item':<10}{'Qty':>5}{'Price':>12}")print("-" * 27)for item, qty, price in rows:    print(f"{item:<10}{qty:>5}{price:>12,.2f}")print(f"{' Total ':=^27}")
Output
Item        Qty       Price
---------------------------
Keyboard      2    1,499.50
Mouse        10      349.00
Monitor       1   12,999.00
========== Total ==========

Debugging with =

Adding = after the expression prints the expression and its value:

Python
user_id = 7retries = 3print(f"{user_id=}, {retries * 2=}")
Output
user_id=7, retries * 2=6

Dates in f-strings

Date objects understand their own format codes (covered fully in the Datetime lesson):

Python
from datetime import date d = date(2026, 11, 2)print(f"{d:%d %B %Y}")print(f"{d:%Y-%m-%d} is a {d:%A}")
Output
02 November 2026
2026-11-02 is a Monday

Braces and quotes inside f-strings

Double the braces to print them literally. Use a different quote style for strings inside the expression:

Python
key = "id"print(f"{{{key}}}")data = {"id": 7}print(f"value: {data['id']}")
Output
{id}
value: 7

str.format()

str.format() uses the same {} placeholders and format specs. You will see it in older code and when a template is stored in a variable:

Python
template = "{name} scored {score:.1f}%"print(template.format(name="Ravi", score=87.456))print("{0} vs {1}, {0} wins".format("India", "Australia"))
Output
Ravi scored 87.5%
India vs Australia, India wins

The old % style

The oldest style uses % codes. It still appears in logging calls and old scripts:

Python
print("%s has %d items costing %.2f" % ("cart", 3, 499.999))
Output
cart has 3 items costing 500.00
Tip

Use f-strings for new code. They are the fastest and the easiest to read. Use str.format() only when the template must be defined before the values exist.

Check your understanding

Predict the output

x = 7.0 / 3print(f"[{x:.3f}] [{x:8.2f}] [{'ok':*^6}] [{1e6:,.0f}]")

Quick quiz

1. Which prints 0.50 for x = 0.5?

2. What does f"{0.25:.0%}" produce?

3. How do you print a literal { in an f-string?

Exercise: invoice line

Print each line as a 12-character left-aligned item name, the quantity right-aligned in 4 characters, and the line total with a thousands separator and 2 decimals in 12 characters.

Input
[("Laptop", 2, 54999), ("USB cable", 5, 199.5)]
Expected output
Laptop         2  109,998.00
USB cable      5      997.50
Hint 1

The line total is qty * price; use the spec >12,.2f.

Program Practice

Python string formatting FAQ

How do I format a float to 2 decimal places in Python?

Use an f-string: f"{value:.2f}". It returns a string; use round(value, 2) if you need a number.

What is an f-string in Python?

An f-string is a string literal prefixed with f in which {expression} placeholders are replaced by their values. They were added in Python 3.6.

How do I add commas to large numbers in Python?

Use the , format option: f"{1234567:,}" gives 1,234,567.

Which is better: f-string, format() or %?

f-strings are the most readable and the fastest. str.format() is useful for reusable templates, and % formatting is mainly found in older code and logging.