iZONE

Python Cheatsheet

A practical Python guide covering data structures, decorators, async, and type hints.

Resources

Basics: Types

Python figures out types automatically — you don't have to declare them. But you can add type hints for clarity and better editor help.

the five core types

Python

age    = 30     # int — whole number
price  = 9.99   # float — decimal
name   = "Alice" # str — text
active = True   # bool — True or False
empty  = None   # None — no value

# check the type
type(age)             # <class 'int'>
type(name)            # <class 'str'>

# check if it's a specific type
isinstance(age, int)  # True

type hints

Python

age:  int = 30
name: str = "Alice"

# hint parameters and return value
def greet(name: str) -> str:
    return f"Hello, {name}"

def add(a: int, b: int = 0) -> int:
    return a + b

# value that could be string OR None
def find_user(id: int) -> str | None:
    ...   # ... means "not implemented yet"

Type hints are optional but strongly recommended — they make bugs easier to spot before running your code.

converting between types

Python

int("42")        # 42
float("3.14")    # 3.14
str(100)         # "100"

# falsy → False, everything else → True
bool(0)          # False
bool("")         # False
bool("hello")    # True

# between collections
list((1, 2, 3))  # [1, 2, 3]
tuple([1, 2, 3]) # (1, 2, 3)

Python never converts types for you — if you need a number from a string, use `int()` or `float()` yourself.

Strings

Strings are text. Python has excellent string tools — and f-strings make it easy to embed variables directly inside text.

f-strings

Python

name  = "Alice"
score = 95.678

f"Hello, {name}!"
# "Hello, Alice!"

f"Score: {score:.2f}"
# "Score: 95.68"

f"2 + 2 = {2 + 2}"
# "2 + 2 = 4"

f"{'hello'.upper()}"
# "HELLO" — any expression works

Always use f-strings instead of joining strings with `+` — cleaner and much easier to read.

searching & checking

Python

s = "  Hello, World!  "

# transform
s.upper()
# "  HELLO, WORLD!  "

s.lower()
# "  hello, world!  "

s.strip()
# "Hello, World!" — removes spaces both ends

s.replace("World", "Python")
# "  Hello, Python!  "

# search
s.startswith("  Hello") # True
s.endswith("!")         # True

s.find("World")
# 8 — position of first match
# returns -1 if not found

"World" in s            # True

String methods never change the original — they always return a new string.

split & join

Python

# split — break into a list
"Hello, World".split(", ")
# ["Hello", "World"]

"a,b,c".split(",")
# ["a", "b", "c"]

# join — combine a list into a string
", ".join(["a", "b", "c"])
# "a, b, c"

"-".join(["2024", "01", "15"])
# "2024-01-15"

string slicing

Python

s = "Hello, World!"

s[0]    # "H" — first character
s[-1]   # "!" — last character

s[0:5]  # "Hello"
s[7:]   # "World!" — index 7 to end
s[:5]   # "Hello" — start to index 5

# reverse the whole string
s[::-1] # "!dlroW ,olleH"

Numbers & Math

Python handles whole numbers and decimals with no surprises. The math module gives you everything else.

basic operations

Python

10 + 3   # 13
10 - 3   # 7
10 * 3   # 30

10 / 3   # 3.333.. — always float
10 // 3  # 3 — integer division
10 % 3   # 1 — remainder

2 ** 10  # 1024 — power
abs(-5)  # 5 — remove minus sign

`/` always returns a float — even `10 / 2` gives `5.0`. Use `//` if you need a whole number.

rounding and formatting

Python

import math

round(3.14159, 2) # 3.14
math.floor(3.9)   # 3 — always down
math.ceil(3.1)    # 4 — always up
math.sqrt(16)     # 4.0 — square root

# format as readable string
f"{3.14159:.2f}"  # "3.14"
f"{1000000:,}"    # "1,000,000"

random numbers

Python

import random

random.random()
# decimal between 0.0 and 1.0

random.randint(1, 10)
# whole number from 1 to 10 (inclusive)

random.choice(["a", "b", "c"])
# pick one random item

random.shuffle([1, 2, 3, 4])
# shuffle in place — changes the original

Lists

A list is an ordered collection of items. You can add, remove, search, and loop over them. Lists are Python's most used data structure.

create and access items

Python

fruits = ["apple", "banana", "cherry"]

fruits[0]   # "apple" — first item
fruits[-1]  # "cherry" — last item

fruits[1:3]
# ["banana", "cherry"] — slice

len(fruits) # 3

# create from a range
list(range(5))
# [0, 1, 2, 3, 4]

list(range(1, 10, 2))
# [1, 3, 5, 7, 9] — start, stop, step

add and remove items

Python

a = [1, 2, 3]

a.append(4)
# [1, 2, 3, 4]

a.insert(1, 99)
# [1, 99, 2, 3, 4]

a.remove(99)
# [1, 2, 3, 4] — removes first match

a.pop()
# removes & returns last item

a.pop(0)
# removes & returns at index 0

a.extend([4, 5])
# add multiple items

a.sort()    # sort in place
a.reverse() # reverse in place
a.clear()   # remove everything → []

useful built-in functions

Python

nums = [3, 1, 4, 1, 5, 9, 2]

min(nums)      # 1 — smallest
max(nums)      # 9 — largest
sum(nums)      # 25 — total

# new sorted list — original unchanged
sorted(nums)
# [1, 1, 2, 3, 4, 5, 9]

# is at least one item > 8?
any(x > 8 for x in nums)  # True

# are ALL items > 0?
all(x > 0 for x in nums)  # True

nums.count(1)  # 2 — how many times does 1 appear
nums.index(4)  # 2 — index of first 4

`sorted()` returns a new list and leaves the original unchanged. `list.sort()` changes the original in place.

Comprehensions & Generators

Comprehensions build collections in one line. Generators produce values one at a time instead of all at once — great for large data.

list comprehensions

Python

# [what_to_keep  for  item  in  collection  if  condition]

squares = [x**2 for x in range(5)]
# [0, 1, 4, 9, 16]

evens = [x for x in range(10) if x % 2 == 0]
# [0, 2, 4, 6, 8]

upper = [s.upper() for s in ["a", "b"]]
# ["A", "B"]

# same thing without comprehension
squares = []
for x in range(5):
    squares.append(x**2)

all four comprehension types

Python

nums = range(10)

# list → [ ]
squares = [x**2 for x in nums]

# dict → { key: value }
sq_map  = {x: x**2 for x in nums}

# set → { } — no duplicates
sq_set  = {x**2 for x in nums}

# generator → ( ) — lazy, one value at a time
sq_gen  = (x**2 for x in nums)

next(sq_gen) # 0 — get next value
list(sq_gen) # get all remaining

# all support conditions
evens = [x for x in nums if x % 2 == 0]

generator functions

Python

def fibonacci():
    a, b = 0, 1
    while True:
        yield a      # pause and return a
        a, b = b, a + b

fib = fibonacci()
next(fib) # 0
next(fib) # 1
next(fib) # 1
next(fib) # 2

# get the first 8 numbers
import itertools
list(itertools.islice(fibonacci(), 8))
# [0, 1, 1, 2, 3, 5, 8, 13]

Tuples & Sets

Tuples are like lists but can never be changed. Sets are like lists but every value must be unique.

tuples — a list that can't change

Python

point = (3, 4)
point[0]      # 3

# unpack into variables
x, y = point

# single-item — trailing comma required
single      = (42,)  # ✅ a tuple
not_a_tuple = (42)   # ❌ just the number 42

# return multiple values from a function
def min_max(numbers):
    return min(numbers), max(numbers)

low, high = min_max([3, 1, 4, 1, 5])
# low=1, high=5

A single-item tuple needs a trailing comma: `(42,)` — without it, `(42)` is just the number 42.

sets — a collection with no duplicates

Python

# duplicates removed automatically
s = {1, 2, 3, 2, 1}
# {1, 2, 3}

s.add(4)
s.remove(2)    # error if not found
s.discard(99)  # no error if missing
99 in s        # False — fast check

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

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

# remove duplicates from a list
unique = list(set([1, 2, 2, 3, 3]))
# [1, 2, 3]

Most common use: remove duplicates from a list with `list(set(your_list))`.

Dictionaries

A dictionary stores data as key-value pairs — like a real dictionary where you look up a word (key) to find its meaning (value).

create and access

Python

user = {
    "name":   "Alice",
    "age":    30,
    "active": True,
}

user["name"]
# "Alice"

user.get("email")
# None — no error if missing

user.get("email", "no email")
# "no email" — fallback value

"name" in user
# True — check if key exists

# add or update
user["email"] = "[email protected]"

user.update({"age": 31, "role": "admin"})
# update multiple at once

loop over a dictionary

Python

scores = {"Alice": 95, "Bob": 87, "Carol": 91}

# keys only
for name in scores:
    print(name)

# key + value (most common)
for name, score in scores.items():
    print(f"{name}: {score}")

# get as lists
scores.keys()
# dict_keys(["Alice", "Bob", "Carol"])

scores.values()
# dict_values([95, 87, 91])

scores.items()
# dict_items([("Alice", 95), ...])

dict comprehensions

Python

# map each number to its square
squares = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# filter — keep only scores above 90
scores = {"Alice": 95, "Bob": 87, "Carol": 91}
top = {
    name: s
    for name, s in scores.items()
    if s > 90
}
# {"Alice": 95, "Carol": 91}

merging two dictionaries

Python

defaults = {"theme": "light", "lang": "en"}
custom   = {"lang": "fr", "font": "mono"}

# Python 3.9+ — cleanest
merged = defaults | custom
# {"theme": "light", "lang": "fr", "font": "mono"}

# Python 3.5+ — also works
merged = {**defaults, **custom}
# same result

When merging, keys on the right overwrite keys on the left. Use `|` (Python 3.9+) — it's the cleanest syntax.

Control Flow

Python uses indentation to define blocks — no curly braces. Every if, for, and while block ends with a colon :.

if / elif / else

Python

score = 85

if score >= 90:
    grade = "A"
elif score >= 80:   # 'elif' not 'else if'
    grade = "B"
elif score >= 70:
    grade = "C"
else:
    grade = "F"

# one-line ternary
status = "pass" if score >= 60 else "fail"

for loops

Python

# loop over a list
for fruit in ["apple", "banana", "cherry"]:
    print(fruit)

# loop with index — enumerate()
for i, fruit in enumerate(["apple", "banana"]):
    print(i, fruit)
# 0 apple
# 1 banana

# loop a set number of times
for i in range(5):
    print(i)
# 0, 1, 2, 3, 4

# loop two lists together — zip()
names  = ["Alice", "Bob"]
scores = [95, 87]
for name, score in zip(names, scores):
    print(f"{name}: {score}")

while loops

Python

n = 0
while n < 5:
    n += 1
    if n == 3:
        continue   # skip this iteration
    if n == 5:
        break      # stop the loop
    print(n)
# prints 1, 2, 4

pass, break and continue

Python

# pass — empty block placeholder
def todo():
    pass   # ✅ no error — just a placeholder

class Empty:
    pass   # ✅ empty class

if True:
    pass   # ✅ empty if block

# break — exit immediately
for i in range(10):
    if i == 5:
        break
# stops at 4

# continue — skip to next
for i in range(5):
    if i == 2:
        continue
# prints 0, 1, 3, 4

match / case

Python

command = "quit"

match command:
    case "quit":
        print("Quitting...")
    case "help":
        print("Showing help...")
    case _:
        # _ is the default (like 'else')
        print(f"Unknown: {command}")

# match on shapes too
point = (1, 0)
match point:
    case (0, 0): print("Origin")
    case (x, 0): print(f"x-axis at {x}")
    case (0, y): print(f"y-axis at {y}")
    case (x, y): print(f"Point at {x}, {y}")

Functions

A function is a reusable block of code. Define it once with def, then call it as many times as you need.

function basics

Python

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

greet("Alice")
# "Hello, Alice!"

# default parameter
def power(base: float, exp: int = 2) -> float:
    return base ** exp

power(3)     # 9 — exp defaults to 2
power(2, 10) # 1024

*args and **kwargs

Python

# *nums → collects all args into a tuple
def total(*nums: float) -> float:
    return sum(nums)

total(1, 2, 3)        # 6
total(10, 20, 30, 40) # 100

# **info → collects keyword args into a dict
def show_info(**info):
    for key, value in info.items():
        print(f"{key}: {value}")

show_info(name="Alice", age=30)
# name: Alice
# age: 30

lambda

Python

# lambda arguments: expression
double = lambda x: x * 2
double(5) # 10

# most common use — sort by a specific field
people = [
    {"name": "Bob",   "age": 25},
    {"name": "Alice", "age": 30},
]
people.sort(key=lambda p: p["age"])
# sorted by age

closures

Python

def multiplier(factor):
    # 'factor' is remembered by the inner function
    def multiply(n):
        return n * factor
    return multiply

double = multiplier(2)
triple = multiplier(3)

double(5) # 10
triple(5) # 15

decorators

Python

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}...")
        result = func(*args, **kwargs)
        print("Done.")
        return result
    return wrapper

# same as: add = log(add)
@log
def add(a, b):
    return a + b

add(2, 3)
# Calling add...
# Done.
# returns 5

Classes & OOP

A class is a blueprint for creating objects. Python uses self to refer to the object itself — it's always the first parameter of every method.

basic class

Python

class Animal:
    def __init__(self, name: str, species: str):
        self.name    = name
        self.species = species

    def speak(self) -> str:
        return f"{self.name} makes a sound."

    def __str__(self) -> str:
        # what print() shows
        return self.name

cat = Animal("Mia", "Cat")
cat.speak()  # "Mia makes a sound."
print(cat)   # "Mia"

inheritance

Python

class Dog(Animal):
    def __init__(self, name: str, breed: str):
        super().__init__(name, "Dog")
        self.breed = breed

    def speak(self) -> str:
        # override parent method
        return f"{self.name} barks!"

    @classmethod
    def from_shelter(cls, name: str) -> "Dog":
        # called on the class, not an instance
        return cls(name, "Mixed")

    @staticmethod
    def is_domestic() -> bool:
        # doesn't need class or instance
        return True

rex = Dog("Rex", "Labrador")
rex.speak()               # "Rex barks!"
Dog.is_domestic()         # True
Dog.from_shelter("Buddy") # Dog(name="Buddy", breed="Mixed")

dataclasses

Python

from dataclasses import dataclass, field

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

@dataclass
class Config:
    host: str       = "localhost"
    port: int       = 8080
    tags: list[str] = field(default_factory=list)
    # mutable defaults need field()

p = Point(1.0, 2.0)
print(p)
# Point(x=1.0, y=2.0, z=0.0) — auto __repr__

p1 = Point(1, 2)
p2 = Point(1, 2)
p1 == p2
# True — auto __eq__ compares all fields

properties

Python

class Temperature:
    def __init__(self, celsius: float):
        self._celsius = celsius
        # _ means "private, don't touch directly"

    @property
    def celsius(self) -> float:
        return self._celsius
        # runs when you read .celsius

    @celsius.setter
    def celsius(self, value: float):
        if value < -273.15:
            raise ValueError("Below absolute zero!")
        self._celsius = value
        # runs when you write .celsius = something

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

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

Error Handling

When something goes wrong, Python raises an exception. Use try/except to handle it gracefully instead of crashing.

try / except / else / finally

Python

try:
    number = int(input("Enter a number: "))
    result = 10 / number

except ValueError:
    # user typed text, not a number
    print("That's not a valid number.")

except ZeroDivisionError:
    print("You can't divide by zero.")

except Exception as e:
    # catch-all for anything unexpected
    print(f"Something went wrong: {e}")

else:
    # runs ONLY if no exception occurred
    print(f"Result: {result}")

finally:
    # ALWAYS runs
    print("Done.")

custom exceptions

Python

class ValidationError(Exception):
    def __init__(self, field: str, message: str):
        super().__init__(message)
        self.field = field

try:
    raise ValidationError(
        "email",
        "Email format is invalid"
    )
except ValidationError as e:
    print(f'Problem with "{e.field}": {e}')
    # Problem with "email": Email format is invalid

with statement — automatic cleanup

Python

# file closes automatically — even if error occurs
with open("data.txt", "r") as f:
    content = f.read()
# file is closed here

# custom context manager
from contextlib import contextmanager

@contextmanager
def timer():
    import time
    start = time.time()
    yield   # code inside 'with' runs here
    print(f"Took {time.time() - start:.2f}s")

with timer():
    do_something_slow()
# prints how long it took

File I/O

Python makes reading and writing files simple. Always use with open() — it guarantees the file closes properly even if something goes wrong.

reading and writing text files

Python

# read whole file
with open("notes.txt", "r", encoding="utf-8") as f:
    content = f.read()

# read line by line
with open("notes.txt") as f:
    for line in f:
        print(line.strip())
        # .strip() removes the newline

# write — creates or OVERWRITES
with open("output.txt", "w", encoding="utf-8") as f:
    f.write("Hello\n")
    f.writelines(["line1\n", "line2\n"])

# append — adds to the end
with open("log.txt", "a") as f:
    f.write("New log entry\n")

'w' mode overwrites the entire file — use 'a' if you want to keep existing content.

JSON files

Python

import json

data = {"name": "Alice", "scores": [95, 87, 92]}

# save to file
with open("data.json", "w") as f:
    json.dump(data, f, indent=2)
    # indent=2 makes it human-readable

# load from file
with open("data.json") as f:
    loaded = json.load(f)

# without a file
text   = json.dumps(data)
# dict → JSON string

parsed = json.loads('{"x": 1}')
# JSON string → dict

pathlib — modern file paths

Python

from pathlib import Path

p = Path("data/notes.txt")

p.exists()  # True / False
p.is_file() # True
p.suffix    # ".txt"
p.stem      # "notes"
p.parent    # Path("data")
p.name      # "notes.txt"

# join paths safely — works everywhere
base   = Path("project")
config = base / "config" / "settings.json"

# read and write directly
config.read_text(encoding="utf-8")
config.write_text("content", encoding="utf-8")

# list all Python files
for f in Path(".").glob("*.py"):
    print(f)

Async & Await

Async lets your program do other things while waiting for slow operations — like network requests — without freezing or needing multiple threads.

async / await basics

Python

import asyncio

async def fetch_data(url: str) -> str:
    await asyncio.sleep(1)
    # simulate waiting for a server
    return f"Data from {url}"

async def main():
    result = await fetch_data("https://api.example.com")
    print(result)

asyncio.run(main())

run multiple tasks at the same time

Python

import asyncio

async def task(name: str, delay: float):
    await asyncio.sleep(delay)
    return f"{name} finished"

async def main():
    # ❌ sequential — 1.0 + 0.5 + 0.8 = 2.3s total
    # r1 = await task("A", 1.0)
    # r2 = await task("B", 0.5)

    # ✅ concurrent — all start at once, total = 1.0s
    results = await asyncio.gather(
        task("A", 1.0),
        task("B", 0.5),
        task("C", 0.8),
    )
    print(results)
    # ["A finished", "B finished", "C finished"]

asyncio.run(main())

Use `asyncio.gather()` whenever you have multiple independent async tasks — it runs them all at the same time.

aiohttp — async HTTP requests

Python

import aiohttp
import asyncio

async def get_json(url: str) -> dict:
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            response.raise_for_status()
            # raises error for 4xx/5xx
            return await response.json()

async def main():
    data = await get_json(
        "https://jsonplaceholder.typicode.com/users/1"
    )
    print(data["name"])

asyncio.run(main())

Modules & Packages

Every .py file is a module you can import. Python comes with a huge standard library — always check it before installing a package.

importing

Python

import math
from math import sqrt, pi
from math import sqrt as square_root
import os.path

math.sqrt(16)    # 4.0
sqrt(16)         # 4.0
square_root(16)  # 4.0

Never use `from module import *` — it pollutes your namespace and makes it hard to know where names come from.

the __name__ == '__main__' guard

Python

# mymodule.py

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

# only runs when: python mymodule.py
# does NOT run when: import mymodule
if __name__ == "__main__":
    print(greet("World"))
    # Hello, World!

collections module

Python

from collections import Counter, defaultdict, deque

# Counter — count item occurrences
words = ["apple", "banana", "apple", "cherry", "apple"]
Counter(words)
# Counter({'apple': 3, 'banana': 1, 'cherry': 1})

# defaultdict — no KeyError for missing keys
dd = defaultdict(list)
dd["fruits"].append("apple")
# missing key gets an empty list automatically

# deque — add/remove from front in O(1)
dq = deque([1, 2, 3])
dq.appendleft(0)
# [0, 1, 2, 3]

datetime & re modules

Python

from datetime import datetime, timedelta

now   = datetime.now()
later = now + timedelta(days=7)

now.strftime("%Y-%m-%d")
# "2024-01-15"

now.strftime("%d/%m/%Y %H:%M")
# "15/01/2024 14:30"

# re — regular expressions
import re

match = re.search(r"\d{4}-\d{2}-\d{2}", "Today is 2024-01-15")
if match:
    print(match.group())
    # "2024-01-15"

re.findall(r"\d+", "I have 3 cats and 12 dogs")
# ["3", "12"]

re.sub(r"\s+", "-", "hello   world")
# "hello-world"

Testing

pytest is the standard way to test Python code. Write functions that start with test_ and use assert to check that things work correctly.

install and first test

bash

pip install pytest

writing tests

Python

# test_math.py

def add(a, b):
    return a + b

def test_add():
    assert add(2, 3) == 5

def test_add_negative():
    assert add(-1, 1) == 0

def test_raises():
    import pytest
    with pytest.raises(ZeroDivisionError):
        1 / 0

# run tests
# pytest                — all tests
# pytest test_math.py   — one file
# pytest -v             — verbose
# pytest -k "test_add"  — by name

fixtures — reusable test setup

Python

import pytest

@pytest.fixture
def user():
    return {"name": "Alice", "age": 30, "active": True}

# receive fixture by name as a parameter
def test_user_name(user):
    assert user["name"] == "Alice"

def test_user_age(user):
    assert user["age"] == 30

def test_user_is_active(user):
    assert user["active"] is True

parametrize — run one test with many inputs

Python

import pytest

@pytest.mark.parametrize("a, b, expected", [
    (2,   3,  5),
    (0,   0,  0),
    (-1,  1,  0),
    (10, -5,  5),
])
def test_add(a, b, expected):
    assert a + b == expected
# pytest runs this 4 times — once per row

Type Hints & mypy

Type hints are optional labels that tell you and your tools what types a function expects and returns. mypy checks them without running your code.

modern type syntax (Python 3.10+)

Python

# before 3.10 — required imports
from typing import Union, Optional
def old(value: Union[int, str]) -> Optional[str]: ...

# Python 3.10+ — clean and simple
def process(value: int | str) -> str:
    return str(value)

def find(id: int) -> str | None:
    ...

# Python 3.9+ — built-in generics
def first(items: list[int]) -> int | None:
    return items[0] if items else None

def lookup(data: dict[str, int], key: str) -> int:
    return data[key]

If you're on Python 3.10+, use the `|` syntax — it's shorter and doesn't need any imports.

generics

Python

from typing import TypeVar, Generic

T = TypeVar("T")

# returns the same type it receives
def identity(value: T) -> T:
    return value

identity("hello") # returns str
identity(42)      # returns int

# typed class that works for any type
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()

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

TypedDict — typed dictionary shapes

Python

from typing import TypedDict, NotRequired

class User(TypedDict):
    id:    int
    name:  str
    email: NotRequired[str]
    # optional — can be missing

# mypy checks this matches User's shape
user: User = {"id": 1, "name": "Alice"}
# ✅ email is optional

user2: User = {
    "id": 2,
    "name": "Bob",
    "email": "b@b.com"
}
# ✅ with email

# user3: User = {"id": 3}
# ❌ missing required 'name'

Protocols — describe a shape without inheritance

Python

from typing import Protocol

class Drawable(Protocol):
    def draw(self) -> None: ...

class Circle:
    def draw(self) -> None:
        print("Drawing a circle")

class Square:
    def draw(self) -> None:
        print("Drawing a square")

# both work — no need to inherit from Drawable
def render(shape: Drawable) -> None:
    shape.draw()

render(Circle()) # ✅
render(Square()) # ✅

mypy — check types without running your code

bash

pip install mypy

# check a single file
mypy main.py

# check entire folder
mypy src/

# strict mode — catches even more
mypy --strict main.py

# mypy.ini (project root)
# [mypy]
# strict = True
# ignore_missing_imports = True
Was this helpful?

No login required to share feedback

More Cheatsheets

Keep your reference handy

Explore more zero-to-hero cheatsheets for the tools you use daily.