Python Decorators

Python Tutorial › Functions › Decorators

Python Decorators

AdvancedLesson 30 of 4712 min read7 runnable examples

A decorator adds behaviour to a function without changing its code: logging, timing, retrying, checking permissions. You meet decorators everywhere in test frameworks, such as @pytest.fixture and @pytest.mark.parametrize. This lesson builds one from scratch.

In this lesson
  • Why decorators work: functions are objects
  • Write a wrapper and apply it with @
  • Keep the function’s name with functools.wraps
  • Write decorators that take arguments

Functions are objects

A decorator relies on two facts: a function can be passed to another function, and a function can create and return a new function.

Python
def shout(text):    return text.upper() def call_twice(func, value):    return func(value), func(value) print(call_twice(shout, "hi"))
Output
('HI', 'HI')

A decorator by hand

A decorator is a function that takes a function and returns a new function, usually a wrapper that runs extra code around the original:

Python
def log_calls(func):    def wrapper(*args, **kwargs):        print(f"calling {func.__name__} with {args}")        result = func(*args, **kwargs)        print(f"{func.__name__} returned {result}")        return result    return wrapper def add(a, b):    return a + b add = log_calls(add)      # replace add with the wrapped versionadd(2, 3)
Output
calling add with (2, 3)
add returned 5

The @ syntax

@log_calls above a definition is a shortcut for add = log_calls(add):

Python
def log_calls(func):    def wrapper(*args, **kwargs):        print(f"calling {func.__name__}{args}")        return func(*args, **kwargs)    return wrapper @log_callsdef multiply(a, b):    return a * b print(multiply(4, 5))
Output
calling multiply(4, 5)
20
Note

The wrapper uses *args, **kwargs so it can pass along whatever arguments the decorated function takes.

Keep the original name with functools.wraps

Without help, the decorated function reports the wrapper’s name and loses its docstring. functools.wraps copies them over:

Python
from functools import wraps def plain(func):    def wrapper(*args, **kwargs):        return func(*args, **kwargs)    return wrapper def proper(func):    @wraps(func)    def wrapper(*args, **kwargs):        return func(*args, **kwargs)    return wrapper @plaindef first():    """First docstring.""" @properdef second():    """Second docstring.""" print(first.__name__, first.__doc__)print(second.__name__, second.__doc__)
Output
wrapper None
second Second docstring.

Always use @wraps in your decorators.

A timing decorator

Python
import timefrom functools import wraps def timer(func):    @wraps(func)    def wrapper(*args, **kwargs):        start = time.perf_counter()        result = func(*args, **kwargs)        elapsed = time.perf_counter() - start        print(f"{func.__name__} took {'under' if elapsed < 0.5 else 'over'} 0.5 s")        return result    return wrapper @timerdef slow_sum(n):    time.sleep(0.1)    return sum(range(n)) print(slow_sum(1000))
Output
slow_sum took under 0.5 s
499500

Decorators with arguments

To pass settings such as @retry(times=3), add one more outer function. retry(times=3) runs first and returns the actual decorator.

Python
from functools import wraps def retry(times):    def decorator(func):        @wraps(func)        def wrapper(*args, **kwargs):            for attempt in range(1, times + 1):                try:                    return func(*args, **kwargs)                except ValueError as e:                    print(f"attempt {attempt} failed: {e}")            raise RuntimeError(f"{func.__name__} failed {times} times")        return wrapper    return decorator calls = {"n": 0} @retry(times=3)def flaky():    calls["n"] += 1    if calls["n"] < 3:        raise ValueError("server busy")    return "success" print(flaky())
Output
attempt 1 failed: server busy
attempt 2 failed: server busy
success

Stacking decorators

Several decorators apply from the bottom up: the one closest to the function wraps it first.

Python
def bold(func):    return lambda: "<b>" + func() + "</b>" def italic(func):    return lambda: "<i>" + func() + "</i>" @bold@italicdef label():    return "Python" print(label())
Output
<b><i>Python</i></b>

Built-in decorators you will meet

DecoratorWhat it does
@staticmethod, @classmethodMethods that do not need an instance (see Classes & Objects)
@propertyCall a method like an attribute
@functools.lru_cacheRemember results of expensive calls
@dataclasses.dataclassGenerate __init__, __repr__ and more for a class
@pytest.fixture, @pytest.mark.parametrizeSet up test data and run a test with many inputs

Check your understanding

Predict the output

def twice(func):    def wrapper(x):        return func(func(x))    return wrapper @twicedef add3(x):    return x + 3 print(add3(10))

Quick quiz

1. What is @deco above def f(): equivalent to?

2. Why use functools.wraps?

3. With @a above @b above def f, which wraps f first?

Exercise: count calls

Write a decorator count_calls that counts how many times a function has been called and stores the number in wrapper.calls.

Input
call greet("Asha"), greet("Ravi"), then print greet.calls
Expected output
Hello Asha
Hello Ravi
calls: 2
Hint 1

Set wrapper.calls = 0 before returning the wrapper, and add 1 inside it.

Program Practice

Python decorators FAQ

What is a decorator in Python?

A decorator is a function that takes another function and returns a modified version of it, usually a wrapper that adds behaviour before or after the original runs.

What does functools.wraps do?

It copies the wrapped function’s name, docstring and other metadata onto the wrapper, so debugging and documentation tools see the original function.

Can a decorator take arguments?

Yes. Write a function that takes the arguments and returns the decorator, giving three levels: def retry(times): def decorator(func): def wrapper(...).

Python OS Module

Python Tutorial › Modules › OS Module

Python OS Module

IntermediateLesson 38 of 4712 min read8 runnable examples

The os module lets your program work with the operating system: create and list folders, build file paths that work on every OS, rename and delete files, and read environment variables. Its modern partner, pathlib, does the same jobs with path objects.

In this lesson
  • Create, list and remove folders
  • Build portable paths with os.path and pathlib
  • Walk a whole folder tree
  • Read environment variables

The current folder

Relative paths are resolved against the current working directory, the folder the program was started from.

Python
import os print(os.path.basename(os.getcwd()))print(os.name)        # 'nt' on Windows, 'posix' on macOS and Linux
Output
os
nt
Note

The examples on this page were run on Windows, so paths print with \. On macOS and Linux you will see / instead. The code is the same.

Create folders and files

Python
import os os.makedirs("reports/2026/nov", exist_ok=True)   # creates every missing levelwith open("reports/2026/nov/summary.txt", "w") as f:    f.write("all tests passed")with open("reports/readme.md", "w") as f:    f.write("# Reports") print(os.listdir("reports"))print(os.path.exists("reports/2026/nov/summary.txt"))
Output
['2026', 'readme.md']
True

exist_ok=True stops makedirs from raising an error when the folder already exists.

Check what a path is

Python
import os for p in ["reports", "reports/readme.md", "missing.txt"]:    print(p, os.path.exists(p), os.path.isdir(p), os.path.isfile(p)) print(os.path.getsize("reports/readme.md"), "bytes")
Output
reports True True False
reports/readme.md True False True
missing.txt False False False
9 bytes

Build and split paths

Never glue paths together with + "/". os.path.join uses the right separator for the OS:

Python
import os path = os.path.join("reports", "2026", "nov", "summary.txt")print(path)print(os.path.dirname(path))print(os.path.basename(path))print(os.path.splitext(path))
Output
reports\2026\nov\summary.txt
reports\2026\nov
summary.txt
('reports\\2026\\nov\\summary', '.txt')

Walk a folder tree

os.walk() visits every folder under a starting point and gives you its files:

Python
import os for folder, subfolders, files in os.walk("reports"):    level = folder.count(os.sep)    print("  " * level + os.path.basename(folder) + "/")    for name in files:        print("  " * (level + 1) + name)
Output
reports/
  readme.md
  2026/
    nov/
      summary.txt

Rename, move and delete

Python
import os, shutil os.rename("reports/readme.md", "reports/README.md")print(sorted(os.listdir("reports"))) os.remove("reports/README.md")             # delete a fileshutil.rmtree("reports/2026")              # delete a folder and everything in itprint(os.listdir("reports"))os.rmdir("reports")                        # delete an empty folderprint(os.path.exists("reports"))
Output
['2026', 'README.md']
[]
False
Watch out

os.remove and shutil.rmtree delete permanently; there is no recycle bin. Print the path first when you build it from variables.

Environment variables

Environment variables pass settings such as API keys and base URLs to a program without hard-coding them.

Python
import os os.environ["APP_ENV"] = "staging"           # set for this processprint(os.environ["APP_ENV"])print(os.environ.get("BASE_URL", "https://sqatools.in"))   # with a defaultprint("PATH" in os.environ)
Output
staging
Home
True
Tip

Read secrets with os.environ.get("API_TOKEN") instead of writing them in your code, so they never end up in Git.

pathlib: the modern way

pathlib.Path objects join with / and have methods for most file jobs:

Python
from pathlib import Path base = Path("data")(base / "logs").mkdir(parents=True, exist_ok=True)log = base / "logs" / "run.log"log.write_text("start\nend\n") print(log.name, log.suffix, log.stem)print(log.parent.name)print(log.read_text().splitlines())print([p.name for p in base.rglob("*.log")])
Output
run.log .log run
logs
['start', 'end']
['run.log']
Taskos / os.pathpathlib
Join pathsos.path.join(a, b)Path(a) / b
Existsos.path.exists(p)p.exists()
Make foldersos.makedirs(p, exist_ok=True)p.mkdir(parents=True, exist_ok=True)
Listos.listdir(p)p.iterdir()
Read textopen(p).read()p.read_text()
Extensionos.path.splitext(p)[1]p.suffix

Check your understanding

Quick quiz

1. Which function creates nested folders in one call?

2. Why use os.path.join instead of string concatenation?

3. How do you read an environment variable safely with a default?

Exercise: group files by extension

Create a few files in a folder, then count how many files there are of each extension.

Input
a.py, b.py, notes.txt, data.csv, c.py
Expected output
.py 3
.csv 1
.txt 1
Hint 1

os.path.splitext(name)[1] gives the extension; a Counter counts them.

Program Practice

Python os module FAQ

How do I get the current directory in Python?

os.getcwd() returns it as a string; Path.cwd() returns it as a Path object.

How do I check if a file exists in Python?

os.path.exists(path) or Path(path).exists(). Use isfile() / isdir() to check the type too.

What is the difference between os and pathlib?

os and os.path work with paths as strings. pathlib (Python 3.4+) wraps paths in objects with methods and the / operator. pathlib is usually more readable for new code.

How do I delete a folder that is not empty?

Use shutil.rmtree(path). os.rmdir only removes empty folders.

Python Read/Write CSV

Python Tutorial › File Handling › Read/Write CSV

Python Read/Write CSV

BeginnerLesson 43 of 4710 min read7 runnable examples

CSV (comma-separated values) files are plain-text tables that every spreadsheet and database can export. Python’s built-in csv module reads and writes them correctly, including the tricky cases such as commas inside quoted values.

In this lesson
  • Write rows with csv.writer
  • Read rows with csv.reader
  • Use column names with DictReader and DictWriter
  • Summarise CSV data

Write a CSV file

Open the file with newline="" (the csv module handles line endings itself), then write rows as lists.

Python
import csv rows = [    ["name", "city", "score"],    ["Asha", "Pune", 91],    ["Ravi", "Delhi", 78],    ["Meena", "Mumbai, MH", 85],]with open("students.csv", "w", newline="", encoding="utf-8") as f:    writer = csv.writer(f)    writer.writerows(rows) print(open("students.csv", encoding="utf-8").read())
Output
name,city,score
Asha,Pune,91
Ravi,Delhi,78
Meena,"Mumbai, MH",85

Notice that "Mumbai, MH" was wrapped in quotes automatically, because it contains a comma.

Watch out

Without newline="", files written on Windows get an extra blank line between rows.

Read a CSV file

csv.reader returns each row as a list of strings:

Python
import csv with open("students.csv", newline="", encoding="utf-8") as f:    reader = csv.reader(f)    header = next(reader)            # first row    print(header)    for row in reader:        print(row)
Output
['name', 'city', 'score']
['Asha', 'Pune', '91']
['Ravi', 'Delhi', '78']
['Meena', 'Mumbai, MH', '85']

DictReader: rows as dictionaries

DictReader uses the header row as keys, so you can write row["score"] instead of row[2]:

Python
import csv with open("students.csv", newline="", encoding="utf-8") as f:    for row in csv.DictReader(f):        print(row["name"], "scored", row["score"])
Output
Asha scored 91
Ravi scored 78
Meena scored 85

DictWriter

Python
import csv results = [    {"test": "login", "status": "pass", "ms": 812},    {"test": "search", "status": "fail", "ms": 1530},]with open("results.csv", "w", newline="", encoding="utf-8") as f:    writer = csv.DictWriter(f, fieldnames=["test", "status", "ms"])    writer.writeheader()    writer.writerows(results) print(open("results.csv", encoding="utf-8").read().strip())
Output
test,status,ms
login,pass,812
search,fail,1530

Append rows

Python
import csv with open("results.csv", "a", newline="", encoding="utf-8") as f:    csv.writer(f).writerow(["checkout", "pass", 990]) with open("results.csv", newline="", encoding="utf-8") as f:    print(sum(1 for _ in csv.DictReader(f)), "data rows")
Output
3 data rows

Summarise CSV data

Values come back as strings, so convert them before doing maths:

Python
import csv with open("students.csv", newline="", encoding="utf-8") as f:    students = list(csv.DictReader(f)) scores = [int(s["score"]) for s in students]top = max(students, key=lambda s: int(s["score"]))print("Average:", round(sum(scores) / len(scores), 2))print("Top:", top["name"], top["score"])print("Above 80:", [s["name"] for s in students if int(s["score"]) > 80])
Output
Average: 84.67
Top: Asha 91
Above 80: ['Asha', 'Meena']

Other delimiters

Some files use ; or tabs. Pass delimiter:

Python
import csv, io data = "id;product;price\n1;Pen;10\n2;Bag;650\n"for row in csv.DictReader(io.StringIO(data), delimiter=";"):    print(row)
Output
{'id': '1', 'product': 'Pen', 'price': '10'}
{'id': '2', 'product': 'Bag', 'price': '650'}

io.StringIO turns a string into a file-like object, which is handy for testing CSV code without a real file.

Tip

For large data or heavy analysis, Pandas reads CSV in one line: pd.read_csv("students.csv"). See the Pandas lesson.

Check your understanding

Predict the output

import csv, iorows = list(csv.reader(io.StringIO('a,"b,c",d\n1,2,3\n')))print(rows, len(rows[0]))

Quick quiz

1. What type are the values csv.reader returns?

2. Why open CSV files with newline=""?

3. Which class gives each row as a dictionary keyed by the header?

Exercise: filter rows into a new file

From students.csv, write the students who scored at least 80 to toppers.csv with the same header, then print the new file.

Input
students.csv from this lesson
Expected output
name,city,score
Asha,Pune,91
Meena,"Mumbai, MH",85
Hint 1

Use DictReader to read and DictWriter with reader.fieldnames to write.

Program Practice

Python CSV FAQ

How do I read a CSV file in Python?

with open("file.csv", newline="", encoding="utf-8") as f: then for row in csv.reader(f): or csv.DictReader(f) for dictionaries.

How do I skip the header row?

Call next(reader) once before the loop, or use DictReader, which consumes the header for you.

Why do I get blank lines between rows in my CSV on Windows?

The file was opened without newline="". Add it to open() when writing CSV files.

Should I use csv or pandas?

Use csv for simple reading and writing with no extra installs. Use pandas when you need filtering, grouping, statistics or Excel export.

Python Read/Write JSON

Python Tutorial › File Handling › Read/Write JSON

Python Read/Write JSON

BeginnerLesson 42 of 4710 min read8 runnable examples

JSON is the format APIs, config files and test data use most. Python’s built-in json module converts JSON text into dictionaries and lists, and back again, in one call.

In this lesson
  • Parse JSON strings with json.loads
  • Create JSON with json.dumps
  • Read and write JSON files
  • Handle nested data and errors

JSON and Python types

JSONPython
object {}dict
array []list
string "..."str
numberint or float
true / falseTrue / False
nullNone

Parse a JSON string: loads

Python
import json text = '{"id": 7, "name": "Asha", "active": true, "skills": ["python", "sql"], "manager": null}'user = json.loads(text) print(type(user).__name__)print(user["name"], user["skills"][0], user["active"], user["manager"])
Output
dict
Asha python True None

Create a JSON string: dumps

Python
import json report = {"suite": "login", "passed": 12, "failed": 0, "ok": True, "notes": None}print(json.dumps(report))print(json.dumps(report, indent=2, sort_keys=True))
Output
{"suite": "login", "passed": 12, "failed": 0, "ok": true, "notes": null}
{
  "failed": 0,
  "notes": null,
  "ok": true,
  "passed": 12,
  "suite": "login"
}
Note

ensure_ascii=False keeps non-English characters readable instead of \u escapes: json.dumps({"city": "Pune"}, ensure_ascii=False).

Write a JSON file: dump

dump (without the s) writes straight to a file:

Python
import json config = {    "base_url": "https://sqatools.in",    "browsers": ["chromium", "firefox"],    "timeouts": {"page": 30, "api": 10},}with open("config.json", "w", encoding="utf-8") as f:    json.dump(config, f, indent=2) print(open("config.json", encoding="utf-8").read())
Output
{
  "base_url": "https://sqatools.in",
  "browsers": [
    "chromium",
    "firefox"
  ],
  "timeouts": {
    "page": 30,
    "api": 10
  }
}

Read a JSON file: load

Python
import json with open("config.json", encoding="utf-8") as f:    config = json.load(f) print(config["timeouts"]["api"])for browser in config["browsers"]:    print("run on", browser)
Output
10
run on chromium
run on firefox
Tip

Remember the pairs: loads / dumps work with strings, load / dump work with files.

Work with nested API data

Python
import json response = json.loads("""{"page": 1, "data": [    {"id": 1, "email": "asha@example.com", "orders": [120, 80]},    {"id": 2, "email": "ravi@example.com", "orders": []},    {"id": 3, "email": "meena@example.com", "orders": [300]}]}""") for user in response["data"]:    total = sum(user["orders"])    print(f'{user["id"]} {user["email"]:<20} {total}') print([u["email"] for u in response["data"] if u["orders"]])print(response.get("next_page", "no more pages"))
Output
1 asha@example.com     200
2 ravi@example.com     0
3 meena@example.com    300
['asha@example.com', 'meena@example.com']
no more pages

Update a JSON file

Read, change, write back:

Python
import json with open("config.json", encoding="utf-8") as f:    config = json.load(f) config["browsers"].append("webkit")config["timeouts"]["page"] = 45 with open("config.json", "w", encoding="utf-8") as f:    json.dump(config, f, indent=2) print(json.load(open("config.json", encoding="utf-8"))["browsers"])
Output
['chromium', 'firefox', 'webkit']

Invalid JSON

JSON is stricter than Python: keys need double quotes and trailing commas are not allowed.

Python
import json for text in ['{"ok": true}', "{'ok': true}", '{"a": 1,}']:    try:        print(json.loads(text))    except json.JSONDecodeError as e:        print("invalid:", e.msg, "at position", e.pos)
Output
{'ok': True}
invalid: Expecting property name enclosed in double quotes at position 1
invalid: Illegal trailing comma before end of object at position 7

Custom objects

json.dumps only knows the basic types. Convert other objects first, for example with default=:

Python
import jsonfrom datetime import date order = {"id": 99, "placed": date(2026, 11, 2), "tags": {"gift"}} def to_json(value):    if isinstance(value, date):        return value.isoformat()    if isinstance(value, set):        return sorted(value)    raise TypeError(f"cannot serialise {type(value).__name__}") print(json.dumps(order, default=to_json))
Output
{"id": 99, "placed": "2026-11-02", "tags": ["gift"]}

Check your understanding

Predict the output

import jsondata = json.loads('[1, "two", null, true, {"k": 3.5}]')print(data, json.dumps(data))

Quick quiz

1. Which function parses a JSON string into Python objects?

2. What does JSON null become in Python?

3. Which makes the output easy to read with line breaks?

Exercise: summarise test results

Parse the JSON results, print the pass percentage and the names of failed tests.

Input
'[{"name": "login", "status": "pass"}, {"name": "search", "status": "fail"}, {"name": "cart", "status": "pass"}, {"name": "pay", "status": "fail"}]'
Expected output
Pass rate: 50%
Failed: search, pay
Hint 1

Count with a comprehension: [r for r in results if r["status"] == "pass"].

Program Practice

Python JSON FAQ

How do I read a JSON file in Python?

with open("data.json", encoding="utf-8") as f: data = json.load(f).

What is the difference between json.load and json.loads?

json.load reads from a file object; json.loads parses a string.

How do I pretty print JSON in Python?

print(json.dumps(data, indent=2)). Add sort_keys=True to sort the keys.

Why does json.dumps fail with “Object of type datetime is not JSON serializable”?

JSON has no date type. Convert dates to strings (isoformat()) or pass a default= function to dumps.

Python Read/Write Excel

Python Tutorial › File Handling › Read/Write Excel

Python Read/Write Excel

IntermediateLesson 44 of 4711 min read7 runnable examples

Excel workbooks hold a lot of business and test data. The openpyxl library reads and writes .xlsx files cell by cell, and pandas can load a whole sheet into a table in one line.

In this lesson
  • Create a workbook and write rows
  • Read cells, rows and ranges
  • Add formulas, styles and column widths
  • Load sheets with pandas

Install openpyxl

openpyxl is not part of the standard library. Install it with pip:

Terminal
python -m pip install openpyxl
Note

The Try it button runs Python in your browser, where openpyxl may not be available. Run these examples on your computer after installing it.

Create a workbook and write data

Python
from openpyxl import Workbook wb = Workbook()ws = wb.activews.title = "Results" ws.append(["Test", "Status", "Duration (s)"])ws.append(["login", "pass", 1.8])ws.append(["search", "fail", 3.2])ws.append(["checkout", "pass", 2.4])ws["E1"] = "Generated by Python" wb.save("results.xlsx")print("saved", ws.max_row, "rows and", ws.max_column, "columns")
Output
saved 4 rows and 5 columns

Read a workbook

Python
from openpyxl import load_workbook wb = load_workbook("results.xlsx")print(wb.sheetnames)ws = wb["Results"] print(ws["A2"].value, ws.cell(row=2, column=2).value)for row in ws.iter_rows(min_row=2, max_col=3, values_only=True):    print(row)
Output
['Results']
login pass
('login', 'pass', 1.8)
('search', 'fail', 3.2)
('checkout', 'pass', 2.4)

values_only=True returns plain values instead of cell objects, which is what you want most of the time.

Read rows as dictionaries

Python
from openpyxl import load_workbook ws = load_workbook("results.xlsx")["Results"]rows = ws.iter_rows(max_col=3, values_only=True)header = next(rows)records = [dict(zip(header, r)) for r in rows] print(records[1])print([r["Test"] for r in records if r["Status"] == "fail"])
Output
{'Test': 'search', 'Status': 'fail', 'Duration (s)': 3.2}
['search']

Update cells and add formulas

Python
from openpyxl import load_workbook wb = load_workbook("results.xlsx")ws = wb["Results"] ws["B3"] = "pass"                         # fix a valuews["C6"] = "=SUM(C2:C4)"                  # a formula, calculated when Excel opens itws["B6"] = "Total"wb.save("results.xlsx") print(load_workbook("results.xlsx")["Results"]["C6"].value)
Output
=SUM(C2:C4)
Note

openpyxl stores formulas but does not calculate them. Excel (or LibreOffice) calculates the result when it opens the file. Load with data_only=True to read the last values Excel saved.

Styles and column widths

Python
from openpyxl import load_workbookfrom openpyxl.styles import Font, PatternFill wb = load_workbook("results.xlsx")ws = wb["Results"] for cell in ws[1]:    cell.font = Font(bold=True, color="FFFFFF")    cell.fill = PatternFill("solid", fgColor="0B7F62")ws.column_dimensions["A"].width = 14 for row in ws.iter_rows(min_row=2, max_row=4):    if row[1].value == "fail":        row[1].font = Font(color="C23A3A", bold=True) wb.save("results.xlsx")print(ws["A1"].font.bold, ws.column_dimensions["A"].width)
Output
True 14.0

Several sheets

Python
from openpyxl import load_workbook wb = load_workbook("results.xlsx")summary = wb.create_sheet("Summary")summary.append(["Total tests", 3])wb.save("results.xlsx")print(load_workbook("results.xlsx").sheetnames)
Output
['Results', 'Summary']

Excel with pandas

pandas uses openpyxl under the hood and loads a whole sheet into a DataFrame:

Python
import pandas as pd df = pd.read_excel("results.xlsx", sheet_name="Results", usecols="A:C", nrows=3)print(df)print(df["Duration (s)"].mean()) df.to_excel("copy.xlsx", index=False)print(pd.read_excel("copy.xlsx").shape)
Output
       Test Status  Duration (s)
0     login   pass           1.8
1    search   pass           3.2
2  checkout   pass           2.4
2.466666666666667
(3, 3)

Check your understanding

Quick quiz

1. Which method adds a row after the last used row?

2. What does values_only=True do in iter_rows?

3. Does openpyxl calculate formulas?

Exercise: pass rate per status

Read results.xlsx and print how many tests have each status.

Input
the Results sheet created in this lesson
Expected output
pass 3
Hint 1

Loop over iter_rows(min_row=2, max_row=4, values_only=True) and count the second value.

Program Practice

Python Excel FAQ

How do I read an Excel file in Python?

Use openpyxl: load_workbook("file.xlsx")["Sheet1"], then iter_rows(values_only=True). Or use pandas: pd.read_excel("file.xlsx").

Can openpyxl read .xls files?

No. openpyxl supports .xlsx, .xlsm, .xltx and .xltm. Convert old .xls files to .xlsx first.

How do I write data to a specific cell?

Assign to it: ws["B2"] = "pass" or ws.cell(row=2, column=2, value="pass"), then wb.save().

Python Tuples

Python Tutorial › Data Structures › Tuples

Python Tuples

BeginnerLesson 20 of 479 min read11 runnable examples

A tuple is an ordered collection that cannot be changed after it is created. Use tuples for fixed groups of values, such as coordinates, RGB colours or a database row, and to return several values from a function.

In this lesson
  • Create tuples, including one-item tuples
  • Access items by index and slice
  • Unpack a tuple into variables
  • Know when a tuple beats a list

Create a tuple

Write items separated by commas, usually inside round brackets.

Python
point = (12.5, 77.3)rgb = (255, 128, 0)mixed = ("Asha", 29, True)empty = () print(point, type(point).__name__)print(len(mixed), len(empty))
Output
(12.5, 77.3) tuple
3 0

The commas make the tuple, not the brackets. You can even leave the brackets out:

Python
version = 3, 12, 1print(version)
Output
(3, 12, 1)

The one-item trap

A single value in brackets is just that value. Add a trailing comma to make a one-item tuple:

Python
not_a_tuple = (5)one_item = (5,)print(type(not_a_tuple).__name__, type(one_item).__name__)
Output
int tuple

Access items

Indexing and slicing work exactly as with lists and strings, and a slice of a tuple is a new tuple.

Python
days = ("Mon", "Tue", "Wed", "Thu", "Fri")print(days[0], days[-1])print(days[1:4])print("Wed" in days) for i, day in enumerate(days, 1):    print(i, day, end="  ")
Output
Mon Fri
('Tue', 'Wed', 'Thu')
True
1 Mon  2 Tue  3 Wed  4 Thu  5 Fri  

Tuples are immutable

You cannot add, remove or replace items. Trying to raises a TypeError:

Python
rgb = (255, 128, 0)rgb[0] = 200
Output
Traceback (most recent call last):
  File "main.py", line 2, in <module>
    rgb[0] = 200
    ~~~^^^
TypeError: 'tuple' object does not support item assignment

To “change” a tuple, build a new one:

Python
rgb = (255, 128, 0)rgb = (200,) + rgb[1:]print(rgb)print(rgb * 2)
Output
(200, 128, 0)
(200, 128, 0, 200, 128, 0)
Note

A tuple can contain a mutable object such as a list, and that list can still change. The tuple itself still holds the same objects, so it is called “shallowly” immutable.

Unpacking

Assign a tuple’s items to several names in one step. This is used everywhere in Python.

Python
name, age, city = ("Ravi", 31, "Pune")print(name, age, city) first, *middle, last = (1, 2, 3, 4, 5)print(first, middle, last) def min_max(values):    return min(values), max(values)      # returns a tuple low, high = min_max([7, 2, 9, 4])print(low, high)
Output
Ravi 31 Pune
1 [2, 3, 4] 5
2 9

Tuple methods

Because tuples cannot change, they have only two methods:

Python
marks = (90, 75, 90, 60, 90)print(marks.count(90))print(marks.index(75))print(sorted(marks), min(marks), max(marks), sum(marks))
Output
3
1
[60, 75, 90, 90, 90] 60 90 405

Tuples as dictionary keys

Tuples can be dictionary keys and set members because they are immutable (and hashable). Lists cannot.

Python
distances = {("Pune", "Mumbai"): 150, ("Pune", "Nashik"): 210}print(distances[("Pune", "Mumbai")])
Output
150
Python
bad = {["Pune", "Mumbai"]: 150}
Output
Traceback (most recent call last):
  File "main.py", line 1, in <module>
    bad = {["Pune", "Mumbai"]: 150}
          ^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: cannot use 'list' as a dict key (unhashable type: 'list')

Named tuples

collections.namedtuple gives each position a name, so code reads p.x instead of p[0]:

Python
from collections import namedtuple Point = namedtuple("Point", ["x", "y"])p = Point(3, 4)print(p)print(p.x, p.y, p[0])
Output
Point(x=3, y=4)
3 4 3

Check your understanding

Predict the output

t = (1, [2, 3], 4)t[1].append(99)print(t, len(t))

Quick quiz

1. Which of these is a tuple with one item?

2. How many methods does a tuple have?

3. Can a tuple be a dictionary key?

Exercise: student records

Each record is a (name, marks) tuple. Print the topper and the class average, using unpacking in the loop.

Input
[("Asha", 88), ("Ravi", 74), ("Meena", 93)]
Expected output
Topper: Meena 93
Average: 85.0
Hint 1

max(records, key=lambda r: r[1]) finds the record with the highest marks.

Program Practice

Python tuples FAQ

What is the difference between a list and a tuple in Python?

A list is mutable and written with [ ]; a tuple is immutable and written with ( ). See List vs Tuple for a full comparison.

How do I create a tuple with one element?

Add a trailing comma: t = (5,) or just t = 5,.

Can I add an element to a tuple?

Not in place. Create a new tuple: t = t + (new_item,).

How do I convert a list to a tuple?

Use tuple(my_list). Use list(my_tuple) to go the other way.

Python String Methods

  1. len(): Returns the length of a string. Here’s an example:
# Using len() to get the length of a string
my_string = "Python Programming"

# Get the length of the string
length = len(my_string)

print("The length of the string is:", length) # Output: 18
  1. lower(): Converts all characters in a string to lowercase. Here’s an example:
# Using lower() to convert string to lowercase
my_string = "Python Programming"

# Convert the string to lowercase
lowercase_string = my_string.lower()

print("Original string:", my_string) # Output: Python Programming
print("Lowercase string:", lowercase_string) # Output: python programming
  1. upper(): Converts all characters in a string to uppercase. Here’s an example:
# Using upper() to convert string to uppercase
my_string = "Python Programming"

# Convert the string to uppercase
uppercase_string = my_string.upper()

print("Original string:", my_string) # Output: Python Programming
print("Uppercase string:", uppercase_string) # Output: PYTHON PROGRAMMING
  1. title(): Capitalizes the first character of each word in a string. Here’s an example:
# Using title() to capitalize the first character of each word
my_string = "python programming is fun"

# Convert the string to title case
title_string = my_string.title()

print("Original string:", my_string) # Output: python programming is fun
print("Title case string:", title_string) # Output: Python Programming Is Fun
  1. capitalize(): Capitalizes the first character of a string. Here’s an example:
# Using capitalize() to capitalize the first character of a string
my_string = "python programming"

# Capitalize the first character
capitalized_string = my_string.capitalize()

print("Original string:", my_string) # Output: python programming
print("Capitalized string:", capitalized_string) # Output: Python programming
  1. swapcase(): Swaps the case of all characters in a string. Here’s an example:
# Using swapcase() to swap the case of all characters in a string
my_string = "Python Programming"

# Swap the case of all characters
swapped_string = my_string.swapcase()

print("Original string:", my_string) # Output: Python Programming
print("Swapped case string:", swapped_string) # Output: pYTHON pROGRAMMING
  1. count(): Returns the number of occurrences of a substring in a string. Here’s an example:
# Using count() to count occurrences of a substring
my_string = "Python programming is fun. Python is easy to learn."

# Count the occurrences of the word "Python"
count_python = my_string.count("Python")

print("Occurrences of 'Python':", count_python) # Output: 2
  1. find(): Returns the index of the first occurrence of a substring in a string. Here’s an example:
# Using find() to find the index of the first occurrence of a substring
my_string = "Python programming is fun."

# Find the index of the first occurrence of "Python"
index_python = my_string.find("Python")

print("Index of 'Python':", index_python) # Output: 0
  1. rfind(): Returns the index of the last occurrence of a substring in a string.
# Using rfind() to find the index of the last occurrence of a substring
my_string = "Python programming is fun. Python is easy to learn."

# Find the index of the last occurrence of "Python"
last_index_python = my_string.rfind("Python")

print("Index of the last 'Python':", last_index_python) # Output: 36

       10. index(): Like `find()`, but raises a `ValueError` if the substring is not found.

# Using index() to find the index of the first occurrence of a substring
my_string = "Python programming is fun."

# Find the index of the first occurrence of "Python"
index_python = my_string.index("Python")

print("Index of 'Python':", index_python) # Output: 0
  1. rindex(): Like `rfind()`, but raises a `ValueError` if the substring is not found.
# Using rindex() to find the index of the last occurrence of a substring
my_string = "Python programming is fun. Python is easy to learn."

# Find the index of the last occurrence of "Python"
last_index_python = my_string.rindex("Python")

print("Index of the last 'Python':", last_index_python) # Output: 36
  1. startswith(): Returns `True` if a string starts with a specified prefix, otherwise `False`.
# Using startswith() to check if the string starts with a specified prefix
my_string = "Python programming is fun."

# Check if the string starts with "Python"
starts_with_python = my_string.startswith("Python")

print("Starts with 'Python':", starts_with_python) # Output: True
  1. endswith(): Returns `True` if a string ends with a specified suffix, otherwise `False`.
# Using endswith() to check if the string ends with a specified suffix
my_string = "Python programming is fun."

# Check if the string ends with "fun."
ends_with_fun = my_string.endswith("fun.")

print("Ends with 'fun.':", ends_with_fun) # Output: True
  1. replace(): Replaces all occurrences of a substring with another substring.
# Using replace() to replace all occurrences of a substring
my_string = "Python is fun. Python is awesome."

# Replace "Python" with "Java"
new_string = my_string.replace("Python", "Java")

print("Original string:", my_string)
print("New string:", new_string)

       15. strip(): Removes whitespace (or other characters) from the beginning and end of a string.

  1. rstrip(): Removes whitespace (or other characters) from the end of a string.
# Using rstrip() to remove trailing whitespace
my_string = "Hello, world! "

# Remove the trailing whitespace
new_string = my_string.rstrip()

print("Original string:", repr(my_string))
print("New string:", repr(new_string))
  1. lstrip(): Removes whitespace (or other characters) from the beginning of a string.
# Using lstrip() to remove leading whitespace
my_string = " Hello, world!"

# Remove the leading whitespace
new_string = my_string.lstrip()

print("Original string:", repr(my_string))
print("New string:", repr(new_string))
  1. split(): Splits a string into a list of substrings using a specified delimiter.
# Using split() to split a string into a list of words
my_string = "Python is fun!"

# Split the string by whitespace (default behavior)
words = my_string.split()

print("List of words:", words)
  1. rsplit(): Splits a string from the right into a list of substrings using a specified delimiter.
# Using rsplit() to split a string from the right
my_string = "Python is fun and Python is awesome"

# Split the string by whitespace from the right
words = my_string.rsplit()

print("List of words:", words)

The separator used is the comma, which is passed as an argument to the rsplit() method. The 1 argument is used to specify that only one splitting should occur, which in this case splits the last fruit from the first two. Finally, we print the list of fruits.

       20. join(): Joins a list of strings into a single string using a specified delimiter.

# Joining with a space
words = ["Hello", "world", "Python", "rocks!"]
sentence = " ".join(words)
print(sentence) # Output: "Hello world Python rocks!"

# Joining with a comma
items = ["apple", "banana", "cherry"]
csv_line = ",".join(items)
print(csv_line) # Output: "apple,banana,cherry"

# Joining with a hyphen
letters = ["a", "b", "c", "d"]
hyphenated = "-".join(letters)
print(hyphenated) # Output: "a-b-c-d"
  1. isalnum(): Returns `True` if a string contains only alphanumeric characters, otherwise `False`.
# String with only letters and numbers
print("Hello123".isalnum()) # Output: True

# String with only letters
print("Python".isalnum()) # Output: True

# String with only numbers
print("2024".isalnum()) # Output: True

# String with a space
print("Hello World".isalnum()) # Output: False

# String with special characters
print("Hello@123".isalnum()) # Output: False

# Empty string
print("".isalnum()) # Output: False
  1. isalpha(): Returns `True` if a string contains only alphabetic characters, otherwise `False`.
# String with only letters
print("Python".isalpha()) # Output: True

# String with a space
print("Hello World".isalpha()) # Output: False

# String with numbers
print("Python3".isalpha()) # Output: False

# String with special characters
print("Hello!".isalpha()) # Output: False

# Empty string
print("".isalpha()) # Output: False
  1. isdigit() : Returns `True` if a string contains only digits; otherwise, `False`.
# String with only digits
print("12345".isdigit()) # Output: True

# String with letters
print("123abc".isdigit()) # Output: False

# String with a space
print("123 456".isdigit()) # Output: False

# Empty string
print("".isdigit()) # Output: False
  1. islower(): Returns `True` if all characters in a string are lowercase, otherwise `False`.
# All lowercase letters
print("hello".islower()) # Output: True

# Mixed case
print("Hello".islower()) # Output: False

# Digits and special characters are ignored
print("hello123!".islower()) # Output: True

# Contains an uppercase letter
print("helloWorld".islower()) # Output: False

# Empty string
print("".islower()) # Output: False

      25. isupper(): Returns `True` if all characters in a string are uppercase, otherwise `False`.

# All uppercase letters
print("HELLO".isupper()) # Output: True

# Mixed case
print("Hello".isupper()) # Output: False

# Includes digits and symbols
print("HELLO123!".isupper()) # Output: True

# Contains a lowercase letter
print("HELLOworld".isupper()) # Output: False

# Empty string
print("".isupper()) # Output: False
  1. istitle(): Returns `True` if a string is titlecased (i.e., the first character of each word is capitalized), otherwise `False`.
# Title-cased string
print("Hello World".istitle()) # Output: True

# Mixed case
print("Hello world".istitle()) # Output: False

# Uppercase string
print("HELLO WORLD".istitle()) # Output: False

# Lowercase string
print("hello world".istitle()) # Output: False

# Single title-cased word
print("Python".istitle()) # Output: True
  1. isspace(): Returns `True` if a string contains only whitespace characters, otherwise `False`.
# Only spaces
print(" ".isspace()) # Output: True

# Only tabs
print("\t\t".isspace()) # Output: True

# Newline characters
print("\n\n".isspace()) # Output: True

# Mixed whitespace characters
print(" \t\n".isspace()) # Output: True

# Empty string
print("".isspace()) # Output: False
  1. maketrans(): Creates a translation table to be used with the `translate()` function.
# Mapping characters 'a' -> '1', 'b' -> '2', 'c' -> '3'
trans_table = str.maketrans("abc", "123")
text = "abcde"
print(text.translate(trans_table)) # Output: "123de"
  1. translate(): Returns a copy of a string with specified characters replaced.
# Create a translation table to replace 'a' -> '1', 'b' -> '2', 'c' -> '3'
trans_table = str.maketrans("abc", "123")
text = "abcde"
print(text.translate(trans_table)) # Output: "123de"
  1. zfill(): Pads a numeric string with zeros on the left until the specified width is reached.
# Example using zfill() to pad a string with leading zeros
number = "42"
padded_number = number.zfill(5)

print(padded_number) # Output: '00042'
  1. expandtabs(): Replaces tabs in a string with spaces.
# Example using expandtabs() to replace tabs with spaces
text = "Hello\tWorld\tPython"
expanded_text = text.expandtabs(4) # Replaces tabs with 4 spaces

print(expanded_text)

# Output:
'Hello World Python'
  1. encode(): Encodes a string using a specified encoding.
# Encoding a string using UTF-8
my_string = "hello world"
encoded_string = my_string.encode("utf-8")

print(encoded_string) # Output: b'hello world'

  1. format_map(): Formats a string using a dictionary.
# Using format_map() to format a string using a dictionary
person = {"name": "Alice", "age": 30}

# Format string using the dictionary
formatted_string = "My name is {name} and I am {age} years old.".format_map(person)

print(formatted_string)
  1. isdecimal(): Returns `True` if a string contains only decimal characters, otherwise `False`.
# Using isdecimal() to check if a string contains only decimal characters
my_string = "12345"

# Check if the string contains only decimal characters
is_decimal = my_string.isdecimal()

print(is_decimal) # Output: True
  1. isnumeric(): Returns `True` if a string contains only numeric characters, otherwise `False`.
# Using isnumeric() to check if a string contains only numeric characters
my_string = "12345"

# Check if the string is numeric
is_numeric = my_string.isnumeric()

print(is_numeric) # Output: True
  1. partition(): It splits the string into parts based on the first occurrence of the substring.
# Using partition() to split a string based on the first occurrence of a substring
my_string = "Hello, world! Welcome to Python."

# Split the string at the first occurrence of ","
result = my_string.partition(",")

print(result)

Python Strings & Methods

Python Tutorial › Strings › Strings & Methods

Python Strings & Methods

BeginnerLesson 16 of 4714 min read11 runnable examples

A string is a piece of text: a name, a URL, a log line, a whole file. Python strings come with dozens of built-in methods, and knowing the common ones will save you from writing loops for everyday text work.

In this lesson
  • Create strings with any quote style
  • Index and slice text
  • Use the most common string methods
  • Split text into parts and join it back

Create a string

Use single or double quotes; they mean the same thing. Pick the one that avoids escaping quotes inside the text. Triple quotes allow several lines.

Python
a = 'single quotes'b = "double quotes, with an 'apostrophe' inside"c = """Line oneLine two"""print(a)print(b)print(c)print(len(c))
Output
single quotes
double quotes, with an 'apostrophe' inside
Line one
Line two
17

A raw string, written r"...", ignores backslash escapes. It is handy for Windows paths and regular expressions:

Python
print("C:\new\table")      # \n and \t become a newline and a tabprint(r"C:\new\table")     # printed exactly as written
Output
C:
ew	able
C:\new\table

Index and slice

Strings are sequences of characters, so they support the same indexing and slicing as lists.

Python
text = "PYTHON"print(text[0], text[-1])print(text[0:2], text[2:], text[:-2])print(text[::2])       # every second characterprint(text[::-1])      # reversed
Output
P N
PY THON PYTH
PTO
NOHTYP

Strings are immutable

You cannot change a character in place. Methods return a new string and leave the original alone.

Python
word = "python"upper_word = word.upper()print(word, upper_word)word[0] = "P"
Output
python PYTHON
Traceback (most recent call last):
  File "main.py", line 4, in <module>
    word[0] = "P"
    ~~~~^^^
TypeError: 'str' object does not support item assignment

To “change” a string, build a new one and assign it: word = "P" + word[1:].

Change case

Python
title = "pYthon tUTORIAL"print(title.upper())print(title.lower())print(title.title())print(title.capitalize())print(title.swapcase())
Output
PYTHON TUTORIAL
python tutorial
Python Tutorial
Python tutorial
PyTHON Tutorial
Python
log = "ERROR 2026-11-02 login failed for user asha"print("failed" in log)print(log.startswith("ERROR"), log.endswith(".py"))print(log.find("login"))        # index of the first matchprint(log.find("logout"))       # -1 when not foundprint(log.count("a"))
Output
True
True False
17
-1
3
Tip

find() returns -1 when the text is missing, while index() raises ValueError. Use in when you only need yes or no.

Clean up text

Python
raw = "   Pune, Maharashtra \n"print(repr(raw.strip()))print(repr(raw.lstrip()), repr(raw.rstrip()))print("--report--".strip("-"))print("2026-11-02".replace("-", "/"))print("aaa".replace("a", "b", 2))     # only the first 2
Output
'Pune, Maharashtra'
'Pune, Maharashtra \n' '   Pune, Maharashtra'
report
2026/11/02
bba

Split and join

split() breaks a string into a list. join() does the opposite: it glues a list of strings together with the string you call it on.

Python
csv_line = "asha,tester,pune"fields = csv_line.split(",")print(fields) sentence = "  learn   python   step by step "words = sentence.split()        # no argument: split on any run of whitespaceprint(words) print(" ".join(words))print("-".join(["2026", "11", "02"]))print(list("abc"))
Output
['asha', 'tester', 'pune']
['learn', 'python', 'step', 'by', 'step']
learn python step by step
2026-11-02
['a', 'b', 'c']

Check what a string contains

Python
for s in ["2026", "python", "Py3", "  ", "3.5"]:    print(repr(s).ljust(9), s.isdigit(), s.isalpha(), s.isalnum(), s.isspace())
Output
'2026'    True False True False
'python'  False True True False
'Py3'     False False True False
'  '      False False False True
'3.5'     False False False False

The columns are isdigit(), isalpha(), isalnum() and isspace(). Note that "3.5".isdigit() is False because . is not a digit.

Alignment and padding

Python
for name, score in [("Asha", 91), ("Ravi", 7), ("Meenakshi", 100)]:    print(name.ljust(10), str(score).rjust(4), str(score).zfill(5))print("menu".center(20, "*"))
Output
Asha         91 00091
Ravi          7 00007
Meenakshi   100 00100
********menu********

Common string methods

MethodReturns
upper(), lower(), title()Text in a different case
strip(), lstrip(), rstrip()Text without surrounding whitespace (or given characters)
replace(old, new)Text with every old replaced
split(sep)A list of parts
sep.join(items)One string from a list of strings
find(sub) / index(sub)Position of sub (-1 / error if missing)
count(sub)How often sub appears
startswith(x) / endswith(x)True or False
isdigit(), isalpha(), isalnum()True or False
ljust(), rjust(), center(), zfill()Padded text

Loop over a string

Python
word = "level"vowels = 0for ch in word:    if ch in "aeiou":        vowels += 1print("vowels:", vowels)print("palindrome:", word == word[::-1])
Output
vowels: 2
palindrome: True

Check your understanding

Predict the output

s = "Hello, World"print(s[7:], s.lower().count("l"), s.split(", "))

Quick quiz

1. What does "python"[1:4] return?

2. What does "a-b-c".split("-") return?

3. Why does name[0] = "J" fail?

4. Which returns -1 when the text is not found?

Exercise: initials and username

From a full name, print the initials in upper case and a username made of the lower-case first name and the length of the full name.

Input
"  meena  ravi  kumar "
Expected output
MRK
meena16
Hint 1

split() without arguments handles the extra spaces.

Hint 2

"".join(...) can build the initials from a list.

Program Practice

Python strings FAQ

How do I reverse a string in Python?

Use slicing with a step of -1: text[::-1]. "".join(reversed(text)) also works.

How do I check if a string contains a substring?

Use in: "py" in "python" is True. Use find() when you also need the position.

Are Python strings mutable?

No. Every method returns a new string. To change a variable, assign the new string to it: name = name.strip().

What is the difference between split() and split(” “)?

split() splits on any run of whitespace and ignores leading and trailing spaces. split(" ") splits on every single space, so repeated spaces give empty strings.

Python String Programs with Solutions

Python Practice › String Programs

Python String Programs with Solutions

106 programs101 easy5 medium0 hard

106 string programs that cover slicing, searching, counting, replacing, splitting and joining text. They are among the most common Python interview questions, and each one has an explained solution with real output.

Before you start

Read the Strings & Methods and String Formatting & f-strings lesson first if the topic is new to you.

All programs