00:00

Python Tutorial

Last updated: 11 min read

Python is a high-level, general-purpose programming language known for clean, readable code and a huge ecosystem of libraries. It is used for web back ends, automation and scripting, data analysis, machine learning and AI, scientific computing, DevOps tooling and teaching programming. Its motto could be "batteries included": a large standard library lets you do useful work with very little code.

This tutorial teaches Python 3 from the ground up: installing Python, variables and data types, strings, collections, control flow, functions, comprehensions, modules and packages, virtual environments, files, exceptions, classes and dataclasses, iterators and generators, decorators, type hints, concurrency and testing. Every section includes short programs you can run straight away.

1. What Is Python?

Python is an interpreted, dynamically typed and multi-paradigm language. You can write simple scripts from top to bottom, organise larger programs with classes, or use a functional style with functions passed as values. Code is grouped by indentation instead of braces, which makes well-formatted code the only kind that works.

Python's popularity comes from three things working together:

  • Readability: Python code often reads almost like plain English, so it is easy to learn, review and maintain.
  • Productivity: high-level data structures, automatic memory management and a vast standard library mean less code for the same result.
  • Ecosystem: hundreds of thousands of packages on the Python Package Index (PyPI) cover web frameworks (Django, Flask, FastAPI), data work (NumPy, pandas), machine learning (scikit-learn, PyTorch), automation and much more.

Python is used by beginners writing their first program and by large companies running services used by millions of people. It is also the main language of data science and AI.

2. A Short History of Python

Python was created by Guido van Rossum, who started work on it in the late 1980s at the CWI research institute in the Netherlands. He named it after the British comedy group Monty Python, not the snake. The language is now developed by a global community and stewarded by the non-profit Python Software Foundation (PSF).

YearMilestone
1991The first public release (version 0.9.0).
2000Python 2.0 added list comprehensions and cycle-detecting garbage collection.
2008Python 3.0, a cleaned-up, backward-incompatible version of the language, with Unicode text strings by default.
2020Python 2 reached end of life on 1 January; Python 3 is now the only supported version.
2021Python 3.10 added structural pattern matching (match/case).
2022Python 3.11 brought major speed improvements to the interpreter.
2024Python 3.13 introduced an improved interactive shell and an experimental free-threaded build without the GIL.
2025Python 3.14 made the free-threaded build officially supported and added template strings (t-strings).

A new Python version is released every October, and each version receives bug and security fixes for five years.

3. Key Features of Python

FeatureWhat it means for you
Simple, readable syntaxFewer symbols and enforced indentation make code easy to read and write.
Dynamic typingVariables do not need type declarations; types are checked while the program runs.
Automatic memory managementReference counting plus a garbage collector free memory for you.
Interactive shellTry code line by line in the REPL, ideal for learning and quick experiments.
Batteries includedA rich standard library for files, JSON, HTTP, dates, maths, testing and more.
Huge ecosystemPackages for almost any task, installed with one pip command.
Cross-platformThe same code runs on Windows, macOS, Linux and many other systems.
Easy integrationPython can call fast C, C++ and Rust code, which is how libraries like NumPy are so fast.

4. How Python Runs Your Code

Python is usually called an interpreted language, but the reference implementation, CPython, actually works in two steps. First it compiles your source code into bytecode, a compact set of instructions. Then the Python Virtual Machine executes that bytecode. Imported modules have their bytecode cached in __pycache__ folders as .pyc files, so they load faster next time.

How CPython runs a program Source code app.py is compiled to bytecode, cached as a .pyc file for imported modules, and executed by the Python Virtual Machine, which uses the standard library and installed packages to produce output. app.pysource code Compilerparse + compile Bytecode.pyc cache Python VMexecutes standard library + installed packages
Figure 1: CPython compiles source code to bytecode and runs it on a virtual machine, much like Java, but all in one step when you run a script.

Other implementations exist too. PyPy uses a just-in-time compiler to run many programs faster, and MicroPython runs a small subset of Python on microcontrollers.

5. Installing Python and Your First Program

  • Windows and macOS: download the installer from python.org (on Windows, tick "Add python.exe to PATH"), or use the Microsoft Store or Homebrew.
  • Linux: Python 3 is usually pre-installed; otherwise install it with your package manager.

Check the installation with python3 --version (on Windows, py --version). Type python3 on its own to open the interactive shell, and exit() to leave it. Popular editors include Visual Studio Code with the Python extension, PyCharm and Jupyter notebooks for data work.

hello.py
name = input("What is your name? ")
print(f"Hello, {name}! Welcome to Python.")

year = 2026
print(f"{name} started learning Python in {year}.")
terminal
python3 hello.py

There is no main function, no semicolons and no type declarations: Python runs the file from top to bottom. input reads a line of text from the keyboard, and the f before the string makes it an f-string, which inserts the values of expressions written inside braces.

6. Variables and Data Types

A variable is created the first time you assign to it. In Python, a variable is a name that refers to an object; the object has a type, but the name does not, so the same name can later refer to an object of a different type.

types.py
count = 42                 # int: whole numbers of any size
price = 19.99              # float: decimal numbers
title = "Python Basics"    # str: text
is_published = True        # bool: True or False
discount = None            # NoneType: "no value"

print(type(count), type(price), type(title), type(is_published), type(discount))

big = 2 ** 100             # ints never overflow
print(big)

print(7 / 2, 7 // 2, 7 % 2, 2 ** 3)   # 3.5 3 1 8

# Converting between types
quantity = int("12")
total = quantity * price
print(f"Total: {total:.2f}")          # format with two decimal places
print(round(0.1 + 0.2, 2), 0.1 + 0.2 == 0.3)   # floats are approximate

For exact decimal arithmetic, such as money, use the decimal module instead of float. Names follow the snake_case convention, and constants are written in UPPER_CASE by convention (Python does not enforce constants).

7. Working with Strings

Strings are immutable sequences of Unicode characters. You can write them with single or double quotes, and with triple quotes for text that spans several lines.

strings.py
text = "  Learning Python is Fun  "

print(text.strip())                 # remove surrounding spaces
print(text.lower(), text.upper())
print(text.strip().split(" "))      # ['Learning', 'Python', 'is', 'Fun']
print("-".join(["2026", "10", "18"]))
print(text.replace("Fun", "Rewarding"))
print("Python" in text)             # True

word = "programming"
print(word[0], word[-1])            # first and last character: p g
print(word[0:7], word[3:], word[::-1])   # slicing: progra, gramming, reversed

language, version = "Python", 3.14
print(f"{language} {version} | {language:>10} | {version:.1f}")

report = """Line one
Line two"""
print(report.splitlines())

Slicing with [start:stop:step] works on all sequences, including lists and tuples. Negative indexes count from the end, and the stop index is not included.

8. Lists, Tuples, Dictionaries and Sets

TypeExampleOrderedChangeableUse it for
list[3, 1, 2]YesYesA sequence of items you add to, remove from and sort.
tuple(12.97, 77.59)YesNoFixed groups of values, such as coordinates or function results.
dict{"id": 7, "name": "Ana"}Insertion orderYesLooking up values by key.
set{"red", "blue"}NoYesUnique items and fast membership tests.
collections_demo.py
# list
scores = [72, 95, 88]
scores.append(64)
scores.sort(reverse=True)
print(scores, len(scores), max(scores), sum(scores) / len(scores))

# tuple and unpacking
location = (12.97, 77.59)
lat, lon = location
print(f"lat={lat}, lon={lon}")

# dict
student = {"name": "Ravi", "age": 21, "courses": ["Maths", "Physics"]}
student["age"] += 1
student["city"] = "Chennai"
print(student.get("email", "no email"))      # safe lookup with a default
for key, value in student.items():
    print(f"{key}: {value}")

# set
tags_a = {"python", "web", "api"}
tags_b = {"python", "data"}
print(tags_a & tags_b, tags_a | tags_b, tags_a - tags_b)   # intersection, union, difference

# Useful helpers from the standard library
from collections import Counter, defaultdict
words = "to be or not to be".split()
print(Counter(words).most_common(2))          # [('to', 2), ('be', 2)]

by_length = defaultdict(list)
for w in words:
    by_length[len(w)].append(w)
print(dict(by_length))

Dictionary keys and set items must be hashable, which in practice means immutable: strings, numbers and tuples work, but lists do not.

9. Control Flow

control.py
marks = 78

if marks >= 90:
    grade = "A"
elif marks >= 75:
    grade = "B"
else:
    grade = "C"
print("Grade:", grade)

# for loops iterate over any sequence or iterable
for i in range(1, 6):
    print(i, end=" ")
print()

fruits = ["apple", "mango", "banana"]
for index, fruit in enumerate(fruits, start=1):
    print(index, fruit)

for name, score in zip(["Ana", "Ben"], [91, 84]):
    print(f"{name} scored {score}")

# while loop with break
attempts = 0
while True:
    attempts += 1
    if attempts == 3:
        break
print("Stopped after", attempts, "attempts")

# match / case (Python 3.10+): structural pattern matching
def describe(command):
    match command.split():
        case ["go", direction]:
            return f"Moving {direction}"
        case ["pick", "up", item]:
            return f"Picked up {item}"
        case ["quit" | "exit"]:
            return "Goodbye"
        case _:
            return "Unknown command"

print(describe("go north"), "|", describe("pick up lamp"), "|", describe("dance"))

Indentation defines the blocks: everything indented under an if, for or def belongs to it. The standard is four spaces per level. In conditions, empty collections, 0, "" and None count as false, so if items: is the idiomatic way to check that a list is not empty.

10. Functions

functions.py
def bmi(weight_kg, height_m):
    """Return the body-mass index rounded to one decimal place."""
    return round(weight_kg / height_m ** 2, 1)

print(bmi(68, 1.75))
print(bmi(height_m=1.62, weight_kg=55))      # keyword arguments, any order

def greet(name, greeting="Hello"):           # default argument
    return f"{greeting}, {name}!"

print(greet("Meera"), greet("Meera", "Namaste"))

def stats(*numbers):                         # any number of positional arguments
    return min(numbers), max(numbers), sum(numbers) / len(numbers)

low, high, avg = stats(4, 8, 15, 16, 23, 42) # functions can return several values
print(low, high, avg)

def build_profile(name, **details):          # any number of keyword arguments
    return {"name": name, **details}

print(build_profile("Kiran", city="Pune", role="Developer"))

# Functions are objects: pass them around, and write small ones as lambdas
products = [("pen", 10), ("book", 250), ("bag", 799)]
print(sorted(products, key=lambda p: p[1], reverse=True))

The string right after the def line is a docstring; tools and help() show it as documentation. Variables assigned inside a function are local to it.

Mutable default trap: never write def add(item, items=[]). The default list is created once and shared between calls, so items pile up. Use items=None and create a new list inside the function when it is None.

11. Comprehensions

A comprehension builds a list, dictionary or set from another iterable in one readable expression:

comprehensions.py
numbers = range(1, 11)

squares = [n * n for n in numbers]
evens = [n for n in numbers if n % 2 == 0]
labels = ["even" if n % 2 == 0 else "odd" for n in range(1, 5)]

prices = {"pen": 10, "book": 250, "bag": 799}
with_tax = {item: round(price * 1.18, 2) for item, price in prices.items()}
initials = {name[0] for name in ["Asha", "Arjun", "Bela"]}

print(squares, evens, labels, with_tax, initials, sep="\n")

# Generator expression: computes values lazily, without building a list
total = sum(n * n for n in range(1_000_000))
print(total)

Keep comprehensions short. If one needs several conditions or nested loops that are hard to read, a regular for loop is clearer.

12. Modules, Packages and Virtual Environments

Every .py file is a module that other files can import. A folder of modules is a package. The standard library provides hundreds of modules ready to use:

stdlib_tour.py
import json
import random
from datetime import date, timedelta
from pathlib import Path

order = {"id": 101, "items": ["kettle", "mug"], "total": 1798.0}
text = json.dumps(order, indent=2)          # dict -> JSON text
print(json.loads(text)["items"])            # JSON text -> dict

print(random.choice(["red", "green", "blue"]))
print(date.today() + timedelta(days=30))
print([p.name for p in Path(".").glob("*.py")])

def main():
    print("Running as a script")

if __name__ == "__main__":     # true only when this file is run directly, not imported
    main()

Third-party packages come from PyPI. Install them into a virtual environment, an isolated folder with its own Python and packages, so that each project has the exact versions it needs and projects do not interfere with each other:

terminal
# Create and activate a virtual environment
python3 -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate

# Install packages and record the versions
pip install requests
pip freeze > requirements.txt

# Recreate the same environment elsewhere
pip install -r requirements.txt

Modern projects describe their dependencies in a pyproject.toml file. Tools such as uv and Poetry manage the environment, dependencies and lock files for you; uv is especially fast.

13. Files and Exceptions

Use a with statement to open files. It closes the file automatically when the block ends, even if an error occurs. Errors in Python are exceptions, handled with try/except.

expenses.py
import csv

rows = [("item", "amount"), ("books", "450"), ("lunch", "180.5"), ("metro", "sixty")]

with open("expenses.csv", "w", newline="", encoding="utf-8") as f:
    csv.writer(f).writerows(rows)

total = 0.0
with open("expenses.csv", encoding="utf-8") as f:
    for record in csv.DictReader(f):
        try:
            total += float(record["amount"])
        except ValueError:
            print(f"Skipping bad amount: {record['amount']!r}")

print(f"Total: {total:.2f}")

class InsufficientFunds(Exception):
    """Raised when a withdrawal is larger than the balance."""

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFunds(f"Cannot withdraw {amount}; balance is {balance}")
    return balance - amount

try:
    withdraw(100, 250)
except InsufficientFunds as e:
    print("Error:", e)
finally:
    print("Transaction attempt finished")

Catch specific exceptions, such as ValueError or FileNotFoundError, rather than a bare except:, which would also hide bugs and even keyboard interrupts. Always pass encoding="utf-8" when reading or writing text files so your program behaves the same on every operating system.

14. Object-Oriented Python

A class defines a new type with data (attributes) and behaviour (methods). The __init__ method initialises each new object, and self refers to the object a method is called on.

shapes.py
import math
from abc import ABC, abstractmethod
from dataclasses import dataclass, field

class Shape(ABC):
    @abstractmethod
    def area(self) -> float: ...

    def __str__(self):
        return f"{type(self).__name__} with area {self.area():.2f}"

class Circle(Shape):
    def __init__(self, radius: float):
        self.radius = radius

    def area(self) -> float:
        return math.pi * self.radius ** 2

class Rectangle(Shape):
    def __init__(self, width: float, height: float):
        self.width, self.height = width, height

    def area(self) -> float:
        return self.width * self.height

for shape in [Circle(1.5), Rectangle(2, 3.5)]:   # polymorphism: same call, different classes
    print(shape)

# dataclass: generates __init__, __repr__ and __eq__ automatically
@dataclass
class Product:
    name: str
    price: float
    tags: list[str] = field(default_factory=list)

    @property
    def price_with_tax(self) -> float:
        return round(self.price * 1.18, 2)

kettle = Product("Kettle", 1499.0, ["kitchen"])
print(kettle, kettle.price_with_tax)
print(kettle == Product("Kettle", 1499.0, ["kitchen"]))   # True

Methods with double underscores, such as __str__, __len__ and __eq__, are called special or "dunder" methods. Defining them lets your objects work with built-in functions and operators. Python has no private keyword; by convention, a leading underscore (_balance) marks an attribute as internal.

15. Iterators, Generators and Decorators

A generator function uses yield to produce values one at a time, pausing between them. It uses very little memory even for huge or endless sequences. A decorator wraps a function to add behaviour, such as timing, caching or access checks, without changing its code.

generators_decorators.py
import time
from functools import lru_cache, wraps

def fibonacci():
    a, b = 0, 1
    while True:              # an endless sequence, produced lazily
        yield a
        a, b = b, a + b

gen = fibonacci()
print([next(gen) for _ in range(10)])

def read_large_file(path):
    with open(path, encoding="utf-8") as f:
        for line in f:       # files are iterators: one line in memory at a time
            yield line.rstrip("\n")

def timed(func):
    @wraps(func)             # keep the original function's name and docstring
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.perf_counter() - start:.4f}s")
        return result
    return wrapper

@lru_cache(maxsize=None)     # built-in decorator: remember previous results
def ways_to_climb(steps):
    return 1 if steps <= 1 else ways_to_climb(steps - 1) + ways_to_climb(steps - 2)

@timed
def compute():
    return ways_to_climb(200)

print(compute())

16. Type Hints

Python stays dynamically typed, but you can add optional type hints. They do not change how code runs; instead, editors use them for auto-completion, and checkers such as mypy and Pyright use them to find bugs before the program runs. Frameworks such as FastAPI and Pydantic also use them to validate data.

typed.py
from typing import TypedDict

class User(TypedDict):
    id: int
    name: str
    email: str | None

def find_user(users: list[User], user_id: int) -> User | None:
    for user in users:
        if user["id"] == user_id:
            return user
    return None

def average(values: list[float]) -> float:
    return sum(values) / len(values) if values else 0.0

users: list[User] = [{"id": 1, "name": "Ana", "email": None}]
print(find_user(users, 1), average([2.5, 3.5]))

17. Concurrency in Python

Python offers three main ways to do several things at once:

ApproachBest forNotes
asyncioMany network or I/O operations at once, such as calling hundreds of APIs.One thread; tasks take turns at await points.
threading / ThreadPoolExecutorI/O-bound work with libraries that are not async.In the standard build, the GIL lets only one thread run Python bytecode at a time.
multiprocessing / ProcessPoolExecutorCPU-heavy work such as number crunching.Separate processes, each with its own interpreter, use all CPU cores.
async_demo.py
import asyncio
import time

async def fetch(name: str, delay: float) -> str:
    await asyncio.sleep(delay)          # stands in for a network call
    return f"{name} done after {delay}s"

async def main():
    start = time.perf_counter()
    results = await asyncio.gather(     # run all three concurrently
        fetch("orders", 1.0),
        fetch("users", 1.2),
        fetch("stock", 0.8),
    )
    print(results)
    print(f"Total time: {time.perf_counter() - start:.1f}s")   # about 1.2s, not 3s

asyncio.run(main())

The Global Interpreter Lock (GIL) has long prevented threads from running Python code truly in parallel. Since Python 3.13 there is also a free-threaded build without the GIL, officially supported from 3.14, which lets threads use multiple cores. Library support for it is still growing, so check your dependencies before relying on it.

18. Testing Your Code

The most popular testing tool is pytest. Tests are plain functions whose names start with test_, using ordinary assert statements:

test_pricing.py
import pytest

def apply_discount(price: float, percent: float) -> float:
    if not 0 <= percent <= 100:
        raise ValueError("percent must be between 0 and 100")
    return round(price * (1 - percent / 100), 2)

@pytest.mark.parametrize("price, percent, expected", [
    (100, 10, 90.0),
    (59.99, 0, 59.99),
    (80, 100, 0.0),
])
def test_apply_discount(price, percent, expected):
    assert apply_discount(price, percent) == expected

def test_invalid_percent():
    with pytest.raises(ValueError):
        apply_discount(100, 150)

Install pytest with pip install pytest and run pytest in the project folder; it finds and runs every test automatically. The standard library also includes the unittest module. Pair tests with a formatter and linter such as Ruff to keep code consistent.

19. Python vs. Java

AspectPythonJava
TypingDynamic, with optional type hintsStatic; types checked at compile time
SyntaxIndentation-based, conciseBraces and semicolons, more verbose
ExecutionBytecode run by an interpreter (CPython)Bytecode run by the JVM with a powerful JIT compiler
PerformanceSlower for pure Python loops; fast when using C-backed librariesGenerally much faster for CPU-heavy code
ParallelismGIL in the standard build; processes or the free-threaded build for parallel CPU workTrue multithreading, including virtual threads
Typical usesData science, AI, scripting, automation, web APIsLarge enterprise systems, Android, high-throughput back ends
Learning curveVery gentleModerate

Many teams use both: Java for core back-end services and Python for data pipelines, machine learning and tooling. If you are coming from Java, the Java tutorial on this site makes a useful side-by-side reference.

20. Best Practices and Common Mistakes

Best practices

  • Follow PEP 8, Python's style guide, and let a formatter such as Ruff or Black handle layout.
  • Use a virtual environment for every project and pin dependency versions.
  • Write small functions with clear names and docstrings.
  • Use f-strings for formatting, pathlib for file paths and with for files and other resources.
  • Add type hints to public functions and check them with mypy or Pyright.
  • Catch specific exceptions and let unexpected ones surface.
  • Write tests with pytest and run them automatically.
  • Prefer the standard library before adding a dependency.

Common mistakes to avoid

MistakeWhat goes wrongFix
Mutable default arguments (def f(x=[]))The same list is shared across calls.Default to None and create the list inside.
Modifying a list while looping over itItems are skipped or processed twice.Loop over a copy, or build a new list with a comprehension.
Using == to compare with NoneCan be fooled by custom __eq__ methods.Use is None and is not None.
Bare except:Hides real bugs and blocks Ctrl+C.Catch specific exception types.
Installing packages globallyVersion conflicts between projects.Use a virtual environment.
Building strings with + in a loopSlow for large amounts of text.Collect parts in a list and use "".join(parts).
Naming a file after a module, such as random.pyYour file is imported instead of the real module.Choose names that do not clash with the standard library.

21. Summary

  • Python is a readable, dynamically typed, general-purpose language with a huge ecosystem.
  • CPython compiles source code to bytecode and runs it on a virtual machine.
  • Core types include numbers, strings, lists, tuples, dictionaries and sets; indentation defines code blocks.
  • Functions support default, keyword and variable arguments, and comprehensions build collections concisely.
  • Modules and packages organise code; virtual environments isolate each project's dependencies.
  • Use with for files and try/except for errors; classes and dataclasses model data.
  • Generators, decorators and type hints make code efficient, reusable and easier to check.
  • Use asyncio for I/O concurrency, processes (or the free-threaded build) for CPU parallelism, and pytest for testing.

A good next step is a small project that combines these ideas, such as a command-line expense tracker that stores data in JSON, uses dataclasses and has pytest tests. Then open the FAQ tab for answers to the questions learners most often ask about Python.