Introduction to Python #

Some languages were born to be fast, some were born to be safe, and then there’s Python — born to be readable by humans. Guido van Rossum started Python in the late 1980s not because the world lacked programming languages, but because he was disappointed with ABC — a language whose philosophy he loved but whose implementation was too closed for development. Python took ABC’s philosophy (readability, simplicity, no unnecessary boilerplate) but made it open source, extensible, and pragmatic. The result is a language that in three decades managed to dominate very different domains simultaneously: web development, data science, machine learning, scripting, education, and scientific research. No other language has achieved the same breadth. This article discusses why Python was designed this way, how Python evolved from version 1 to 3.12, what makes its ecosystem unique, and when Python is the right choice — and when it isn’t.

The Philosophy of Python — The Zen of Python #

Every programming language has implicit design decisions. Python has something more explicit: The Zen of Python, written by Tim Peters, available directly from the Python interpreter with import this. This isn’t formal documentation — it’s a guiding principle that influences every language design decision and how the Python community writes code.

import this
# Output (19 aphorisms):
# Beautiful is better than ugly.
# Explicit is better than implicit.
# Simple is better than complex.
# Complex is better than complicated.
# Flat is better than nested.
# Sparse is better than dense.
# Readability counts.
# Special cases aren't special enough to break the rules.
# Although practicality beats purity.
# Errors should never pass silently.
# Unless explicitly silenced.
# In the face of ambiguity, refuse the temptation to guess.
# There should be one-- and preferably only one --obvious way to do it.
# Now is better than never.
# Although never is often better than *right* now.
# If the implementation is hard to explain, it's a bad idea.
# If the implementation is easy to explain, it may be a good idea.
# Namespaces are one honking great idea -- let's do more of those!

The three principles that most impact how Python is written daily:

Readability counts — Python uses indentation as part of the syntax, not a style choice. This forces all Python code to look consistent and be easy to read. No curly braces making developers argue over where they go.

Explicit is better than implicit — Python avoids hidden “magic”. If something happens, you can see it in the code. This differs from languages like Ruby that love hidden metaprogramming.

There should be one obvious way to do it — Unlike Perl or Ruby, which pride themselves on TIMTOWTDI (There Is More Than One Way To Do It), Python prefers one clear way over many confusing ones. gofmt in Go was inspired by this philosophy.

flowchart TD
    A[The Zen of Python] --> B[Readability]
    A --> C[Explicitness]
    A --> D[Simplicity]
    A --> E[Practicality]

    B --> B1[Indentation as syntax]
    B --> B2[Descriptive variable names]

    C --> C1[Explicit imports]
    C --> C2[No hidden magic methods]

    D --> D1[One obvious way for every task]
    D --> D2[Flat is better than nested]

    E --> E1[Practicality beats purity]
    E --> E2[Batteries included stdlib]

History and Evolution of Python #

Python isn’t a language born from a large corporate project — it was born from one person with a clear vision of what a pleasant-to-use language should look like.

YearVersionImportant Milestone
1989Guido van Rossum starts working on Python at CWI, the Netherlands
19910.9.0First public release: functions, exceptions, str/list/dict already present
19941.0First stable release: lambda, map, filter, reduce
20002.0List comprehensions, garbage collection, Unicode support
20083.0Major breaking change: print became a function, str/bytes separated, consistent division
20102.7Last 2.x version, many Python 3 features backported
20153.5async/await — native asynchronous programming
20163.6f-strings, variable type annotations, ordered dicts by default
20183.7Data classes, breakpoint(), context variables
20193.8Walrus operator :=, f"{val!r}" debug format
20202 EOLPython 2 officially End of Life — no more security updates
20203.9dict | dict merge, list[int] as direct type hints
20213.10Structural pattern matching (match/case), better error messages
20223.1140–60% faster than 3.10 (CPython Faster project)
20233.12Per-interpreter GIL, more flexible f-strings, pathlib improvements
20243.13Experimental free-threaded mode (no GIL), experimental JIT compiler

The biggest leap in Python’s modern history is Python 3.11. The CPython team, led by Łukasz Langa and Mark Shannon, launched the “Faster CPython” project funded by Microsoft to make CPython significantly faster. The result: Python 3.11 is 40–60% faster than Python 3.10 for many workloads. Python 3.13 brings something more radical: an experimental free-threaded mode that disables the GIL — something the Python community dreamed about for nearly 30 years.

stateDiagram-v2
    [*] --> PythonEarly: 1991-1999
    PythonEarly --> Python2Era: Python 2.0 (2000)
    Python2Era --> Python3Era: Python 3.0 (2008)
    Python3Era --> AsyncEra: async/await 3.5 (2015)
    AsyncEra --> ModernEra: 3.10+ (2021)
    ModernEra --> [*]

    PythonEarly: Simple scripting language, small community
    Python2Era: Mass adoption, web frameworks, Unicode
    Python3Era: Breaking changes, 12-year-long transition
    AsyncEra: FastAPI, data science boom, ML dominance
    ModernEra: Faster CPython, pattern matching, free-threaded GIL

Modern Python Features #

Type Hints and Static Analysis #

Python is a dynamic language — types aren’t checked at runtime unless you ask. But since Python 3.5, Python supports type hints: type annotations used by tools like mypy, pyright, or basedpyright for static analysis, without changing runtime behavior.

# ANTI-PATTERN: function without type hints — ambiguous for readers and tools
def proses_data(data, threshold):
    hasil = []
    for item in data:
        if item > threshold:
            hasil.append(item * 2)
    return hasil

# CORRECT: type hints clarify the function contract
def proses_data(data: list[float], threshold: float) -> list[float]:
    return [item * 2 for item in data if item > threshold]

# More complex types
from typing import Optional, Union, TypeVar, Generic

def cari_pengguna(user_id: int) -> Optional[dict[str, str]]:
    # Optional[X] = X | None
    if user_id <= 0:
        return None
    return {"id": str(user_id), "nama": f"Pengguna {user_id}"}

# Python 3.10+ — union types with | directly
def proses(nilai: int | float | None) -> str:
    match nilai:
        case None:
            return "kosong"
        case int(n) if n < 0:
            return f"negatif: {n}"
        case int(n) | float(n):
            return f"positif: {n}"

Comprehensions — Declarative Syntax #

Comprehensions are one of the most Pythonic features — an expressive way to create collections from iteration.

angka = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# ANTI-PATTERN: imperative loop for a simple transformation
genap = []
for n in angka:
    if n % 2 == 0:
        genap.append(n * n)

# CORRECT: list comprehension — declarative, one line
genap = [n * n for n in angka if n % 2 == 0]
# => [4, 16, 36, 64, 100]

# Dict comprehension
kuadrat_map = {n: n**2 for n in range(1, 6)}
# => {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

# Set comprehension — automatically deduplicates
huruf_unik = {c.lower() for c in "Python Programming" if c.isalpha()}

# Generator expression — lazy evaluation, memory efficient
total = sum(n**2 for n in range(1_000_000))  # doesn't build a 1-million-item list

Decorators #

A decorator is a pattern for modifying the behavior of functions or classes without changing their original code. It’s Python’s implementation of Higher-Order Functions, widely used in web frameworks, testing, and caching.

import functools
import time

# Simple decorator — measuring execution time
def ukur_waktu(fungsi):
    @functools.wraps(fungsi)  # preserve the original function's metadata
    def wrapper(*args, **kwargs):
        mulai = time.perf_counter()
        hasil = fungsi(*args, **kwargs)
        selesai = time.perf_counter()
        print(f"{fungsi.__name__} finished in {selesai - mulai:.4f} seconds")
        return hasil
    return wrapper

@ukur_waktu
def hitung_berat(n: int) -> int:
    return sum(i**2 for i in range(n))

hitung_berat(1_000_000)
# => hitung_berat finished in 0.1234 seconds

# Decorator with arguments
def retry(maks_coba: int = 3, delay: float = 1.0):
    def decorator(fungsi):
        @functools.wraps(fungsi)
        def wrapper(*args, **kwargs):
            for percobaan in range(maks_coba):
                try:
                    return fungsi(*args, **kwargs)
                except Exception as e:
                    if percobaan == maks_coba - 1:
                        raise
                    print(f"Attempt {percobaan + 1} failed: {e}. Trying again...")
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(maks_coba=3, delay=0.5)
def panggil_api(url: str) -> dict:
    # simulate a request that can fail
    import random
    if random.random() < 0.7:
        raise ConnectionError("Connection failed")
    return {"status": "ok"}

Async/Await and Asyncio #

Python 3.5 introduced async/await as a native way to write asynchronous code without callback hell.

import asyncio
import aiohttp

# Async function -- defined with async def
async def ambil_data(session: aiohttp.ClientSession, url: str) -> dict:
    async with session.get(url) as response:
        return await response.json()

# ANTI-PATTERN: sequential -- waiting one at a time (slow)
async def ambil_sequential():
    async with aiohttp.ClientSession() as session:
        data1 = await ambil_data(session, "https://api.example.com/users/1")
        data2 = await ambil_data(session, "https://api.example.com/users/2")
        data3 = await ambil_data(session, "https://api.example.com/users/3")
        return [data1, data2, data3]

# CORRECT: concurrent -- run everything at once with asyncio.gather
async def ambil_concurrent():
    urls = [
        "https://api.example.com/users/1",
        "https://api.example.com/users/2",
        "https://api.example.com/users/3",
    ]
    async with aiohttp.ClientSession() as session:
        tasks = [ambil_data(session, url) for url in urls]
        hasil = await asyncio.gather(*tasks)
        return hasil

# Running the event loop
if __name__ == "__main__":
    asyncio.run(ambil_concurrent())
Python async/await is only effective for I/O-bound tasks (network requests, file I/O, database queries). For CPU-bound tasks (heavy computation, image processing, ML inference), asyncio doesn’t help because the GIL still blocks true parallelism. For CPU-bound, use multiprocessing or ProcessPoolExecutor. Python 3.13 is starting to introduce an experimental free-threaded mode that disables the GIL.

Pattern Matching (Python 3.10+) #

match/case is structural pattern matching — far more powerful than switch/case in other languages because it can do destructuring.

from dataclasses import dataclass

@dataclass
class Titik:
    x: float
    y: float

@dataclass
class Lingkaran:
    pusat: Titik
    radius: float

@dataclass
class Persegi:
    kiri_atas: Titik
    kanan_bawah: Titik

def deskripsikan(bentuk) -> str:
    match bentuk:
        case Lingkaran(pusat=Titik(x=0, y=0), radius=r):
            return f"Circle at the origin with radius {r}"
        case Lingkaran(radius=r) if r > 100:
            return f"Very large circle (radius {r})"
        case Lingkaran(pusat=p, radius=r):
            return f"Circle at ({p.x}, {p.y}), radius {r}"
        case Persegi(kiri_atas=Titik(x=x1, y=y1), kanan_bawah=Titik(x=x2, y=y2)):
            lebar = x2 - x1
            tinggi = y2 - y1
            return f"Square {lebar}x{tinggi}"
        case _:
            return "Unknown shape"

The Ecosystem: pip, virtualenv, and PyPI #

Python has a huge package ecosystem through PyPI (Python Package Index) with more than 500,000 packages available.

# Package management with pip
pip install requests                      # install a package
pip install "django>=4.2,<5.0"           # specific version
pip install -r requirements.txt           # install from a file
pip list                                  # list installed packages
pip freeze > requirements.txt            # export dependencies
pip show requests                         # detailed package info

# Virtual environments -- REQUIRED for every project
python -m venv venv                       # create a virtual environment
source venv/bin/activate                  # activate (Linux/macOS)
venv\Scripts\activate                     # activate (Windows)
deactivate                                # deactivate

# uv -- a modern package manager far faster than pip
pip install uv
uv init nama-project                      # init a new project
uv add requests fastapi                   # add dependencies
uv run python main.py                     # run within the environment
uv sync                                   # sync dependencies from a lockfile

Example pyproject.toml for a modern project (PEP 517/518 standard):

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "aplikasi-saya"
version = "1.0.0"
requires-python = ">=3.11"
dependencies = [
    "fastapi>=0.110",
    "sqlalchemy>=2.0",
    "pydantic>=2.0",
    "httpx>=0.27",
    "redis>=5.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=8.0",
    "mypy>=1.9",
    "ruff>=0.4",         # modern linter + formatter, replacing flake8+black
    "pytest-asyncio",
]

Key Libraries per Domain #

DomainLibraryPurpose
Web FrameworksFastAPI, Django, FlaskHTTP servers and APIs
HTTP Clientshttpx, requests, aiohttpConsuming APIs
ORMSQLAlchemy, Django ORM, TortoiseDatabase access
Data Sciencepandas, NumPy, PolarsData manipulation
VisualizationMatplotlib, Plotly, SeabornGraphs and charts
Machine Learningscikit-learn, XGBoost, LightGBMClassical ML
Deep LearningPyTorch, TensorFlow, JAXNeural networks
NLPTransformers (HuggingFace), spaCyText processing
Testingpytest, unittest, hypothesisTesting
Lintingruff, mypy, pylintCode quality
CLIclick, typer, argparseCommand-line apps
Task QueuesCelery, arq, dramatiqBackground jobs

Python Implementation Variants #

CPython is the reference implementation of Python, but there are several other implementations relevant for specific use cases.

ImplementationWritten inMain AdvantageSuitable for
CPythonCFull compatibility, largest ecosystemAlmost every use case
PyPyRPythonJIT compiler — 5–10x faster for long-running programsPrograms with lots of numerical computation
JythonJavaIntegration with the JVM ecosystemProjects needing Java interop
IronPythonC#Integration with .NET.NET projects needing Python
MicroPythonCVery lightweight, runs on microcontrollersIoT, embedded systems
CircuitPythonCMicroPython fork for educationHobby electronics projects with Adafruit
PyPy is often a source of confusion. PyPy is very fast for pure Python code that runs long (game servers, simulations, batch processing). But PyPy is not compatible with all C extension libraries — NumPy, pandas, and most data science libraries don’t run optimally on PyPy. For data science, stick with CPython. For workloads whose bottleneck is in pure Python loops, PyPy can be an attractive choice.

The GIL — Global Interpreter Lock #

The GIL (Global Interpreter Lock) is a mechanism in CPython ensuring only one thread executes Python bytecode at a time. It’s one of the most misunderstood topics about Python.

flowchart TD
    A{Type of task?} --> B[I/O-bound\nNetwork, file, DB]
    A --> C[CPU-bound\nComputation, image processing]

    B --> D[asyncio / threading\ncan be effective — GIL released during I/O]
    C --> E[multiprocessing\nor ProcessPoolExecutor]
    C --> F[C/Cython extensions\nor NumPy which releases the GIL]

    D --> G[Good for\nweb scraping, API calls, bots]
    E --> H[Good for\nML preprocessing, batch compute]
    F --> I[Good for\nscientific computing]

The GIL is not a problem for:

  • Web servers (I/O-bound — the GIL is released while waiting on network/DB)
  • Data science with NumPy/pandas (NumPy operations release the GIL)
  • Asyncio (single-threaded, the GIL is irrelevant)

The GIL becomes a problem for:

  • Parallel computation in pure Python across multiple threads
  • CPU-intensive workloads needing true parallelism in threads

The solutions: multiprocessing for CPU-bound, asyncio or threading for I/O-bound.


When to Choose Python #

Choose Python if:
  ✓ You work in data science, machine learning, or AI — Python has no rival here
  ✓ You need scripting or automation — Python is king in this domain
  ✓ You're building web APIs with high development speed needs
  ✓ You're in research or academia — the scientific Python ecosystem is unmatched
  ✓ You're building prototypes or MVPs needing fast iteration
  ✓ New or diverse-background teams — Python is the easiest to learn

Consider alternatives if:
  ✗ You need high performance for CPU-intensive tasks → Go, Rust, C++
  ✗ You're building mobile apps → Flutter/Dart, Swift, Kotlin
  ✗ You need massive concurrency with thousands of goroutines/threads → Go
  ✗ Memory footprint is a top priority → Go, Rust
  ✗ You're building embedded systems with tight constraints → C, MicroPython
  ✗ Absolute compile-time type safety is a requirement → Rust, Go, Kotlin
CriterionPythonGoRustJavaScript
Ease of learning★★★★★★★★★☆★★☆☆☆★★★★☆
Performance★★☆☆☆★★★★★★★★★★★★★☆☆
ML/AI ecosystem★★★★★★☆☆☆☆★★☆☆☆★★☆☆☆
Scripting/Automation★★★★★★★★☆☆★★☆☆☆★★★☆☆
Web APIs★★★★☆★★★★★★★★☆☆★★★★★
Concurrency★★★☆☆★★★★★★★★★☆★★★★☆

FAQ #

Is Python slow and does it matter?

Python CPython is indeed slower than Go, Rust, or Java for CPU-intensive computation. But for most real applications, the bottleneck is I/O (database, network), not the language. A Django or FastAPI web server responding in 50ms — is “slow” Python relevant there? For ML/AI, the libraries used (NumPy, PyTorch) are written in C/C++ and CUDA — Python is just the orchestrator. Python 3.11 is also already 40–60% faster than 3.10.

What’s the difference between requirements.txt and pyproject.toml?

requirements.txt is an old format that only records the package list and versions — flat, no project metadata. pyproject.toml is the modern standard (PEP 517/518) that unifies build system configuration, dependencies, and tooling (mypy, pytest, ruff) in one file. For new projects, always use pyproject.toml. For deployment, use uv lock or pip freeze to produce a reproducible lockfile.

When should I use FastAPI vs Django vs Flask?

Django: a full-stack framework with an ORM, admin panel, built-in auth — suitable for traditional web apps with many built-in features. Flask: a minimal micro-framework, you pick every component yourself — suitable for simple APIs or when you need full control. FastAPI: a modern async-based framework with type hints and OpenAPI auto-generation — the best choice for REST APIs or microservices needing performance and automatic documentation.

What is __init__.py and when is it needed?

__init__.py is the file that marks a directory as a Python package — allowing that directory to be imported. In Python 3.3+, “namespace packages” exist that don’t require __init__.py, but for regular packages that need distribution or organized initialization, __init__.py remains relevant and recommended.

Is Python good for competitive programming?

Python is popular in competitive programming because of its concise syntax and rich built-in libraries (collections, heapq, itertools, math). The drawback: Python can TLE (Time Limit Exceeded) on problems needing high performance. Solutions: use PyPy if the platform supports it, or implement critical parts with optimized built-in data structures like collections.deque and heapq.


Summary #

  • Python was born for human readability — The Zen of Python isn’t a slogan, it’s a real design principle. Indentation as syntax, one obvious way to do things, explicit over implicit — all of this shapes the language’s unique character.
  • Python 3.11 and 3.12 are much faster — The Faster CPython project made Python 3.11 40–60% faster than 3.10. Always upgrade to the latest stable version. Python 2 has been EOL since 2020 — no reason to use Python 2.
  • Type hints are a modern best practice — Use type hints in all production code. They don’t change runtime behavior but provide huge benefits: automatic documentation, IDE support, and earlier bug detection with mypy or pyright.
  • Virtual environments are required for every project — Don’t install packages globally. Use python -m venv or modern tools like uv for per-project dependency isolation.
  • The GIL isn’t a problem for most use cases — I/O-bound tasks (web servers, API calls) aren’t affected by the GIL. For CPU-bound, use multiprocessing. For async I/O, use asyncio.
  • Python is irreplaceable in ML/AI — PyTorch, TensorFlow, scikit-learn, HuggingFace Transformers — the Python ML ecosystem has no equal in any language. If you work in AI/ML, Python is the only practical choice.
  • Comprehensions and decorators are Python idioms — List/dict/set comprehensions for collection transformations, decorators for cross-cutting concerns. Pythonic code leverages both well.
  • uv and ruff are modern Python tooling — uv replaces pip+virtualenv with 10–100x faster speed. ruff replaces flake8+isort+black in one tool that’s also much faster. For new projects, use both from the start.

Next: Installation →
About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact