CodeNebula
Back to Go Overview
🔧

Go — Core Language

Types, functions, structs, interfaces

Go🔧Core Language

Variables and Type Inference

Go is statically typed but uses type inference to eliminate boilerplate. The `:=` short declaration infers the type from the right-hand side at compile time. `var` declarations with explicit types are used at package level or when the zero value is intentional.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
package main

import "fmt"

func main() {
    x := 42            // int inferred
    name := "Go"       // string inferred
    ratio := 3.14      // float64 inferred
    active := true     // bool inferred

    var count int      // zero value: 0
    var label string   // zero value: ""

    fmt.Println(x, name, ratio, active, count, label)
}
Go🔧Core Language

Basic Types

Go has a small, fixed set of built-in types. Integer types are explicitly sized (`int8`, `int16`, `int32`, `int64`, `uint8` … `uint64`), plus `int` and `uint` which are platform-width (64-bit on 64-bit systems). `string` is an immutable sequence of bytes (UTF-8). `rune` is an alias for `int32`, representing a Unicode code point.

beginnerflip for code
1
2
3
4
5
6
7
var i int = 100
var u uint32 = 4294967295
var f float64 = 3.141592653589793
var b byte = 255        // alias for uint8
var r rune = '🚀'       // alias for int32, holds Unicode code point
var s string = "Hello, 世界"
var ok bool = true
Go🔧Core Language

Functions

Functions are first-class values in Go. They can return multiple values — idiomatic Go uses this for `(result, error)` pairs instead of exceptions. Functions can be assigned to variables, passed as arguments, and defined as closures capturing their surrounding scope.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
// Multiple return values
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

// Named return values (document what each value means)
func minMax(nums []int) (min, max int) {
    min, max = nums[0], nums[0]
    for _, n := range nums[1:] {
        if n < min { min = n }
        if n > max { max = n }
    }
    return // naked return
}

// Function as value
double := func(x int) int { return x * 2 }
Go🔧Core Language

Control Flow

Go has `if`, `for`, and `switch` — no `while`, no `do-while`, no `until`. `for` covers all looping: classic C-style, while-style, and range-based. `switch` does not fall through by default (no `break` needed); use `fallthrough` explicitly when required.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
// if with initialization statement
if err := doWork(); err != nil {
    log.Fatal(err)
}

// for as while
for running {
    process()
}

// range over slice (index, value)
for i, v := range items {
    fmt.Println(i, v)
}

// switch — no fall-through by default
switch status {
case 200: fmt.Println("OK")
case 404: fmt.Println("Not Found")
default:  fmt.Println("Other")
}
Go🔧Core Language

Structs

Go uses structs for composite data types. There is no `class` keyword. Methods are defined outside the struct body using a receiver syntax. Structs are value types — assigning one copies it; use a pointer receiver when a method needs to mutate state or the struct is large.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
type Point struct {
    X, Y float64
}

// Value receiver — reads state
func (p Point) Distance() float64 {
    return math.Sqrt(p.X*p.X + p.Y*p.Y)
}

// Pointer receiver — mutates state
func (p *Point) Scale(factor float64) {
    p.X *= factor
    p.Y *= factor
}

p := Point{X: 3, Y: 4}
fmt.Println(p.Distance()) // 5
p.Scale(2)
fmt.Println(p)            // {6 8}
Go🔧Core Language

Interfaces

Interfaces in Go are satisfied implicitly — a type implements an interface simply by having the required methods, with no `implements` keyword. This enables loose coupling: packages define small interfaces, and any type that happens to match can be used. The most famous Go interface is `io.Reader` with its single `Read([]byte) (int, error)` method.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
type Stringer interface {
    String() string
}

type Temperature struct {
    Celsius float64
}

// Temperature implicitly satisfies Stringer
func (t Temperature) String() string {
    return fmt.Sprintf("%.1f°C", t.Celsius)
}

func Print(s Stringer) {
    fmt.Println(s.String())
}

Print(Temperature{36.6}) // 36.6°C
Go🔧Core Language

Pointers

Go has pointers but no pointer arithmetic. A pointer holds the memory address of a value. Use `&` to take an address, `*` to dereference. Pointers are primarily used for: mutating a value through a function argument, sharing large structs without copying, and expressing optional values (nil pointer = absent).

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
x := 10
p := &x       // p is *int, holds address of x
*p = 20       // dereference: x is now 20
fmt.Println(x) // 20

// nil pointer — optional value pattern
type Node struct {
    Value int
    Next  *Node // nil when there is no next node
}
Go🔧Core Language

Arrays and Slices

An array in Go has a fixed length that is part of its type: `[3]int` and `[4]int` are different types. In practice, arrays are rarely used directly — **slices** are the workhorse. A slice is a descriptor (pointer + length + capacity) over an underlying array. `make([]T, len, cap)` allocates one. `append` grows slices automatically.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
// Array (fixed size, value type)
arr := [3]int{1, 2, 3}

// Slice (dynamic, reference type)
s := []int{10, 20, 30}
s = append(s, 40)

// Slice of slice
sub := s[1:3] // [20, 30] — shares underlying array

// make with capacity hint
buf := make([]byte, 0, 4096)
Go🔧Core Language

Maps

A `map[K]V` is Go's built-in hash map. Keys must be `comparable` (no slices, maps, or functions as keys). The zero value of a map is `nil` — attempting to write to a nil map panics. Always initialize with `make` or a literal. The two-value assignment `v, ok := m[key]` safely checks for key presence without panicking on missing keys.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
// Literal initialization
scores := map[string]int{
    "Alice": 95,
    "Bob":   87,
}

// Safe lookup
score, ok := scores["Charlie"]
if !ok {
    fmt.Println("not found")
}

// Delete
delete(scores, "Bob")

// Iterate (order is random)
for name, s := range scores {
    fmt.Println(name, s)
}
Go🔧Core Language

Error Handling

Go uses explicit error return values instead of exceptions. The `error` interface has one method: `Error() string`. Functions signal failure by returning a non-nil error as their last return value. `fmt.Errorf("context: %w", err)` wraps errors with context; `errors.Is` and `errors.As` unwrap them for inspection.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
import "errors"

var ErrNotFound = errors.New("not found")

func findUser(id int) (*User, error) {
    if id <= 0 {
        return nil, fmt.Errorf("findUser: invalid id %d", id)
    }
    user := db.Lookup(id)
    if user == nil {
        return nil, fmt.Errorf("findUser %d: %w", id, ErrNotFound)
    }
    return user, nil
}

// Caller
user, err := findUser(42)
if errors.Is(err, ErrNotFound) {
    // handle not-found specifically
}
Go🔧Core Language

Defer

`defer` schedules a function call to run when the surrounding function returns — regardless of whether it returns normally or via `panic`. Multiple defers execute in LIFO order. The primary use is resource cleanup: closing files, releasing locks, closing database connections.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
func copyFile(src, dst string) error {
    in, err := os.Open(src)
    if err != nil {
        return err
    }
    defer in.Close() // guaranteed to run when copyFile returns

    out, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer out.Close()

    _, err = io.Copy(out, in)
    return err
}
Go🔧Core Language

Closures

A closure is a function that captures variables from its enclosing scope by reference. In Go, closures are commonly used to create stateful function values, implement middleware, and build iterators. Since Go 1.22, range loop variables create fresh bindings per iteration, eliminating the classic closure-in-loop pitfall.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
func makeCounter() func() int {
    n := 0
    return func() int {
        n++
        return n
    }
}

c := makeCounter()
fmt.Println(c()) // 1
fmt.Println(c()) // 2
fmt.Println(c()) // 3

// Each counter has independent state
d := makeCounter()
fmt.Println(d()) // 1
Go🔧Core Language

Embedding

Go's alternative to inheritance is struct embedding. Embedding a type promotes its methods to the outer struct, allowing composition-based reuse without the complexity of class hierarchies. The embedded type remains accessible by its type name if needed.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
type Animal struct {
    Name string
}
func (a Animal) Speak() string { return a.Name + " makes a sound" }

type Dog struct {
    Animal           // embedded — Dog inherits Speak
    Breed string
}
func (d Dog) Speak() string { return d.Name + " barks" } // override

d := Dog{Animal: Animal{Name: "Rex"}, Breed: "Labrador"}
fmt.Println(d.Speak())       // Rex barks
fmt.Println(d.Animal.Speak()) // Rex makes a sound
fmt.Println(d.Name)           // promoted field access
Go🔧Core Language

Constants and iota

Constants are immutable values fixed at compile time, declared with `const`. The `iota` identifier is a constant generator that starts at 0 within a `const` block and increments by one for each line — it is the idiomatic way to create enumerated values. Untyped constants in Go have arbitrary precision and adapt to whatever type the context requires.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
const Pi = 3.14159 // untyped constant

type Weekday int

const (
    Sunday Weekday = iota // 0
    Monday                // 1
    Tuesday               // 2
    Wednesday             // 3
)

// iota with expressions — byte-size units
const (
    _  = iota             // skip 0
    KB = 1 << (10 * iota) // 1 << 10
    MB                    // 1 << 20
    GB                    // 1 << 30
)
Go🔧Core Language

Type Assertions

A type assertion extracts the concrete value stored inside an interface. `v, ok := i.(T)` is the safe two-value form — `ok` is `false` instead of panicking when the assertion fails. The single-value form `v := i.(T)` panics on mismatch, so reserve it for cases where the type is guaranteed.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
var i any = "hello"

// Safe form — never panics
s, ok := i.(string)
fmt.Println(s, ok) // hello true

n, ok := i.(int)
fmt.Println(n, ok) // 0 false

// Unsafe form — panics if i does not hold a string
s = i.(string)
Go🔧Core Language

Type Switches

A type switch is a control structure that branches on the dynamic type of an interface value. The special syntax `switch v := i.(type)` binds `v` to the concrete value in each case, with the right static type. It is the idiomatic way to handle a value that may be one of several types.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
func describe(i any) string {
    switch v := i.(type) {
    case int:
        return fmt.Sprintf("int: %d", v)
    case string:
        return fmt.Sprintf("string of length %d", len(v))
    case bool:
        return fmt.Sprintf("bool: %t", v)
    case nil:
        return "nil value"
    default:
        return fmt.Sprintf("unknown type %T", v)
    }
}
Go🔧Core Language

Variadic Functions

A variadic function accepts a variable number of trailing arguments of the same type, declared with `...T`. Inside the function the parameter is a slice `[]T`. An existing slice can be passed to a variadic parameter with the spread operator `slice...`.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

sum(1, 2, 3)        // 6 — pass individual args
sum()               // 0 — zero args is valid

nums := []int{4, 5, 6}
sum(nums...)        // 15 — spread an existing slice
Go🔧Core Language

Named Types and Type Definitions

`type Celsius float64` creates a new named type distinct from its underlying type — values are not interchangeable without an explicit conversion, which prevents whole classes of unit-mixing bugs. A *type alias* `type Byte = uint8` (with `=`) is different: it creates no new type, just another name. Named types can carry their own methods.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
type Celsius float64
type Fahrenheit float64

func (c Celsius) ToF() Fahrenheit {
    return Fahrenheit(c*9/5 + 32)
}

var c Celsius = 100
var f Fahrenheit = c.ToF() // 212

// var bad Fahrenheit = c   // compile error: cannot use c (Celsius) as Fahrenheit

type Byte = uint8 // alias — Byte and uint8 are the SAME type
Go🔧Core Language

init Functions

`init` is a special function that runs automatically before `main`, after all package-level variables are initialized. A package (or even a single file) may declare multiple `init` functions; they run in the order they appear. `init` takes no arguments and returns nothing — it cannot be called directly.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
var config map[string]string

func init() {
    // Runs once, before main, after package vars are set
    config = map[string]string{
        "env":  "production",
        "tier": "backend",
    }
}

func main() {
    fmt.Println(config["env"]) // production — already populated
}
Go🔧Core Language

The Blank Identifier

The blank identifier `_` is a write-only placeholder that discards a value. Go requires every declared variable and import to be used; `_` satisfies that rule when a value is intentionally unwanted — an unused return value, an unwanted range index, or an import needed only for its side effects.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
// Discard the index, keep the value
for _, v := range items {
    fmt.Println(v)
}

// Discard one of multiple returns
_, err := fmt.Println("hi")

// Import for side effects only (runs the package's init)
import _ "github.com/lib/pq"

// Compile-time interface check
var _ io.Writer = (*MyWriter)(nil)
Go🔧Core Language

Panic and Recover

`panic` stops normal execution and begins unwinding the stack, running deferred functions along the way. `recover`, called inside a deferred function, stops the unwinding and returns the panic value — letting a program regain control. Go's convention is to use errors for expected failures and reserve `panic` for truly unrecoverable situations (programmer bugs, impossible states).

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
func safeDivide(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered: %v", r)
        }
    }()
    result = a / b // panics if b == 0
    return result, nil
}

r, err := safeDivide(10, 0)
fmt.Println(r, err) // 0 recovered: runtime error: integer divide by zero
Go🔧Core Language

Labels and goto

Go supports statement labels used with `break`, `continue`, and `goto`. A labeled `break` or `continue` targets an *outer* loop — the cleanest way to exit a nested loop. `goto` jumps to a label within the same function; it is rarely used but occasionally clarifies error-cleanup paths or generated code.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
grid := [][]int{{1, 2}, {3, 4}, {5, 6}}
target := 4

Outer:
for i, row := range grid {
    for j, v := range row {
        if v == target {
            fmt.Printf("found at [%d][%d]\n", i, j)
            break Outer // exits BOTH loops
        }
    }
}

// goto — jump to a label in the same function
i := 0
loop:
if i < 3 {
    fmt.Println(i)
    i++
    goto loop
}
Python🔧Core Language

Variables and Types

Python is dynamically typed — a variable is just a name bound to an object; the type lives on the object, not the variable. Everything is an object: integers, strings, functions, classes. Python's built-in types include `int`, `float`, `complex`, `bool`, `str`, `bytes`, `list`, `tuple`, `dict`, `set`, `frozenset`, and `NoneType`. `type()` returns an object's type; `isinstance()` checks inheritance.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
x = 42            # int
y = 3.14          # float
z = 1 + 2j        # complex
s = "hello"       # str
b = b"bytes"      # bytes
t = (1, 2, 3)     # tuple (immutable)
lst = [1, 2, 3]   # list (mutable)
d = {"a": 1}      # dict
nothing = None    # NoneType

print(type(x), isinstance(x, int))   # <class 'int'> True
print(type(s), len(s))               # <class 'str'> 5
Python🔧Core Language

Control Flow

Python's control flow uses `if`/`elif`/`else`, `for` (iterating any iterable), `while`, `break`, `continue`, and `else` clauses on loops (the `else` runs if the loop wasn't broken out of). Conditional expressions (`x if cond else y`) provide ternary-style logic. Python has no `switch`; use `match`/`case` (3.10+) or dicts for dispatch tables.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
# for/else — runs else only if loop wasn't broken
def find_prime(n: int) -> bool:
    for i in range(2, int(n**0.5) + 1):
        if n % i == 0:
            break
    else:
        return True  # No divisor found
    return False

print([x for x in range(2, 20) if find_prime(x)])
# [2, 3, 5, 7, 11, 13, 17, 19]

# Conditional expression
score = 85
grade = "pass" if score >= 60 else "fail"
Python🔧Core Language

Functions — *args, **kwargs, and Defaults

Python functions support positional args, keyword args, defaults, variadic positional (`*args` → tuple), variadic keyword (`**kwargs` → dict), positional-only params (before `/`), and keyword-only params (after `*`). Functions are first-class: they can be passed, returned, and stored. The default value is evaluated once at definition time — a common gotcha with mutable defaults.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
def create_connection(host, port=5432, /, *, timeout=30, **options):
    # host, port: positional-only (before /)
    # timeout: keyword-only (after *)
    # options: extra keyword args
    print(f"Connecting to {host}:{port} (timeout={timeout})")
    print(f"Extra options: {options}")

create_connection("localhost", 5433, timeout=10, ssl=True, retry=3)

# *args and **kwargs forwarding
def wrapper(*args, **kwargs):
    print(f"args={args}, kwargs={kwargs}")
    return sum(args)

print(wrapper(1, 2, 3, label="sum"))
Python🔧Core Language

Lambda Functions

`lambda` creates an anonymous single-expression function. It's syntactically limited — no statements, no docstrings, no multiple expressions — by design. Lambdas are most useful as short callbacks to `sorted()`, `map()`, `filter()`, or `functools.reduce()`. For anything more complex, a named `def` is clearer.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
# Sort by multiple keys inline
employees = [
    {"name": "Alice", "dept": "eng", "salary": 120000},
    {"name": "Bob",   "dept": "eng", "salary": 95000},
    {"name": "Carol", "dept": "hr",  "salary": 80000},
]
ranked = sorted(employees, key=lambda e: (e["dept"], -e["salary"]))
for e in ranked:
    print(f"{e['dept']:5} {e['salary']:>7,}  {e['name']}")

# Lambda with map and filter
nums = range(1, 11)
evens_sq = list(map(lambda x: x**2, filter(lambda x: x % 2 == 0, nums)))
print(evens_sq)  # [4, 16, 36, 64, 100]
Python🔧Core Language

Comprehensions

Python has four comprehension forms: list `[expr for x in it if cond]`, dict `{k: v for ...}`, set `{expr for ...}`, and generator `(expr for ...)`. Comprehensions are faster than equivalent `for` loops (the loop variable is local, avoiding global lookup) and more readable. Nested comprehensions are legal but keep nesting shallow — beyond two levels, a named generator is clearer.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
# List, dict, set, generator comprehensions
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat   = [x for row in matrix for x in row]
evens  = {x for x in flat if x % 2 == 0}
sq_map = {x: x**2 for x in range(1, 6)}
gen    = (x**3 for x in range(1, 6))  # lazy

print(flat)              # [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(evens)             # {2, 4, 6, 8}
print(sq_map)            # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
print(list(gen))         # [1, 8, 27, 64, 125]
Python🔧Core Language

Generators and the `yield` Keyword

A generator function uses `yield` instead of `return` — calling it returns a generator object (an iterator). Execution pauses at each `yield` and resumes on the next `next()` call. Generators are memory-efficient for large sequences: instead of building a full list in memory, values are produced on demand. `yield from` delegates to a sub-generator, enabling generator pipelines and recursive generators.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
import itertools

def fibonacci():
    """Infinite Fibonacci generator."""
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# Take first 10 Fibonacci numbers
fibs = list(itertools.islice(fibonacci(), 10))
print(fibs)  # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

def read_in_chunks(file_obj, chunk_size=1024):
    """Generator that yields file content in fixed-size chunks."""
    while chunk := file_obj.read(chunk_size):
        yield chunk
Python🔧Core Language

Decorators

A decorator is a callable that takes a function and returns a (usually modified) function. The `@decorator` syntax is syntactic sugar for `func = decorator(func)`. Decorators are used for cross-cutting concerns: logging, timing, access control, caching, retry logic, and input validation — without modifying the decorated function's body. `functools.wraps` preserves the wrapped function's metadata (`__name__`, `__doc__`).

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
import functools
import time

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__} took {elapsed:.4f}s")
        return result
    return wrapper

def retry(max_attempts=3):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts:
                        raise
                    print(f"Attempt {attempt} failed: {e}. Retrying...")
        return wrapper
    return decorator

@timer
@retry(max_attempts=3)
def fetch_data(url: str) -> str:
    return f"data from {url}"

print(fetch_data("https://example.com"))
Python🔧Core Language

Classes and Object-Oriented Programming

Python uses class-based OOP with dynamic dispatch. `__init__` is the initializer (not a constructor — `__new__` allocates the object). Instance attributes are set on `self`; class attributes are shared across instances. `@classmethod` receives the class as first arg; `@staticmethod` receives neither. Properties (`@property`) give attribute-style access to computed values.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
class BankAccount:
    interest_rate = 0.02  # class attribute

    def __init__(self, owner: str, balance: float = 0.0):
        self._owner = owner
        self._balance = balance

    @property
    def balance(self) -> float:
        return self._balance

    def deposit(self, amount: float) -> None:
        if amount <= 0:
            raise ValueError("Deposit must be positive")
        self._balance += amount

    def withdraw(self, amount: float) -> None:
        if amount > self._balance:
            raise ValueError("Insufficient funds")
        self._balance -= amount

    @classmethod
    def from_dict(cls, data: dict) -> "BankAccount":
        return cls(data["owner"], data.get("balance", 0.0))

    def __repr__(self) -> str:
        return f"BankAccount(owner={self._owner!r}, balance={self._balance:.2f})"

acc = BankAccount.from_dict({"owner": "Alice", "balance": 1000})
acc.deposit(500)
acc.withdraw(200)
print(acc)  # BankAccount(owner='Alice', balance=1300.00)
Python🔧Core Language

Inheritance and MRO

Python supports multiple inheritance. The Method Resolution Order (MRO) determines which class's method is called when multiple bases define the same name. Python uses the C3 linearization algorithm — `ClassName.__mro__` shows the order. `super()` follows the MRO, making cooperative multiple inheritance possible (all `__init__` methods in the hierarchy can be called in sequence using `super()`).

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
class A:
    def who(self): return "A"

class B(A):
    def who(self): return f"B -> {super().who()}"

class C(A):
    def who(self): return f"C -> {super().who()}"

class D(B, C):
    def who(self): return f"D -> {super().who()}"

d = D()
print(d.who())      # D -> B -> C -> A
print(D.__mro__)    # (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
Python🔧Core Language

Dunder (Magic) Methods

Dunder methods (`__method__`) let classes integrate with Python's built-in operators and protocols. `__repr__` / `__str__` control string representation. `__len__`, `__getitem__`, `__setitem__`, `__iter__` implement the sequence and mapping protocols. `__enter__` / `__exit__` enable the `with` statement. `__eq__`, `__lt__`, `__hash__` control equality and hashing. `__call__` makes instances callable.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
class Vector:
    def __init__(self, *components):
        self._data = list(components)

    def __repr__(self): return f"Vector{tuple(self._data)}"
    def __len__(self):  return len(self._data)
    def __getitem__(self, i): return self._data[i]
    def __add__(self, other): return Vector(*(a + b for a, b in zip(self, other)))
    def __mul__(self, scalar): return Vector(*(x * scalar for x in self))
    def __eq__(self, other):  return self._data == list(other)

v1 = Vector(1, 2, 3)
v2 = Vector(4, 5, 6)
print(v1 + v2)    # Vector(5, 7, 9)
print(v1 * 3)     # Vector(3, 6, 9)
print(len(v1))    # 3
print(v1[1])      # 2
Python🔧Core Language

Context Managers and `with`

The `with` statement guarantees cleanup code runs even if exceptions occur. Any object implementing `__enter__` / `__exit__` is a context manager. The `contextlib.contextmanager` decorator lets you write generator-based context managers — `yield` is the body of the `with` block. `contextlib.ExitStack` composes a dynamic number of context managers.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
import contextlib
import time

@contextlib.contextmanager
def timed(label: str):
    start = time.perf_counter()
    try:
        yield
    finally:
        elapsed = time.perf_counter() - start
        print(f"{label}: {elapsed:.4f}s")

@contextlib.contextmanager
def managed_resource(name: str):
    print(f"Acquiring {name}")
    try:
        yield name.upper()
    finally:
        print(f"Releasing {name}")

with timed("demo"):
    with managed_resource("database") as res:
        print(f"Using {res}")
Python🔧Core Language

Exception Handling

Python exceptions are objects that inherit from `BaseException`. The hierarchy: `BaseException` → `Exception` → specific exceptions like `ValueError`, `TypeError`, `IOError`. The `try`/`except`/`else`/`finally` structure: `except` catches errors, `else` runs only if no exception occurred, `finally` always runs. Python 3.11+ added `ExceptionGroup` for handling multiple simultaneous exceptions (from `asyncio.TaskGroup`).

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
class InsufficientFundsError(ValueError):
    def __init__(self, balance: float, amount: float):
        self.balance = balance
        self.amount = amount
        super().__init__(f"Need {amount:.2f} but only have {balance:.2f}")

def safe_divide(a, b):
    try:
        result = a / b
    except ZeroDivisionError:
        return None
    except TypeError as e:
        raise ValueError(f"Arguments must be numbers: {e}") from e
    else:
        return result
    finally:
        pass  # cleanup always runs

print(safe_divide(10, 2))   # 5.0
print(safe_divide(10, 0))   # None

try:
    raise InsufficientFundsError(50.0, 100.0)
except InsufficientFundsError as e:
    print(e)  # Need 100.00 but only have 50.00
Python🔧Core Language

Iterables, Iterators, and the Iterator Protocol

Python's `for` loop works on any *iterable* — an object that implements `__iter__()` returning an *iterator*. An iterator implements `__next__()` raising `StopIteration` when exhausted. Built-ins like `list`, `dict`, `str`, `range`, files, and generators are all iterables. `iter(obj)` and `next(obj)` are the protocol functions. Understanding this protocol explains how `for`, comprehensions, `zip`, `map`, `filter`, and `itertools` all interoperate.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
class CountUp:
    """Iterator that counts from start to stop."""
    def __init__(self, start: int, stop: int):
        self._current = start
        self._stop = stop

    def __iter__(self):
        return self  # Iterator is its own iterable

    def __next__(self):
        if self._current >= self._stop:
            raise StopIteration
        val = self._current
        self._current += 1
        return val

for n in CountUp(1, 6):
    print(n, end=" ")  # 1 2 3 4 5

# Equivalent generator expression
gen = (n for n in range(1, 6))
print(list(gen))  # [1, 2, 3, 4, 5]
Python🔧Core Language

Type Hints (PEP 484)

Python 3.5 introduced type hints via PEP 484. They are not enforced at runtime — they're metadata for static type checkers (`mypy`, `pyright`, `ruff`). The `typing` module provides `List`, `Dict`, `Optional`, `Union`, `Callable`, `TypeVar`, `Generic`, `Protocol`. Python 3.10+ replaced `Optional[X]` with `X | None` and `Union[X, Y]` with `X | Y`. Python 3.12 added `type` statement for type aliases.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
from typing import TypeVar, Generic, Callable
from collections.abc import Iterable

T = TypeVar("T")
U = TypeVar("U")

def first(items: Iterable[T]) -> T | None:
    return next(iter(items), None)

def transform(items: list[T], fn: Callable[[T], U]) -> list[U]:
    return [fn(item) for item in items]

class Stack(Generic[T]):
    def __init__(self) -> None:
        self._items: list[T] = []

    def push(self, item: T) -> None:
        self._items.append(item)

    def pop(self) -> T:
        return self._items.pop()

stack: Stack[int] = Stack()
stack.push(1)
stack.push(2)
print(stack.pop())  # 2
print(first([10, 20, 30]))  # 10
print(transform(["a", "bb", "ccc"], len))  # [1, 2, 3]
Python🔧Core Language

Walrus Operator `:=` (Python 3.8+)

The walrus operator (`:=`, PEP 572) assigns a value to a variable and returns it in a single expression. It's most useful inside `while` conditions (avoiding a duplicate read), comprehension conditions (capturing an intermediate computed value), and `if` statements (avoiding re-computation). It makes imperative "compute, check, use" patterns more concise without sacrificing readability when used judiciously.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
import re

# Walrus in a while loop — read chunks until EOF
def count_chars(text: str, chunk_size: int = 4) -> int:
    total = 0
    pos = 0
    while chunk := text[pos:pos + chunk_size]:
        total += len(chunk)
        pos += chunk_size
    return total

print(count_chars("Hello, World!"))  # 13

# Walrus in comprehension — avoid recomputing expensive fn
data = [1, -2, 3, -4, 5]
results = [y for x in data if (y := x * x) > 4]
print(results)  # [4, 9, 16, 25] — wait, condition is y > 4
# => [9, 16, 25]

# Walrus in regex match
text = "Order #12345 placed"
if m := re.search(r"#(\d+)", text):
    print(f"Order ID: {m.group(1)}")  # Order ID: 12345
Python🔧Core Language

Structural Pattern Matching (`match`/`case`, Python 3.10+)

`match`/`case` (PEP 634) is not a simple switch — it performs structural decomposition. Patterns can match literals, variable capture (`case Point(x, y):`), sequences (`case [first, *rest]:`), mappings (`case {"action": action}:`), class patterns, OR patterns (`|`), and guards (`if condition`). Captured variables are bound in the case body. The `_` wildcard matches anything without binding.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

def classify_point(p):
    match p:
        case Point(x=0, y=0):
            return "origin"
        case Point(x=0, y=y):
            return f"on y-axis at {y}"
        case Point(x=x, y=0):
            return f"on x-axis at {x}"
        case Point(x=x, y=y) if x == y:
            return f"on diagonal at {x}"
        case Point(x=x, y=y):
            return f"point ({x}, {y})"

for pt in [Point(0, 0), Point(0, 5), Point(3, 0), Point(4, 4), Point(1, 2)]:
    print(classify_point(pt))
Python🔧Core Language

Dataclasses (Python 3.7+)

`@dataclass` auto-generates `__init__`, `__repr__`, `__eq__` (and optionally `__lt__`, `__hash__`, `__post_init__`) from class-level annotations. With `frozen=True` the class is immutable (and hashable). `field()` controls defaults, whether a field appears in `__repr__`, and `default_factory` for mutable defaults. Python 3.10 added `slots=True` for automatic `__slots__` (lower memory, faster attribute access).

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
from dataclasses import dataclass, field
from typing import ClassVar

@dataclass(order=True, frozen=True)
class Version:
    major: int
    minor: int
    patch: int = 0
    _registry: ClassVar[list] = []  # ClassVar excluded from __init__

    def __str__(self) -> str:
        return f"{self.major}.{self.minor}.{self.patch}"

@dataclass
class Package:
    name: str
    version: Version
    dependencies: list[str] = field(default_factory=list)
    _internal: str = field(default="", repr=False)

v1 = Version(3, 11)
v2 = Version(3, 12)
print(v1 < v2)   # True (order=True generates __lt__)
print(str(v1))   # 3.11.0

pkg = Package("requests", v2, ["certifi", "urllib3"])
print(pkg)
# Package(name='requests', version=3.12.0, dependencies=['certifi', 'urllib3'])
Python🔧Core Language

f-Strings and String Formatting

f-strings (PEP 498, Python 3.6+) embed expressions in string literals: `f"{expr}"`. They call `__format__` on the object with an optional format spec after `:`. Python 3.12 removed the restriction that f-string expressions couldn't contain the same quote type as the outer string. `=` suffix (`f"{value=}"`) prints both the expression text and its value — a debug shorthand.

beginnerflip for code
1
2
3
4
5
6
7
8
9
10
import math
from datetime import datetime

# Format spec: [[fill]align][sign][#][0][width][grouping][.prec][type]
pi      = math.pi
salary  = 123456.789
now     = datetime.now()

print(f"{pi:.4f}")            # 3.1416
print(f"{salary:>15,.2f}")    # 123,456.79 (right-aligned, 15 wide)
print(f"{42:#010b}")          # 0b00101010 (binary with prefix, 10 wide)
print(f"{now:%Y-%m-%d}")      # 2024-01-15 (date format)

# Debug shorthand (Python 3.8+)
x = [1, 2, 3]
print(f"{x=}")                # x=[1, 2, 3]

# Nested f-strings and Python 3.12 same-quote expressions
items = ["a", "b", "c"]
print(f"{'|'.join(items)}")   # a|b|c (Python 3.12+: same quote inside)