Python Tutorial
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).
| Year | Milestone |
|---|---|
| 1991 | The first public release (version 0.9.0). |
| 2000 | Python 2.0 added list comprehensions and cycle-detecting garbage collection. |
| 2008 | Python 3.0, a cleaned-up, backward-incompatible version of the language, with Unicode text strings by default. |
| 2020 | Python 2 reached end of life on 1 January; Python 3 is now the only supported version. |
| 2021 | Python 3.10 added structural pattern matching (match/case). |
| 2022 | Python 3.11 brought major speed improvements to the interpreter. |
| 2024 | Python 3.13 introduced an improved interactive shell and an experimental free-threaded build without the GIL. |
| 2025 | Python 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
| Feature | What it means for you |
|---|---|
| Simple, readable syntax | Fewer symbols and enforced indentation make code easy to read and write. |
| Dynamic typing | Variables do not need type declarations; types are checked while the program runs. |
| Automatic memory management | Reference counting plus a garbage collector free memory for you. |
| Interactive shell | Try code line by line in the REPL, ideal for learning and quick experiments. |
| Batteries included | A rich standard library for files, JSON, HTTP, dates, maths, testing and more. |
| Huge ecosystem | Packages for almost any task, installed with one pip command. |
| Cross-platform | The same code runs on Windows, macOS, Linux and many other systems. |
| Easy integration | Python 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.
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.
name = input("What is your name? ")
print(f"Hello, {name}! Welcome to Python.")
year = 2026
print(f"{name} started learning Python in {year}.")
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.
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.
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
| Type | Example | Ordered | Changeable | Use it for |
|---|---|---|---|---|
list | [3, 1, 2] | Yes | Yes | A sequence of items you add to, remove from and sort. |
tuple | (12.97, 77.59) | Yes | No | Fixed groups of values, such as coordinates or function results. |
dict | {"id": 7, "name": "Ana"} | Insertion order | Yes | Looking up values by key. |
set | {"red", "blue"} | No | Yes | Unique items and fast membership tests. |
# 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
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
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.
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:
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:
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:
# 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.
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.
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.
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.
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:
| Approach | Best for | Notes |
|---|---|---|
asyncio | Many network or I/O operations at once, such as calling hundreds of APIs. | One thread; tasks take turns at await points. |
threading / ThreadPoolExecutor | I/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 / ProcessPoolExecutor | CPU-heavy work such as number crunching. | Separate processes, each with its own interpreter, use all CPU cores. |
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:
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
| Aspect | Python | Java |
|---|---|---|
| Typing | Dynamic, with optional type hints | Static; types checked at compile time |
| Syntax | Indentation-based, concise | Braces and semicolons, more verbose |
| Execution | Bytecode run by an interpreter (CPython) | Bytecode run by the JVM with a powerful JIT compiler |
| Performance | Slower for pure Python loops; fast when using C-backed libraries | Generally much faster for CPU-heavy code |
| Parallelism | GIL in the standard build; processes or the free-threaded build for parallel CPU work | True multithreading, including virtual threads |
| Typical uses | Data science, AI, scripting, automation, web APIs | Large enterprise systems, Android, high-throughput back ends |
| Learning curve | Very gentle | Moderate |
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,
pathlibfor file paths andwithfor 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
| Mistake | What goes wrong | Fix |
|---|---|---|
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 it | Items are skipped or processed twice. | Loop over a copy, or build a new list with a comprehension. |
Using == to compare with None | Can 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 globally | Version conflicts between projects. | Use a virtual environment. |
Building strings with + in a loop | Slow for large amounts of text. | Collect parts in a list and use "".join(parts). |
Naming a file after a module, such as random.py | Your 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
withfor files andtry/exceptfor 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.
Frequently Asked Questions (FAQ)
Q1. Is Python good for beginners?
Yes. Python is one of the best first languages because its syntax is short and readable, it needs no type declarations or boilerplate, and you can try code instantly in the interactive shell. Beginners can focus on problem-solving instead of language details.
It is also a practical choice: the same language is used professionally for web development, automation, data science and AI.
Q2. Is Python free to use?
Yes. Python is open source under the Python Software Foundation License, which allows free use, modification and distribution, including in commercial products. Almost all popular libraries on PyPI are also open source, though you should check each package's licence for commercial use.
Q3. Which Python version should I install?
Install the latest stable Python 3 release from python.org or your operating system's package manager, unless a project or library you depend on requires an older version. Each version is supported for five years, so avoid versions that have reached end of life.
Python 2 is no longer supported and should not be used for new work.
Q4. Is Python compiled or interpreted?
Both, in a sense. CPython first compiles your source code into bytecode, then an interpreter loop, the Python Virtual Machine, executes that bytecode. This happens automatically when you run a script, which is why Python feels like an interpreted language.
Other implementations, such as PyPy, add a just-in-time compiler that turns frequently run code into machine code for extra speed.
Q5. Why is Python slower than languages like Java or C++?
Pure Python code is slower because it is dynamically typed and interpreted: every operation checks types at run time, and there is no ahead-of-time optimisation to machine code. Recent versions (3.11 onwards) have become significantly faster, but the gap remains for tight loops.
In practice, performance-critical work runs inside libraries written in C, C++ or Rust, such as NumPy, pandas and PyTorch, so Python programs that use them can be very fast.
Q6. What is the difference between a list and a tuple?
A list is mutable: you can add, remove and change items. A tuple is immutable: once created, it cannot change. Tuples are slightly faster and smaller, can be used as dictionary keys, and signal that a group of values belongs together, such as a coordinate pair.
Use a list for collections that grow or change, and a tuple for fixed records.
Q7. What is the difference between == and is?
== compares values: are the two objects equal? is compares identity: are they the very same object in memory? Two separate lists with the same contents are == but not is.
Use is only for singletons, mainly x is None, and == for everything else.
Q8. What are *args and **kwargs?
They let a function accept a variable number of arguments. *args collects extra positional arguments into a tuple, and **kwargs collects extra keyword arguments into a dictionary. The names are just conventions; the * and ** do the work.
The same operators unpack values when calling a function, for example func(*my_list, **my_dict).
Q9. What is a virtual environment, and why do I need one?
A virtual environment is an isolated folder containing its own Python interpreter and installed packages. Each project gets its own environment, so one project can use one version of a library while another uses a different version, without conflicts.
Create one with python3 -m venv .venv, activate it, then install packages with pip. Tools such as uv and Poetry create and manage environments automatically.
Q10. What is the difference between pip and uv?
pip is Python's standard package installer, included with Python. uv is a newer, much faster tool written in Rust that can replace pip, venv and other tools: it installs packages, creates virtual environments, manages Python versions and produces lock files for reproducible installs.
Both install packages from PyPI; pip is always available, while uv offers speed and an all-in-one workflow.
Q11. What is the GIL?
The Global Interpreter Lock is a mutex in the standard CPython build that allows only one thread at a time to execute Python bytecode. It simplifies memory management but prevents threads from using multiple CPU cores for pure Python code. Threads still help with I/O, because the GIL is released while waiting.
Python 3.13 introduced an optional free-threaded build without the GIL, officially supported since 3.14. For CPU-heavy work today, multiprocessing is the common choice.
Q12. When should I use asyncio instead of threads?
Use asyncio when you have many I/O operations running at once, such as thousands of network requests or open connections, and you can use async-compatible libraries. It runs everything in one thread with very low overhead per task.
Use threads when you need concurrency with ordinary blocking libraries, and processes for CPU-heavy work.
Q13. What does if __name__ == "__main__" mean?
Every module has a __name__ variable. When you run a file directly, Python sets it to "__main__"; when the file is imported, it is set to the module's name. Putting startup code under this check lets a file work both as a runnable script and as an importable module without running that code on import.
Q14. What is a decorator?
A decorator is a function that takes another function and returns a modified version of it. Writing @timed above a function definition is shorthand for func = timed(func). Decorators add behaviour such as logging, timing, caching (@functools.lru_cache), access control or web routing (@app.get in FastAPI) without changing the function's own code.
Q15. What is a generator, and when should I use one?
A generator is a function that uses yield to produce a sequence of values lazily, one at a time, pausing between them. Because it does not build the whole sequence in memory, it is ideal for large files, data streams, pipelines and infinite sequences.
Generator expressions, written like list comprehensions with parentheses, give the same benefit in one line.
Q16. Do type hints make Python statically typed?
No. Type hints are optional annotations that Python ignores at run time. They help editors offer better suggestions and let tools such as mypy and Pyright catch type errors before you run the code. Some libraries, such as Pydantic and FastAPI, read the hints to validate data.
Q17. How does Python manage memory?
CPython uses reference counting: each object tracks how many references point to it and is freed as soon as the count reaches zero. A cyclic garbage collector also runs periodically to clean up groups of objects that refer to each other but are no longer used.
You rarely need to think about it, but keeping large objects alive in global variables or caches can still cause high memory use.
Q18. Which Python web framework should I learn?
It depends on the project:
- Django: a full-featured framework with an ORM, admin panel, authentication and more, great for complete web applications.
- FastAPI: a modern, fast framework for APIs that uses type hints for validation and automatic documentation.
- Flask: a minimal, flexible framework for small apps and services.
Q19. Is Python used for AI and machine learning?
Yes, it is the main language for AI and machine learning. Libraries such as NumPy and pandas handle data, scikit-learn covers classic machine learning, PyTorch and TensorFlow power deep learning, and many AI APIs and tools provide Python SDKs first. Jupyter notebooks are widely used for experiments.
Q20. What are good projects to practise Python?
Start small and grow:
- A number-guessing game, a to-do list or an expense tracker that saves to JSON or CSV.
- A script that renames and organises files in a folder.
- A web scraper or a program that calls a public API.
- A small REST API with FastAPI or Flask.
- A data analysis of a public dataset with pandas and charts.