Skip to content

esrid/go-python-comparison

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 

Repository files navigation

Golang vs Python: Syntax Comparison

A comprehensive cheat-sheet comparing Go and Python (3.10+) syntax — fundamentals, OOP, type hints, error handling, async/concurrency, and modern features.

Note on paradigm: Go is statically typed, compiled, and designed for explicit concurrency. Python is dynamically typed (with optional type hints), interpreted, and multi-paradigm (imperative, OOP, functional). Both are known for readability and productivity.

Table of Contents


Naming Convention

Go Python
Local variables camelCase snake_case
Exported / Public PascalCase No prefix (no keyword)
Private / Internal camelCase (package-private) _single_underscore (convention)
Name-mangled private N/A __double_underscore (class-level)
Constants PascalCase or ALL_CAPS ALL_CAPS
Types / Classes / Structs PascalCase PascalCase
Functions / Methods camelCase snake_case
Modules / Packages lowercase lowercase or snake_case
Magic methods N/A __dunder__

In Go, visibility is controlled by casing: uppercase first letter = exported. There are no public/private keywords.

In Python, visibility is a convention only. _name means "internal, don't use from outside". __name in a class triggers name mangling (becomes _ClassName__name). Python has no true access control — it's all convention.

# Python
my_local = 1
MY_CONSTANT = 42
_internal_helper = "private by convention"

class MyClass:
    public_attr = 1
    _protected = 2   # convention: don't access from outside
    __private = 3    # name-mangled to _MyClass__private

    def public_method(self): pass
    def _protected_method(self): pass
    def __private_method(self): pass

Sources:


Module & Package

Go — every .go file declares a package; packages map to directories; go.mod defines the module root:

package main

package myapp

// All .go files in the same directory share the same package name

Python — a file is a module; a directory with __init__.py is a package:

# myapp/utils.py — this is the module myapp.utils
# myapp/__init__.py — this makes myapp/ a package
# myapp/sub/__init__.py — sub-package

# Entry point
if __name__ == "__main__":
    main()
Go Python
Module unit Package (directory) File (.py)
Package = directory Yes Directory + __init__.py
Dependency tool go mod pip + pyproject.toml / requirements.txt
Build tool go build python -m build, setuptools, hatch, uv
REPL N/A (third-party) python / ipython

Sources:


Import

Go:

import "fmt"
import (
    "fmt"
    "net/http"
    myhttp "net/http" // alias
    _ "image/png"    // side-effects only
)

Python:

# Import a module
import os
import sys

# Import specific names from a module
from os.path import join, exists
from typing import Optional, Union, List

# Alias
import numpy as np
import pandas as pd
from collections import defaultdict as dd

# Import all (avoid — pollutes namespace)
from math import *

# Relative imports (within a package)
from . import sibling_module
from .. import parent_module
from .utils import helper

# Conditional / lazy import (for optional dependencies)
try:
    import ujson as json
except ImportError:
    import json

# __future__ — enable newer syntax in older Python versions
from __future__ import annotations  # PEP 563: postponed evaluation of annotations

Key difference: Go unused imports are a compile error. Python unused imports are a style warning (flake8/ruff) but not an error.

Sources:


Basic Types

Category Go Python Notes
Boolean bool (true/false) bool (True/False) Python bools are subclass of int
Integer int, int8int64, uint int Python int is arbitrary precision
Float float32, float64 float 64-bit IEEE 754
Complex complex64, complex128 complex
String string (immutable UTF-8) str (immutable Unicode)
Bytes []byte bytes, bytearray
None/Nil nil (nilable types only) None
Any any (interface{}) object / Any (typing)
// Go: explicit types, zero values
var i int = 42
var f float64 = 3.14
var s string = "hello"
var b bool = true
# Python: dynamic typing, no zero values
i = 42
f = 3.14
s = "hello"
b = True
n = None

# Python has arbitrary precision integers
big = 10 ** 100  # no overflow!

# Complex numbers
c = 3 + 4j
c.real  # 3.0
c.imag  # 4.0

Sources:


Type Hints (Python)

Python is dynamically typed, but type hints (PEP 484, 3.5+) enable static analysis via tools like mypy, pyright, and ruff. They have no runtime effect unless explicitly used with typing.get_type_hints(). Go types are enforced by the compiler.

# Variable annotations (PEP 526)
name: str = "Alice"
age: int = 30
ratio: float = 0.5
active: bool = True

# Function annotations
def greet(name: str, times: int = 1) -> str:
    return (f"Hello, {name}! " * times).strip()

# Optional — value or None (Python 3.10+ shorthand: str | None)
from typing import Optional
def find_user(id: int) -> Optional[str]:  # old style
    ...

def find_user(id: int) -> str | None:     # Python 3.10+ union syntax
    ...

# Union types
def process(value: int | str | float) -> str:
    return str(value)

# Collections
from typing import List, Dict, Tuple, Set
def process_list(items: list[int]) -> list[str]:  # 3.9+: built-in generics
    return [str(i) for i in items]

# Callable
from typing import Callable
def apply(fn: Callable[[int, int], int], a: int, b: int) -> int:
    return fn(a, b)

# TypeVar — generics
from typing import TypeVar, Generic
T = TypeVar("T")

def first(items: list[T]) -> T:
    return items[0]

# Literal types
from typing import Literal
def set_direction(d: Literal["left", "right", "up", "down"]) -> None: ...

# TypedDict — typed dictionary
from typing import TypedDict
class UserDict(TypedDict):
    name: str
    age: int
    email: str | None

# Final — constant (no reassignment)
from typing import Final
MAX_SIZE: Final = 1024

# Protocol — structural subtyping (like Go interface)
from typing import Protocol
class Drawable(Protocol):
    def draw(self) -> None: ...

# ParamSpec and Concatenate (3.10+) — for decorator typing
from typing import ParamSpec, Concatenate
P = ParamSpec("P")

# type statement (3.12+) — new syntax for type aliases
type Vector = list[float]
type Matrix[T] = list[list[T]]

Sources:


Custom Type

Go:

// Distinct type (not interchangeable with base)
type Age int16
type UserID string

// Struct
type Person struct {
    Name string
    Age  Age
}

// Interface
type Writer interface {
    Write([]byte) (int, error)
}

Python:

from typing import NewType, TypeAlias

# NewType — creates a distinct type for static checkers only (no runtime cost)
UserId = NewType("UserId", int)
PostId = NewType("PostId", int)
user_id = UserId(42)

# type alias (Python 3.12+ syntax)
type Vector = list[float]

# Old style alias (pre-3.12)
Vector: TypeAlias = list[float]

# TypedDict — typed dictionary shape (not a class)
from typing import TypedDict

class Point(TypedDict):
    x: float
    y: float

p: Point = {"x": 1.0, "y": 2.0}

# Named tuple — immutable, typed, named fields
from typing import NamedTuple

class Person(NamedTuple):
    name: str
    age: int
    email: str | None = None  # default value

alice = Person("Alice", 30)
alice.name   # "Alice"
alice[0]     # "Alice" (also indexable as tuple)

# Class (see Class & Struct section)
class MyType:
    def __init__(self, value: int) -> None:
        self.value = value

Key difference: Go's type Age int16 creates a distinct type the compiler enforces. Python's NewType is only checked by static analysis tools (mypy/pyright) — at runtime it's just the underlying type.

Sources:


Enum

Go — uses iota inside a const block:

type Status int

const (
    Active   Status = iota // 0
    Inactive               // 1
    Deleted                // 2
)

type Color string

const (
    Red   Color = "red"
    Green Color = "green"
)

Python — first-class Enum class:

from enum import Enum, IntEnum, StrEnum, Flag, auto

# Basic enum
class Status(Enum):
    ACTIVE   = "active"
    INACTIVE = "inactive"
    DELETED  = "deleted"

Status.ACTIVE         # <Status.ACTIVE: 'active'>
Status.ACTIVE.value   # "active"
Status.ACTIVE.name    # "ACTIVE"
Status("active")      # Status.ACTIVE — lookup by value

# Integer enum (also an int — comparison works with plain ints)
class Priority(IntEnum):
    LOW    = 1
    MEDIUM = 2
    HIGH   = 3

Priority.HIGH > Priority.LOW  # True

# String enum (Python 3.11+ — also a str)
class Color(StrEnum):
    RED   = "red"
    GREEN = "green"
    BLUE  = "blue"

Color.RED + " sky"   # "red sky"

# auto() — auto-assign values
class Direction(Enum):
    NORTH = auto()
    EAST  = auto()
    SOUTH = auto()
    WEST  = auto()

# Flag — combinable bit flags (like Go's const iota bit flags)
class Permission(Flag):
    READ    = auto()  # 1
    WRITE   = auto()  # 2
    EXECUTE = auto()  # 4
    ALL     = READ | WRITE | EXECUTE

perms = Permission.READ | Permission.WRITE
Permission.READ in perms  # True

# Iterate
for status in Status:
    print(status, status.value)

Key difference: Python enums are full classes — they can have methods. Go iota constants are bare integer/string values with no extra behavior.

Sources:


Variable

Go:

var name string = "Alice"
age := 30
const Pi = 3.14159

// Zero values always provided
var i int     // 0
var s string  // ""

Python:

# Assignment — no declaration keyword (unlike Go's var)
name = "Alice"
age = 30

# Type annotation only (no assignment)
count: int  # declares type, no value — accessing raises NameError

# Constants — convention only (Python has no true const)
MAX_SIZE = 1024
PI = 3.14159

# Multiple assignment
x, y = 1, 2
a = b = c = 0  # chain assignment

# Augmented assignment
x += 1
x -= 1
x *= 2
x //= 3  # floor division assignment
x **= 2  # power assignment

# Walrus operator := (Python 3.8+) — assign and use in expression
if n := len(data):
    print(f"data has {n} elements")

while chunk := file.read(8192):
    process(chunk)

# Global and nonlocal
x = 10

def outer():
    y = 20
    def inner():
        nonlocal y  # modify outer's y
        global x    # modify module-level x
        y = 30
        x = 99

Key difference: Python has no zero values — an unassigned variable name raises NameError. Go always initializes to zero.

Sources:


Function / Method

Go:

func greet(name string) string {
    return "Hello, " + name
}

func divide(a, b float64) (float64, error) {
    if b == 0 { return 0, errors.New("division by zero") }
    return a / b, nil
}

func sum(nums ...int) int {
    total := 0
    for _, n := range nums { total += n }
    return total
}

// Method
type Circle struct{ Radius float64 }
func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }

Python:

# Basic function
def greet(name: str) -> str:
    return f"Hello, {name}"

# Default parameters
def create_user(name: str, role: str = "user", active: bool = True) -> dict:
    return {"name": name, "role": role, "active": active}

# *args (variadic positional) and **kwargs (variadic keyword)
def log(*args: str, level: str = "INFO", **kwargs: str) -> None:
    print(f"[{level}]", *args, kwargs)

log("message", "extra", level="DEBUG", tag="auth")

# Keyword-only arguments (after *)
def connect(host: str, *, port: int = 80, timeout: float = 30.0) -> None:
    ...

connect("example.com", port=443)   # ok
# connect("example.com", 443)      # TypeError — port is keyword-only

# Positional-only arguments (before /) — Python 3.8+
def make_point(x: float, y: float, /) -> tuple[float, float]:
    return x, y

make_point(1.0, 2.0)              # ok
# make_point(x=1.0, y=2.0)       # TypeError — x, y are positional-only

# Multiple return values (returns a tuple)
def min_max(nums: list[int]) -> tuple[int, int]:
    return min(nums), max(nums)

lo, hi = min_max([3, 1, 4, 1, 5])

# Functions are first-class objects
def apply(fn, items):
    return [fn(x) for x in items]

apply(str.upper, ["a", "b"])  # ["A", "B"]

# Method in a class
class Circle:
    def __init__(self, radius: float) -> None:
        self.radius = radius

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

    @classmethod
    def unit(cls) -> "Circle":
        return cls(1.0)

    @staticmethod
    def validate(r: float) -> bool:
        return r > 0

Key differences:

  • Python has no named return values (use a tuple). Go has multiple return values and named returns.
  • Python supports keyword arguments, keyword-only, and positional-only params. Go does not have keyword arguments (except via struct options pattern).
  • Python functions are objects that can have attributes. Go functions are values but not objects.

Sources:


If Statement

Go — braces required, no parentheses around condition:

if x > 0 {
    fmt.Println("positive")
} else if x == 0 {
    fmt.Println("zero")
} else {
    fmt.Println("negative")
}

// Init statement
if x := compute(); x < 0 {
    fmt.Println("negative:", x)
}

Python — uses indentation (no braces); elif keyword:

if x > 0:
    print("positive")
elif x == 0:
    print("zero")
else:
    print("negative")

# Ternary / conditional expression
label = "positive" if x > 0 else ("zero" if x == 0 else "negative")

# Walrus in if
if (n := len(data)) > 10:
    print(f"too many items: {n}")

# Truthiness — Python evaluates many types as boolean
if my_list:      # True if list is non-empty
    ...
if my_dict:      # True if dict is non-empty
    ...
if name:         # True if string is non-empty
    ...

Sources:


Loops

Go — one loop keyword: for:

for i := 0; i < 10; i++ { fmt.Println(i) }
for sum < 1000 { sum += sum }
for { break }
for i, v := range []int{1, 2, 3} { fmt.Println(i, v) }

Pythonfor and while:

# for...in — iterate over any iterable
for i in range(10):
    print(i)

for v in [1, 2, 3]:
    print(v)

# enumerate — index + value (like Go's range)
for i, v in enumerate(["a", "b", "c"]):
    print(i, v)

# zip — iterate multiple iterables together
for name, age in zip(names, ages):
    print(name, age)

# while
i = 0
while i < 10:
    i += 1

# for...else — else block runs if loop completes without break
for item in collection:
    if matches(item):
        break
else:
    print("no match found")

# range variants
range(10)          # 0–9
range(1, 11)       # 1–10
range(0, 10, 2)    # 0, 2, 4, 6, 8 (step=2)
range(10, 0, -1)   # 10, 9, 8, ..., 1 (reverse)

# reversed and sorted
for v in reversed([1, 2, 3]):
    print(v)

for v in sorted([3, 1, 2]):
    print(v)

# continue and break work as in Go
for i in range(10):
    if i % 2 == 0:
        continue
    if i > 7:
        break
    print(i)

Sources:


Match Statement (Python 3.10+)

Python's match is structural pattern matching — much more powerful than Go's switch. The closest Go feature is a type switch combined with struct field access.

Go:

switch v := x.(type) {
case int:
    fmt.Println("int:", v)
case string:
    fmt.Println("string:", v)
}

Python:

# Basic match (like switch)
match command:
    case "quit":
        quit()
    case "help":
        show_help()
    case _:        # wildcard (default)
        print(f"Unknown: {command}")

# Match with value capture
match point:
    case (0, 0):
        print("origin")
    case (0, y):
        print(f"y-axis at {y}")
    case (x, 0):
        print(f"x-axis at {x}")
    case (x, y):
        print(f"point at ({x}, {y})")

# Match on type (like Go type switch)
match value:
    case int(n) if n > 0:
        print(f"positive int: {n}")
    case str(s):
        print(f"string: {s}")
    case list() as lst if len(lst) > 0:
        print(f"non-empty list: {lst}")

# Struct/class pattern matching
from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

match shape:
    case Point(x=0, y=0):
        print("origin")
    case Point(x=x, y=y):
        print(f"point at {x}, {y}")

# OR patterns
match status:
    case 400 | 401 | 403:
        print("client error")
    case 500 | 502 | 503:
        print("server error")

# Sequence patterns
match command.split():
    case ["quit"]:
        quit()
    case ["go", direction]:
        go(direction)
    case ["go", direction, distance]:
        go(direction, int(distance))
    case ["drop", *objects]:
        drop_all(objects)

# Mapping patterns (dict)
match config:
    case {"action": "push", "target": target}:
        push(target)
    case {"action": "pull", "source": src}:
        pull(src)

Sources:


Class & Struct

Go — no classes; uses structs + methods + interfaces:

type Person struct {
    Name string
    Age  int
}

func NewPerson(name string, age int) *Person {
    return &Person{Name: name, Age: age}
}

func (p Person) Greet() string {
    return "Hello, I'm " + p.Name
}

func (p *Person) Birthday() {
    p.Age++
}

// Embedding (composition)
type Employee struct {
    Person
    Company string
}

Python:

class Person:
    # Class variable (shared across all instances)
    species: str = "Homo sapiens"

    def __init__(self, name: str, age: int) -> None:
        # Instance variables
        self.name = name
        self.age = age

    # Instance method
    def greet(self) -> str:
        return f"Hello, I'm {self.name}"

    # String representation
    def __repr__(self) -> str:
        return f"Person(name={self.name!r}, age={self.age})"

    # Equality
    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Person):
            return NotImplemented
        return self.name == other.name and self.age == other.age

    # Class method (alternative constructor)
    @classmethod
    def from_dict(cls, data: dict) -> "Person":
        return cls(data["name"], data["age"])

    # Static method (no access to class or instance)
    @staticmethod
    def validate_age(age: int) -> bool:
        return 0 <= age <= 150

# Inheritance
class Employee(Person):
    def __init__(self, name: str, age: int, company: str) -> None:
        super().__init__(name, age)  # call parent __init__
        self.company = company

    def greet(self) -> str:  # override
        return f"{super().greet()} from {self.company}"

# Multiple inheritance (Python supports it; Go does not)
class Flyable:
    def fly(self) -> str: return "I can fly"

class FlyingEmployee(Employee, Flyable):
    pass

# Property — computed attribute (getter/setter)
class Temperature:
    def __init__(self, celsius: float) -> None:
        self._celsius = celsius

    @property
    def celsius(self) -> float:
        return self._celsius

    @celsius.setter
    def celsius(self, value: float) -> None:
        if value < -273.15:
            raise ValueError("Below absolute zero")
        self._celsius = value

    @property
    def fahrenheit(self) -> float:
        return self._celsius * 9 / 5 + 32

t = Temperature(100)
t.fahrenheit  # 212.0
t.celsius = 0

Key differences:

  • Go has no inheritance — use embedding. Python supports full class-based inheritance including multiple inheritance.
  • Go constructors are regular functions. Python uses __init__ (initializer, not allocator).
  • Python classes have magic methods (__repr__, __eq__, __len__, etc.) for operator overloading. Go uses interfaces for this.

Sources:


Interface & Protocol

Go — interfaces are satisfied implicitly (structural typing):

type Stringer interface {
    String() string
}

type Area interface {
    Area() float64
}

// Circle satisfies Stringer implicitly
type Circle struct{ Radius float64 }
func (c Circle) String() string { return fmt.Sprintf("Circle(r=%.2f)", c.Radius) }
func (c Circle) Area() float64  { return math.Pi * c.Radius * c.Radius }

Python — uses abstract base classes (abc.ABC) or typing.Protocol (structural):

from abc import ABC, abstractmethod
from typing import Protocol, runtime_checkable

# Abstract base class (nominal subtyping — must explicitly inherit)
class Shape(ABC):
    @abstractmethod
    def area(self) -> float: ...

    @abstractmethod
    def perimeter(self) -> float: ...

    # Concrete method in abstract class
    def describe(self) -> str:
        return f"Area: {self.area():.2f}, Perimeter: {self.perimeter():.2f}"

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

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

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

# Protocol — structural subtyping (like Go interface — no inheritance needed)
@runtime_checkable  # enables isinstance() checks
class Drawable(Protocol):
    def draw(self) -> None: ...

class Canvas:
    def draw(self) -> None:
        print("drawing on canvas")

# Canvas satisfies Drawable without inheriting it (structural typing)
def render(d: Drawable) -> None:
    d.draw()

render(Canvas())  # works!

# isinstance check (requires @runtime_checkable)
isinstance(Canvas(), Drawable)  # True

# Built-in protocols via collections.abc
from collections.abc import Sequence, Mapping, Iterable, Iterator, Callable

def process(items: Iterable[int]) -> list[int]:
    return [x * 2 for x in items]

Key differences:

  • Go interfaces are always structural (implicit). Python ABC requires explicit inheritance; Protocol is structural (like Go).
  • Python Protocol (PEP 544) is the direct equivalent of Go interfaces — both use duck typing without requiring explicit implements.

Sources:


Pointer & Reference

Go — explicit pointers:

x := 42
p := &x   // pointer to x
*p = 100  // modify through pointer; x is now 100

func increment(n *int) { *n++ }
increment(&x)

Python — no pointers; mutable objects passed by reference, immutable by value effect:

# Immutable types (int, str, float, tuple, frozenset): behave like pass-by-value
x = 42
def change(n):
    n = 100  # local rebinding, x unchanged
change(x)
print(x)  # 42

# Mutable types (list, dict, set, objects): shared reference
lst = [1, 2, 3]
def append_item(l):
    l.append(4)  # mutates the original list
append_item(lst)
print(lst)  # [1, 2, 3, 4]

# To avoid mutation, pass a copy
append_item(lst.copy())

# Shallow vs deep copy
import copy
shallow = copy.copy(nested_obj)    # copies top-level, shares nested
deep    = copy.deepcopy(nested_obj) # fully independent copy

# "Pass by assignment" — Python passes the object reference
# Rebinding the parameter doesn't affect the caller; mutating does

Sources:


List & Slice

Go:

s := []int{1, 2, 3}
s = append(s, 4)
sub := s[1:3]
len(s); cap(s)

Python:

# List (dynamic, heterogeneous, reference type)
lst = [1, 2, 3]
lst.append(4)           # add to end
lst.insert(0, 0)        # insert at index
lst.extend([5, 6])      # extend with iterable
lst.pop()               # remove and return last
lst.pop(0)              # remove and return at index
lst.remove(3)           # remove first occurrence of value
lst.index(2)            # find index of value
del lst[0]              # delete by index
del lst[1:3]            # delete slice

len(lst)

# Slicing (returns new list — doesn't share memory with original)
lst[1:4]      # elements 1, 2, 3
lst[:3]       # first 3
lst[2:]       # from index 2 to end
lst[-1]       # last element
lst[-3:]      # last 3 elements
lst[::2]      # every other element (step=2)
lst[::-1]     # reversed

# Spread / unpack
merged = [*lst, *other]

# Sort (in-place)
lst.sort()                        # ascending
lst.sort(reverse=True)            # descending
lst.sort(key=lambda x: x.name)   # by attribute

# Sorted (returns new list)
sorted_lst = sorted(lst)
sorted_lst = sorted(lst, key=len, reverse=True)

# Check membership
3 in lst        # True
3 not in lst    # False

# List operations
lst.count(2)    # count occurrences of 2
lst.reverse()   # reverse in place
lst.clear()     # empty the list
lst.copy()      # shallow copy

Key difference: Go slices share underlying array memory with the original. Python list slices create a new independent list.

Sources:


Map / Dictionary

Go:

m := map[string]int{"a": 1, "b": 2}
m["key"] = 42
delete(m, "key")
value, ok := m["key"]

Python:

# Create
d = {"name": "Alice", "age": 30}
d = dict(name="Alice", age=30)   # keyword form
d = dict([("name", "Alice"), ("age", 30)])  # from iterable of pairs

# Access
d["name"]               # "Alice" — KeyError if missing
d.get("missing")        # None (no KeyError)
d.get("missing", "default")  # "default"

# Update
d["name"] = "Bob"
d.update({"email": "bob@example.com", "age": 31})
d |= {"tag": "user"}    # merge in-place (Python 3.9+)

# Delete
del d["age"]
d.pop("age")            # remove and return; KeyError if missing
d.pop("age", None)      # remove and return None if missing

# Existence check
"name" in d             # True
"missing" in d          # False

# Iteration
for key in d:               # iterate keys (insertion-ordered since 3.7)
    print(key, d[key])

for key, value in d.items():
    print(key, value)

for key in d.keys(): ...
for value in d.values(): ...

# Merge (Python 3.9+)
merged = d1 | d2           # d2 wins on conflicts

# Default dict — auto-creates missing keys
from collections import defaultdict
counts = defaultdict(int)
for word in words:
    counts[word] += 1

# Counter — specialized dict for counting
from collections import Counter
counts = Counter(["a", "b", "a", "c", "a"])
counts.most_common(2)   # [("a", 3), ("b", 1)]

# OrderedDict — explicit ordering (redundant since 3.7, kept for .move_to_end)
from collections import OrderedDict
od = OrderedDict(a=1, b=2)
od.move_to_end("a")

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

Key differences:

  • Python dicts preserve insertion order since Python 3.7. Go maps do not.
  • Python's d["key"] raises KeyError on missing; Go returns zero value. Use d.get("key") for safe access.

Sources:


Set

Go has no built-in set type (use map[T]struct{}). Python has first-class set and frozenset:

// Go: set via map
set := map[string]struct{}{}
set["a"] = struct{}{}
_, ok := set["a"] // membership
delete(set, "a")
# Python: first-class set
s = {1, 2, 3}
s = set([1, 2, 3])

# Operations
s.add(4)
s.remove(3)      # KeyError if missing
s.discard(3)     # no error if missing

2 in s           # membership: True

# Set operations
a = {1, 2, 3}
b = {2, 3, 4}

a | b            # union: {1, 2, 3, 4}
a & b            # intersection: {2, 3}
a - b            # difference: {1}
a ^ b            # symmetric difference: {1, 4}

a.issubset(b)    # False
a.issuperset(b)  # False
a.isdisjoint(b)  # False — they share elements

# Frozenset — immutable, hashable (can be used as dict key or set element)
fs = frozenset([1, 2, 3])

Sources:


Tuple

Go has no built-in tuple type. Python tuples are immutable sequences:

// Go: use multiple return values or a struct
func minMax() (int, int) { return 0, 100 }
lo, hi := minMax()
# Python: tuple — immutable, ordered, heterogeneous
t = (1, "hello", 3.14)
t = 1, "hello", 3.14    # parentheses optional (tuple packing)

# Access
t[0]    # 1
t[-1]   # 3.14

# Unpack
x, y, z = t

# Swap (uses tuple packing/unpacking)
a, b = b, a

# Single-element tuple (trailing comma required)
single = (42,)
type(single)   # <class 'tuple'>
not_tuple = (42)
type(not_tuple)  # <class 'int'>

# Named tuple — adds field names (see Custom Type section)
from typing import NamedTuple

class Point(NamedTuple):
    x: float
    y: float

p = Point(1.0, 2.0)
p.x   # 1.0
p[0]  # 1.0 — also indexable

Sources:


Null Handling

Go — only nilable types can be nil; zero values always provided:

var p *int      // nil
var s []int     // nil (but len==0)

if p != nil {
    fmt.Println(*p)
}

PythonNone is a singleton object usable as any type's null value:

value = None

# Check for None
if value is None:        # recommended (identity check)
    print("no value")

if value is not None:
    print(value)

# Pitfall: avoid == None
if value == None:        # works but not idiomatic; is None is better

# Optional type hint
from typing import Optional
def find(id: int) -> Optional[str]:  # str | None
    ...

# Common patterns
result = maybe_none_value or "default"         # default if falsy (None, 0, "", [])
result = maybe_none_value if maybe_none_value is not None else "default"  # safe

# Walrus + None
if (user := get_user(id)) is not None:
    process(user)

Sources:


Error Handling

Go — errors are values; no exceptions:

f, err := os.Open("file.txt")
if err != nil {
    return fmt.Errorf("open: %w", err)
}

type NotFoundError struct{ ID int }
func (e *NotFoundError) Error() string {
    return fmt.Sprintf("ID %d not found", e.ID)
}

Python — uses exceptions (try/except/else/finally):

# Basic try/except/else/finally
try:
    result = int("not a number")
except ValueError as e:
    print(f"ValueError: {e}")
except (TypeError, KeyError):
    print("type or key error")
else:
    # Runs if no exception was raised
    print(f"result: {result}")
finally:
    # Always runs
    cleanup()

# Raise an exception
raise ValueError("invalid input")
raise RuntimeError("something went wrong") from original_error  # chained

# Custom exception
class NotFoundError(Exception):
    def __init__(self, resource: str, id: int) -> None:
        self.resource = resource
        self.id = id
        super().__init__(f"{resource} with id={id} not found")

try:
    raise NotFoundError("User", 42)
except NotFoundError as e:
    print(e.resource, e.id)

# Exception groups (Python 3.11+)
try:
    raise ExceptionGroup("multiple errors", [
        ValueError("bad value"),
        TypeError("bad type"),
    ])
except* ValueError as eg:
    print("handled ValueErrors:", eg.exceptions)
except* TypeError as eg:
    print("handled TypeErrors:", eg.exceptions)

# Context manager for cleanup (see Context Managers section)
with open("file.txt") as f:
    content = f.read()
# File is closed automatically

# Suppress specific exceptions
from contextlib import suppress
with suppress(FileNotFoundError):
    os.remove("maybe_missing.txt")

# Result pattern (optional, not idiomatic Python)
from typing import Union
def safe_divide(a: float, b: float) -> Union[float, Exception]:
    if b == 0:
        return ValueError("division by zero")
    return a / b

Key differences:

  • Go uses multiple return values for errors; Python uses exceptions.
  • Python exceptions propagate up the call stack automatically. Go errors must be explicitly checked and returned.
  • Python finally is equivalent to Go's defer for cleanup.

Sources:


Generics

Go — type parameters since Go 1.18:

func Map[T, U any](s []T, f func(T) U) []U { ... }

type Stack[T any] struct{ items []T }
func (s *Stack[T]) Push(item T) { s.items = append(s.items, item) }

Python — generics via TypeVar (pre-3.12) or type statement (3.12+):

from typing import TypeVar, Generic

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

# Generic function (type checker validates; runtime does not)
def first(items: list[T]) -> T:
    return items[0]

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

# Generic class (pre-3.12)
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()

    def peek(self) -> T:
        return self._items[-1]

s: Stack[int] = Stack()
s.push(1)
s.push(2)
s.pop()   # 2

# Python 3.12+ — built-in generic syntax (no TypeVar import needed)
def first[T](items: list[T]) -> T:
    return items[0]

class Stack[T]:
    def push(self, item: T) -> None: ...
    def pop(self) -> T: ...

# Bound TypeVar — constrain to specific types
from typing import TypeVar
Numeric = TypeVar("Numeric", int, float, complex)

def add(a: Numeric, b: Numeric) -> Numeric:
    return a + b

# Constrained with Protocol
from typing import Protocol, TypeVar

class Comparable(Protocol):
    def __lt__(self, other: "Comparable") -> bool: ...

C = TypeVar("C", bound=Comparable)

def min_val(a: C, b: C) -> C:
    return a if a < b else b

Key difference: Go generics are enforced at compile time. Python generics are only checked by static analysis tools (mypy, pyright) — at runtime, list[int] is just list and Stack[int] is just Stack.

Sources:


Closures & Anonymous Functions

Go:

double := func(x int) int { return x * 2 }

func makeCounter() func() int {
    count := 0
    return func() int { count++; return count }
}

Python:

# Lambda — single-expression anonymous function
double = lambda x: x * 2
add = lambda x, y: x + y

# Lambda in sort key
students.sort(key=lambda s: s.grade)

# Lambda with map/filter (list comprehensions are preferred)
doubled = list(map(lambda x: x * 2, [1, 2, 3]))
evens   = list(filter(lambda x: x % 2 == 0, range(10)))

# Closure — captures variables by reference
def make_counter():
    count = 0
    def counter():
        nonlocal count  # required to modify outer variable
        count += 1
        return count
    return counter

c = make_counter()
c()  # 1
c()  # 2

# Closure with default argument (capture by value idiom)
funcs = []
for i in range(3):
    funcs.append(lambda x, i=i: x + i)  # i=i captures current i by value
# Without i=i, all lambdas would capture the final value of i (2)

# Partial application via functools.partial
from functools import partial

def power(base, exp):
    return base ** exp

square = partial(power, exp=2)
cube   = partial(power, exp=3)
square(5)  # 25
cube(3)    # 27

Key differences:

  • Go closures capture by reference automatically. Python closures capture by reference but require nonlocal to rebind outer variables.
  • Python lambdas are limited to a single expression. Go function literals support full function bodies.
  • functools.partial in Python is the equivalent of Go's closure-based partial application pattern.

Sources:


Type Casting & Conversion

Go:

var i int = 42
f := float64(i)
s := string(65)  // "A"
b := []byte("hello")

Python:

# Numeric conversion
int("42")          # 42
int(3.9)           # 3 (truncates, not rounds)
float(42)          # 42.0
float("3.14")      # 3.14
complex(1, 2)      # (1+2j)

# String conversion
str(42)            # "42"
str(3.14)          # "3.14"
str(True)          # "True"
repr(obj)          # developer-friendly string (like Go's %v)

# Boolean
bool(0)            # False
bool("")           # False
bool([])           # False
bool(None)         # False
bool(1)            # True
bool("a")          # True
bool([0])          # True (non-empty list)

# bytes/str
"hello".encode("utf-8")           # b"hello"
b"hello".decode("utf-8")          # "hello"

# int <-> bytes
(255).to_bytes(2, byteorder="big")    # b'\x00\xff'
int.from_bytes(b'\x00\xff', "big")   # 255

# Type checking (not casting — Python is duck-typed)
isinstance(value, int)             # True if value is int or subclass
isinstance(value, (int, float))    # True if either
type(value) is int                 # True only for exact type (not subclass)

# No equivalent to Go's transmute — use struct / ctypes for bit reinterpretation
import struct
bits = struct.pack("f", 3.14)        # pack float to bytes
val  = struct.unpack("f", bits)[0]   # unpack bytes to float

Sources:


String Operations

Go:

s := "Hello, 世界"
len(s)                              // bytes
utf8.RuneCountInString(s)           // runes
strings.Contains(s, "世界")
strings.ToUpper(s)
strings.Split("a,b,c", ",")
strings.Join([]string{"a","b"}, ",")
fmt.Sprintf("x=%d", 42)

Python:

s = "Hello, 世界"

len(s)                           # 9 — character count (Unicode)
s.encode("utf-8")               # b'Hello, \xe4\xb8\x96\xe7\x95\x8c' — bytes

# Common methods
s.upper()
s.lower()
s.strip()
s.lstrip()
s.rstrip()
s.replace("Hello", "Hi")
s.split(", ")                    # ["Hello", "世界"]
", ".join(["Hello", "世界"])     # "Hello, 世界"
s.startswith("Hello")           # True
s.endswith("界")                 # True
"世界" in s                      # True (substring check)
s.find("世界")                   # 7 (-1 if not found)
s.index("世界")                  # 7 (ValueError if not found)
s.count("l")                    # 2
s.center(20, "*")               # "***** Hello, 世界 ****"
s.ljust(20, "-")
s.rjust(20, "-")
s.zfill(20)
s.isdigit()
s.isalpha()
s.isalnum()
s.isspace()

# f-strings (format strings) — Python 3.6+
name, age = "Alice", 30
f"Name: {name}, Age: {age}"
f"Pi: {3.14159:.2f}"           # "Pi: 3.14"
f"{name!r}"                    # repr of name: "'Alice'"
f"{name!s}"                    # str(name)
f"{value:>10}"                 # right-align in 10 chars
f"{num:,}"                     # thousands separator
f"{num:08b}"                   # 8-digit binary with zero-fill

# f-string debugging (Python 3.8+)
x = 42
f"{x=}"  # "x=42"

# Multiline strings
text = """
line 1
line 2
line 3
"""

# Raw strings (backslashes not processed)
path = r"C:\Users\Alice\Documents"
pattern = r"\d+\.\d+"

# String formatting alternatives
"Hello, %s! You are %d years old." % (name, age)  # old-style (avoid)
"Hello, {}! You are {} years old.".format(name, age)  # str.format
"Hello, {name}! You are {age} years old.".format(name=name, age=age)

# textwrap for long strings
import textwrap
wrapped = textwrap.fill(long_text, width=80)
dedented = textwrap.dedent(indented_text)

Key differences:

  • Python len(s) counts Unicode characters. Go len(s) counts bytes; use utf8.RuneCountInString(s) for characters.
  • Python f-strings are native, expressive, and evaluated at runtime. Go uses fmt.Sprintf with format verbs.
  • Python str methods return new strings (strings are immutable). Go strings package also returns new strings.

Sources:


Comprehensions

Go — no built-in comprehensions; use loops:

doubled := make([]int, len(nums))
for i, n := range nums { doubled[i] = n * 2 }

Python — list, dict, set, and generator comprehensions:

# List comprehension: [expression for item in iterable if condition]
squares = [x**2 for x in range(10)]
evens   = [x for x in range(20) if x % 2 == 0]
flat    = [x for row in matrix for x in row]  # nested (flatten)

# Dict comprehension
squared = {n: n**2 for n in range(1, 6)}
inverted = {v: k for k, v in d.items()}

# Set comprehension
unique_squares = {x**2 for x in [-2, -1, 0, 1, 2]}
# {0, 1, 4}

# Generator expression (lazy — memory efficient)
total = sum(x**2 for x in range(1_000_000))  # no intermediate list
first_even = next(x for x in data if x % 2 == 0)

# Walrus in comprehension (Python 3.8+)
results = [y for x in data if (y := expensive(x)) is not None]

# Conditional expression in comprehension
labels = ["even" if x % 2 == 0 else "odd" for x in range(10)]

Sources:


Decorators (Python only)

Decorators are functions that wrap another function or class to modify its behavior. They use the @ syntax. Go has no equivalent — the closest patterns are middleware functions and code generation.

import functools
from typing import Callable, TypeVar, ParamSpec

P = ParamSpec("P")
R = TypeVar("R")

# Basic decorator — wraps a function
def log(fn: Callable[P, R]) -> Callable[P, R]:
    @functools.wraps(fn)  # preserves function metadata
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        print(f"Calling {fn.__name__}")
        result = fn(*args, **kwargs)
        print(f"{fn.__name__} returned {result!r}")
        return result
    return wrapper

@log  # equivalent to: greet = log(greet)
def greet(name: str) -> str:
    return f"Hello, {name}"

greet("Alice")
# Calling greet
# greet returned 'Hello, Alice'

# Decorator factory (decorator with arguments)
def retry(times: int = 3, exceptions: tuple = (Exception,)):
    def decorator(fn: Callable[P, R]) -> Callable[P, R]:
        @functools.wraps(fn)
        def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
            for attempt in range(times):
                try:
                    return fn(*args, **kwargs)
                except exceptions as e:
                    if attempt == times - 1:
                        raise
                    print(f"Attempt {attempt + 1} failed: {e}")
        return wrapper
    return decorator

@retry(times=3, exceptions=(ConnectionError,))
def fetch_data(url: str) -> str:
    ...

# Class decorator
def singleton(cls):
    instances = {}
    @functools.wraps(cls)
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton
class Config:
    def __init__(self): self.value = "default"

# Method decorators (built-in)
class MyClass:
    class_var = 0

    @classmethod  # receives class as first arg (cls)
    def from_data(cls, data: dict) -> "MyClass":
        return cls()

    @staticmethod  # no self or cls
    def utility(x: int) -> int:
        return x * 2

    @property  # computed attribute
    def computed(self) -> int:
        return self.class_var * 2

# Common standard library decorators
from functools import cached_property, lru_cache
import dataclasses

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

    @cached_property  # computed once, then cached
    def area(self) -> float:
        import math
        return math.pi * self.radius ** 2

@lru_cache(maxsize=128)  # memoize function calls
def fibonacci(n: int) -> int:
    if n < 2: return n
    return fibonacci(n - 1) + fibonacci(n - 2)

@dataclasses.dataclass  # auto-generate __init__, __repr__, __eq__
class Point:
    x: float
    y: float
    z: float = 0.0

Go equivalent: Middleware functions (wrap http.Handler), higher-order functions, or code generation via go generate.

Sources:


Context Managers (Python only)

Context managers implement the __enter__/__exit__ protocol and are used with with statements. They are the Python equivalent of Go's defer — ensuring cleanup runs even if an exception occurs.

Go equivalent:

f, err := os.Open("file.txt")
if err != nil { return err }
defer f.Close()

mu.Lock()
defer mu.Unlock()

Python:

# File — built-in context manager
with open("file.txt", "r") as f:
    content = f.read()
# f is closed automatically (even if exception occurred)

# Multiple contexts in one with
with open("input.txt") as fin, open("output.txt", "w") as fout:
    fout.write(fin.read())

# contextlib — decorator/generator approach
from contextlib import contextmanager

@contextmanager
def timer(label: str):
    import time
    start = time.time()
    try:
        yield                          # code inside `with` runs here
    finally:
        elapsed = time.time() - start
        print(f"{label}: {elapsed:.3f}s")

with timer("my_operation"):
    expensive_computation()

# contextmanager with value
@contextmanager
def managed_connection(host: str):
    conn = connect(host)
    try:
        yield conn                     # as conn in `with ... as conn`
    except Exception:
        conn.rollback()
        raise
    finally:
        conn.close()

with managed_connection("db.example.com") as conn:
    conn.execute("SELECT 1")

# Custom context manager class
class Lock:
    def __enter__(self):
        print("acquiring lock")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("releasing lock")
        return False  # re-raise any exception

# contextlib.suppress — suppress specific exceptions
from contextlib import suppress
with suppress(FileNotFoundError, PermissionError):
    os.remove("maybe_missing.txt")

# contextlib.ExitStack — dynamic number of context managers
from contextlib import ExitStack
with ExitStack() as stack:
    files = [stack.enter_context(open(f)) for f in file_list]
    process_all(files)

Sources:


Generators & Iterators

Go has no generator syntax; iterators are implemented via channels or custom types. Python has first-class generator support:

# Generator function — uses yield
def countdown(n: int):
    while n > 0:
        yield n         # suspends here, resumes on next()
        n -= 1

for i in countdown(5):
    print(i)  # 5 4 3 2 1

# next() — manually advance
gen = countdown(3)
next(gen)   # 3
next(gen)   # 2
next(gen)   # 1
next(gen)   # StopIteration

# yield from — delegate to sub-generator
def chain(*iterables):
    for it in iterables:
        yield from it

list(chain([1,2], [3,4], [5]))  # [1, 2, 3, 4, 5]

# Generator with send() — coroutine-like
def accumulator():
    total = 0
    while True:
        value = yield total   # receives sent value
        if value is None:
            break
        total += value

acc = accumulator()
next(acc)        # prime the generator (advance to first yield)
acc.send(10)     # 10
acc.send(5)      # 15
acc.send(3)      # 18

# Infinite generator
def naturals(start: int = 1):
    while True:
        yield start
        start += 1

import itertools
first_ten = list(itertools.islice(naturals(), 10))

# itertools — composable iterator tools (Go has no equivalent module)
import itertools

itertools.count(1)           # infinite counter: 1, 2, 3, ...
itertools.cycle("ABCD")      # infinite cycle: A, B, C, D, A, B, ...
itertools.repeat(10, 3)      # 10, 10, 10
itertools.chain([1,2], [3,4])        # flatten
itertools.islice(gen, 5)             # first 5
itertools.takewhile(pred, it)        # take while predicate holds
itertools.dropwhile(pred, it)        # drop while predicate holds
itertools.groupby(sorted_data, key)  # group consecutive elements
itertools.product([1,2], [3,4])      # cartesian product
itertools.combinations("ABCD", 2)   # C(4,2) = 6 combinations
itertools.permutations("ABC", 2)    # P(3,2) = 6 permutations

Sources:


Dataclasses (Python only)

@dataclass auto-generates __init__, __repr__, __eq__, and optionally __hash__, __lt__, etc. It's Python's equivalent of Go structs — but more feature-rich. Go has no equivalent decorator.

from dataclasses import dataclass, field, asdict, astuple, replace
from typing import ClassVar

@dataclass
class Point:
    x: float
    y: float
    z: float = 0.0  # default value

    def distance_from_origin(self) -> float:
        return (self.x**2 + self.y**2 + self.z**2) ** 0.5

p = Point(1.0, 2.0)
p           # Point(x=1.0, y=2.0, z=0.0)  ← __repr__
p == Point(1.0, 2.0)  # True ← __eq__

# Immutable dataclass
@dataclass(frozen=True)
class FrozenPoint:
    x: float
    y: float
    # p.x = 5  would raise FrozenInstanceError

# Ordered comparisons
@dataclass(order=True)
class Student:
    gpa: float
    name: str

students = [Student(3.5, "Alice"), Student(3.9, "Bob")]
sorted(students)  # sorted by gpa first, then name

# Fields with defaults and post-init
@dataclass
class Config:
    host: str
    port: int = 8080
    tags: list[str] = field(default_factory=list)  # mutable default: use field()
    _hash: int = field(init=False, repr=False)      # computed, not in __init__
    MAX_PORT: ClassVar[int] = 65535                  # class variable, not field

    def __post_init__(self) -> None:
        # Runs after __init__ — for validation or computed fields
        if self.port > self.MAX_PORT:
            raise ValueError(f"port must be <= {self.MAX_PORT}")
        self._hash = hash((self.host, self.port))

# Utilities
asdict(p)         # {"x": 1.0, "y": 2.0, "z": 0.0}
astuple(p)        # (1.0, 2.0, 0.0)
replace(p, z=5.0) # Point(x=1.0, y=2.0, z=5.0) — like Go's struct literal update

Sources:


Async & Concurrency

Go — Goroutines & Channels

// Goroutines — lightweight green threads
go someFunction()

ch := make(chan int, 1)
go func() { ch <- 42 }()
v := <-ch

// select: multiplex on channels
select {
case v := <-ch1:
    handle(v)
case <-time.After(time.Second):
    timeout()
}

Python — asyncio & Threading

Python has two concurrency models: asyncio (cooperative, single-threaded) for I/O-bound tasks, and threading/multiprocessing for CPU-bound parallelism:

import asyncio

# async def — defines a coroutine (suspendable function)
async def fetch_user(id: int) -> dict:
    await asyncio.sleep(0.1)  # non-blocking wait
    return {"id": id, "name": "Alice"}

# Run a coroutine
asyncio.run(fetch_user(1))

# await — suspend until coroutine completes
async def main():
    user = await fetch_user(1)
    print(user)

# Run concurrently — asyncio.gather (like Promise.all)
async def fetch_all():
    results = await asyncio.gather(
        fetch_user(1),
        fetch_user(2),
        fetch_user(3),
    )
    return results

# TaskGroup (Python 3.11+) — structured concurrency
async def fetch_with_group():
    async with asyncio.TaskGroup() as tg:
        task1 = tg.create_task(fetch_user(1))
        task2 = tg.create_task(fetch_user(2))
    # Both tasks complete before this line
    return task1.result(), task2.result()

# Timeout
async def with_timeout():
    try:
        result = await asyncio.wait_for(slow_operation(), timeout=5.0)
    except asyncio.TimeoutError:
        print("operation timed out")

# Async generator
async def paginate(url: str):
    cursor = None
    while True:
        data = await fetch_page(url, cursor)
        for item in data["items"]:
            yield item
        cursor = data.get("next")
        if not cursor:
            break

async def consume():
    async for item in paginate("/api/items"):
        print(item)

# Async context manager
async def managed():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://example.com") as resp:
            return await resp.text()

# Queue — async producer-consumer (closest to Go channels)
async def producer(q: asyncio.Queue):
    for i in range(10):
        await q.put(i)
    await q.put(None)  # sentinel

async def consumer(q: asyncio.Queue):
    while (item := await q.get()) is not None:
        print(f"processing {item}")
        q.task_done()

async def pipeline():
    q: asyncio.Queue[int | None] = asyncio.Queue(maxsize=5)
    await asyncio.gather(producer(q), consumer(q))

# Threading — true parallel execution (GIL limits CPU parallelism for pure Python)
import threading

def worker(name: str):
    print(f"Thread {name} starting")

t = threading.Thread(target=worker, args=("Alice",))
t.start()
t.join()

# concurrent.futures — high-level threading/multiprocessing
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor

with ThreadPoolExecutor(max_workers=4) as executor:
    futures = [executor.submit(fetch_user, id) for id in range(10)]
    results = [f.result() for f in futures]

# Multiprocessing — true CPU parallelism (bypasses GIL)
with ProcessPoolExecutor(max_workers=4) as executor:
    results = list(executor.map(cpu_intensive, data))

Key differences:

  • Go goroutines are M:N green threads that run truly in parallel on multiple cores. Python's asyncio is single-threaded cooperative multitasking — only one coroutine runs at a time.
  • Python's GIL (Global Interpreter Lock) limits true CPU-bound parallelism in threads. Use multiprocessing or ProcessPoolExecutor for CPU-bound work.
  • Go channels are language primitives. Python uses asyncio.Queue for async producer-consumer patterns.
  • Go 1.21+ has sync.Map, channels, and select. Python has asyncio.Queue, asyncio.Event, asyncio.Lock, asyncio.Semaphore.

Sources:

go-python-comparison

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors