Référence

Référence Python

Syntaxe, exemples et explications courtes — des bases du langage aux schémas algorithmiques, classes et tests.

Bases du langage13

Variables and types
n = 42
ratio = 1.5
name = "Ada"
flag = True
nothing = None

The type comes from the value; no declaration needed. Check with type(x) or isinstance(x, int).

f-strings
f"{name} solved {n} tasks"
f"{ratio:.2f}"
f"{n:>5}"
f"{n=}"

Interpolate expressions into a string. :.2f rounds to two decimals, :>5 pads, {n=} prints name and value.

Truthiness
if items:
    ...
if not s:
    ...
bool(0), bool(""), bool([])   # False

Empty collections, 0, "" and None are falsy. Prefer `if items` over `if len(items) > 0`.

Multiple assignment
a, b = b, a
first, *rest = [1, 2, 3, 4]
x = y = 0

Swap without a temporary; *rest absorbs the remainder of the sequence.

Conditional expression
sign = "+" if n >= 0 else "-"

A one-line conditional. It yields a value, so it can be assigned or returned.

Floor division and modulo
7 // 2          # 3
7 % 2           # 1
divmod(7, 2)    # (3, 1)
-7 // 2         # -4

// floors rather than truncating: -7 // 2 is -4. divmod returns quotient and remainder at once.

Length with len
len([10, 20, 30])  # 3

`len()` is a built-in function that uses the `__len__` protocol, so it works consistently with different containers. Python has no `.len()` method.

Integer input
n = int(input())

`input()` always returns a string, even when the user enters a number. `int()` raises `ValueError` when the string is not a valid integer.

Type conversion and integer base
s = str(42)
n = int("42")
x = float("3.5")
h = int("ff", 16)

`str`, `int`, and `float` create values of the requested type, while the second argument of `int()` specifies the numeral base. An invalid string raises `ValueError`.

Type checks with isinstance
isinstance(True, int)  # True
type(True) is int  # False

`isinstance()` respects inheritance and is usually the better way to check a type. `type(x) is T` accepts only an exact type match.

None as a default value
def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

`None` is a safe sentinel when a mutable object must be created separately for every call. Check it with `is None`, not `== None`.

Identity versus equality
a = [1]
b = [1]
a == b  # True
a is b  # False
x = 1
y = 1
x is y  # True

`==` compares values, while `is` checks whether two references point to the same object. Small integers may be interned, so `is` must not be used for numeric equality.

Structures de contrôle10

If / elif / else
if score >= 90:
    grade = "A"
elif score >= 75:
    grade = "B"
else:
    grade = "C"

Conditions are checked from top to bottom, and the first matching branch runs. A broad condition can hide a more specific one below it.

Comparisons and chains
a == b
a != b
a < b
a <= b
a is None
0 <= x < n
a < b == c
x not in blocked

A comparison chain behaves like multiple checks joined by and, but the middle expression is evaluated once. Compare with None using is or is not.

Boolean operators
value = cached or compute()
result = ready and data
flag = not items

and and or short-circuit and return one of their operands, not necessarily a bool. The not operator always returns a bool.

For and while loops
for item in items:
    process(item)

while left < right:
    left += 1

for iterates over an iterable, while while repeats as long as its condition is truthy. A while loop must change relevant state or it may never terminate.

Range
range(stop)
range(start, stop)
range(start, stop, step)
range(n - 1, -1, -1)

range creates a lazy integer sequence and excludes stop. Counting backwards requires a negative step, and a zero step is invalid.

Break
for x in items:
    if x == target:
        break

break immediately exits the nearest loop. In nested loops, the outer loop continues.

Continue
for x in items:
    if x < 0:
        continue
    process(x)

continue skips the rest of the current iteration and proceeds to the next one. In a while loop, update loop state before continuing.

Loop else
for x in items:
    if x == target:
        found = True
        break
else:
    found = False

A loop else block runs only when the loop finishes without break. It also runs when the iterable is empty.

Match / case
match token:
    case 0:
        kind = "zero"
    case int() if token > 0:
        kind = "positive"
    case _:
        kind = "other"

match selects the first matching pattern, while _ acts as a catch-all. Execution does not fall through into later cases.

Unpacking in loops
for key, value in pairs:
    process(key, value)

for i, (x, y) in enumerate(points):
    process(i, x, y)

A loop target can unpack nested structures directly. The number and shape of values must match unless a starred target is used.

Fonctions10

Function and multiple return values
def bounds(values):
    return min(values), max(values)

low, high = bounds(items)

Multiple return values are actually packed into a tuple. When unpacking, the number of targets must match the number of items.

Default arguments
def power(base, exponent=2):
    return base ** exponent

A default value is used when the argument is omitted. The default expression is evaluated once when the function is created.

Mutable default argument
def add_item(item, items=[]):
    items.append(item)
    return items

A mutable default object is shared across calls, so data can accumulate unexpectedly. Use None and create the object inside the function instead.

Variadic arguments
def collect(first, *args, **kwargs):
    return first, args, kwargs

*args collects extra positional arguments into a tuple, while **kwargs collects keyword arguments into a dictionary. Regular parameters are bound first.

Keyword-only arguments
def clamp(value, *, low=0, high=100):
    return max(low, min(value, high))

Parameters after * can only be passed by name. This prevents mistakes caused by the unclear order of similar arguments.

Lambda
key = lambda item: item[1]
result = sorted(items, key=key)

lambda creates a small function from a single expression and cannot contain regular statements. Use def for more complex logic.

Closure
def make_adder(delta):
    def add(value):
        return value + delta
    return add

A closure keeps access to names from its enclosing function after that function returns. Names are captured by reference, which can cause late-binding surprises in loops.

Global and nonlocal
count = 0

def outer():
    total = 0

    def update():
        global count
        nonlocal total
        count += 1
        total += 1
        return total

    return update

global targets a module-level name, while nonlocal targets a name in the nearest enclosing scope that already binds it. Without these declarations, assignment creates a local name.

LRU cache
from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

lru_cache stores call results and is especially useful for repeated recursive subproblems. Arguments must be hashable, and maxsize=None creates an unbounded cache.

Type annotations
def binary_search(a: list[int], target: int) -> int | None:
    ...

Annotations describe expected parameter and return types but do not enforce them at runtime by themselves. X | None means the result may be absent.

Chaînes7

String slicing
s[1:4]
s[:3]
s[-2:]
s[::-1]
s[::2]

The right bound is exclusive. s[::-1] reverses the string, s[::2] takes every second character, negative indices count from the end.

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

split() with no argument splits on any whitespace and drops empties. join glues the parts back.

String methods
s.strip()
s.lower()
s.upper()
s.replace("a", "b")
s.startswith("ab")
s.endswith(".py")
s.count("a")
s.find("a")   # -1

Strings are immutable: methods return a new string. find returns -1 when missing; index raises instead.

Character checks and membership
s.isdigit()
s.isalpha()
s.isalnum()
c.isupper()
c.islower()
"ABC123".isupper()   # True
"123".isupper()      # False
"ab" in s

isdigit/isalpha/isalnum are true only for a non-empty string that matches all the way through. isupper/islower check every cased character: digits and punctuation are ignored, but at least one cased character is required. `in` tests whether a character or substring is present.

Comparing strings by characters
sorted(s)
sorted(s1) == sorted(s2)
set("aab") == set("ab")        # True
sorted("aab") == sorted("ab")  # False

sorted(s) gives the characters in order, so comparing two of those lists is the usual anagram check. A set will not do: it loses how many times each character occurs. Both forms count case, spaces and punctuation as characters.

Building a string
parts = []
for w in words:
    parts.append(w)
result = "".join(parts)

Do not build strings with += in a loop — each step copies everything. Collect into a list and join once.

ord / chr
ord('a')            # 97
chr(97)             # 'a'
ord(c) - ord('a')

Character code and back. ord(c) - ord("a") gives a letter index — the usual trick for a 26-slot counter.

Chaînes : niveau avancé10

Formatted strings
"{name}: {score:.2f}".format(name="Ada", score=9.876)
score = 9.876
f"{score:.2f}"

`str.format` substitutes values into a template, while an f-string evaluates expressions directly in the current scope. Format specifiers control precision, signs and representation, and literal braces are written as `{{` and `}}`.

String alignment and filling
x = "42"
f"{x:>8}"
f"{x:<8}"
f"{x:*^8}"

The `<`, `>` and `^` specifiers align a value to the left, right or centre, and a preceding character defines the fill. The width is a minimum and does not truncate a value that is too long.

Character translation table
table = str.maketrans({"a": "@", "e": "3"})
"peace".translate(table)

`str.maketrans` creates a translation table, and `translate` applies it to all characters in one pass. A table value of `None` removes the corresponding character.

String partition
head, sep, tail = "key=value".partition("=")

`partition` splits only at the first occurrence and always returns three elements, including the separator itself. If the separator is absent, the original string is the first element and the other two are empty.

Split text into lines
"a\nb\r\nc".splitlines()  # ["a", "b", "c"]

`splitlines` recognises multiple line-boundary formats and removes them by default. Unlike `split("\n")`, it correctly handles several standard line separators.

Zero-filled string
"42".zfill(5)  # "00042"
"-42".zfill(5)  # "-0042"

`zfill` pads a string with leading zeros to a minimum width and places the zeros after a numeric sign. It does not truncate a string that is already wider than the requested width.

Remove exact prefix or suffix
"unhappy".removeprefix("un")  # "happy"
"report.csv".removesuffix(".csv")  # "report"

`removeprefix` and `removesuffix` remove one exact match only from the corresponding end of a string. Unlike `strip`, their argument is not interpreted as a set of individual characters.

Regular expression basics
import re
re.match(r"\d+", text)
re.search(r"\d+", text)
re.findall(r"\d+", text)
re.sub(r"\s+", " ", text)

`match` checks only at the start of a string, `search` finds the first match anywhere, `findall` collects all matches, and `sub` replaces them. A repeatedly used pattern should usually be created once with `re.compile`.

Text encoding and decoding
data = "café".encode("utf-8")
text = data.decode("utf-8")

`encode` converts a `str` to `bytes`, while `decode` performs the reverse conversion using a specified encoding. Using inconsistent encodings can raise an error or corrupt the text.

Unicode case folding
"Straße".lower()  # "straße"
"Straße".casefold()  # "strasse"

`lower` performs normal lowercase conversion, while `casefold` applies a more aggressive case folding (ß → ss) for caseless comparison. It is not Unicode normalisation: a precomposed `é` and `e` plus a combining accent still compare unequal after `casefold` — that is what `unicodedata.normalize` is for.

Listes et tranches13

List slicing
a = [0, 1, 2, 3, 4]
a[1:4]    # [1, 2, 3]
a[:3]
a[-2:]
a[::-1]
b = a[:]

A slice makes a new list. a[:] copies the top level only — nested lists stay shared.

append / extend / insert / pop
a.append(x)        # O(1)
a.extend([x, y])
a.insert(0, x)     # O(n)
a.pop()            # O(1)
a.pop(0)           # O(n)

Inserting or removing at the front costs O(n). If you need both ends, use collections.deque.

enumerate
for i, x in enumerate(a):
    ...
for i, x in enumerate(a, start=1):
    ...

Index and value together. Replaces for i in range(len(a)) and removes the off-by-one risk.

zip
for x, y in zip(a, b):
    ...
list(zip(a, b))
zip(*matrix)

Walks several sequences in parallel and stops at the shortest one. zip(*matrix) transposes a matrix.

Comprehensions
[x for x in a if x > 0]
[x * 2 for x in a]
[y for row in m for y in row]
{x: x ** 2 for x in a}

Shorter and faster than a loop with append. Two for clauses flatten a nested list; braces with a colon build a dict. Nested clauses read in the same order as a plain loop.

any / all / sum / min / max
any(x > 0 for x in a)
all(x > 0 for x in a)
sum(a)
min(a)
max(a)
max(a, key=abs)
max(a, default=0)

any/all short-circuit on the first decisive element. key picks what to compare; default avoids an error on an empty list.

Building a 2D grid
grid = [[0] * m for _ in range(n)]   # ✓
grid = [[0] * m] * n                # ✗

The second form is wrong: list multiplication copies the reference, not the contents, so every row becomes the same list. Build with a comprehension.

List creation and repetition
a = [1, 2, 3]
b = list(range(3))
zeros = [0] * 5
rows = [[0] * 3] * 2

A literal or `list()` creates a list, while multiplication repeats its elements. Repeating a nested mutable object copies references, so the rows in `rows` are linked.

List index and count
a = [4, 2, 4]
i = a.index(4)
n = a.count(4)

`index()` returns the position of the first match, while `count()` returns the number of matches. `index()` raises `ValueError` when the value is absent.

List element removal
a = [10, 20, 30, 20]
a.remove(20)
del a[0]
x = a.pop()

`remove()` deletes the first matching value, `del` deletes by index or slice, and `pop()` deletes and returns an element. A missing value for `remove()` or an invalid index raises an exception.

Shallow and deep list copies
from copy import deepcopy

a = [[1], [2]]
b = a.copy()
c = deepcopy(a)
a[0].append(9)

`copy()` creates a new outer list but preserves references to nested objects. `deepcopy()` recursively copies nested content but usually requires more time and memory.

List argument unpacking
def add(a, b):
    return a + b

nums = [2, 3]
result = add(*nums)

The `*` operator passes list elements as separate positional arguments. Their count must match the function signature unless it accepts `*args`.

Tuple as an immutable sequence
point = (2, 3)
seen = {point}

A tuple resembles a list, but its elements cannot be replaced, added, or removed. It can be stored in a set or used as a dictionary key only when all its elements are hashable.

Dictionnaires et ensembles13

Dict access and defaults
d[k]                          # KeyError
d.get(k)                      # None
d.get(k, 0)
d.setdefault(k, []).append(x)

get never raises. setdefault creates the value when the key is missing and returns it in one step.

Iterating a dict
for k in d:
    ...
for k, v in d.items():
    ...
d.keys()
d.values()

Iterating a dict yields keys. Insertion order is preserved since Python 3.7.

Counter
from collections import Counter
c = Counter(a)
c[x]                        # 0
c.most_common(2)
Counter(s1) == Counter(s2)

A ready-made frequency counter. A missing key gives 0 rather than an error; most_common returns the top entries.

defaultdict
from collections import defaultdict
g = defaultdict(list)
g[k].append(x)
cnt = defaultdict(int)
cnt[k] += 1

The value is created on first access. Handy for grouping and for graph adjacency lists.

Sets
s = set(a)
s.add(x)
s.discard(x)
x in s          # O(1)
a_set & b_set
a_set | b_set
a_set - b_set

Membership is O(1) instead of a list’s O(n). & is intersection, | union, - difference. A set keeps no order and no duplicates.

The "seen" set pattern
seen = set()
for x in a:
    if x in seen:
        return True
    seen.add(x)

Finds a duplicate in one pass, O(n). The same shape solves two-sum: look for target - x in seen.

Dictionary creation
d1 = {"a": 1}
d2 = dict(a=1)
d3 = dict([("a", 1)])
d4 = {k: 0 for k in ("a", "b")}

A dictionary can be created with a literal, `dict()`, a sequence of pairs, or a dictionary comprehension. Keyword arguments to `dict()` work only for string keys that are valid identifiers.

Dictionary membership and removal
d = {"a": 1, "c": 3}
"a" in d  # True
x = d.pop("a", 0)
y = d.pop("b", 0)
del d["c"]

For a dictionary, `in` checks keys rather than values. `del` raises `KeyError` for a missing key, while `pop(key, default)` returns the fallback value.

Dictionary views
d = {"a": 1, "b": 2}
keys = d.keys()
values = d.values()
pairs = d.items()
d["c"] = 3
list(keys)  # ["a", "b", "c"]

The `keys()`, `values()`, and `items()` methods return dynamic views rather than lists. They reflect later dictionary changes; use an explicit `list` conversion for an independent snapshot.

Dictionary merging
a = {"x": 1}
b = {"x": 2, "y": 3}
c = a | b
a.update(b)

The `|` operator creates a new dictionary, while `update()` modifies the existing one. When keys overlap, the value from the right dictionary or supplied source wins.

Dictionary sorting by value
d = {"a": 3, "b": 1, "c": 2}
pairs = sorted(d.items(), key=lambda item: item[1])

To sort by values, sort the pairs from `items()` using their second element as the key. The result is a list of tuples, not an automatically sorted dictionary.

Dictionary frequency counter
freq = {}
for x in [2, 1, 2]:
    freq[x] = freq.get(x, 0) + 1

Frequencies can be counted with a regular dictionary by using zero for an unseen key. Without a default value, `get()` returns `None`, which cannot be added to a number.

Frozenset as a dictionary key
edge = frozenset({2, 5})
weights = {edge: 7}

A `frozenset` is immutable and hashable, so it can be used as a dictionary key or an element of another set. A regular `set` cannot be used this way.

Collections de la bibliothèque standard10

Deque ends
from collections import deque
q = deque([2, 3])
q.appendleft(1)
q.append(4)
left = q.popleft()
right = q.pop()

deque adds and removes elements from both ends in O(1). For a queue, use append and popleft instead of list pop(0).

Bounded deque and rotation
from collections import deque
q = deque([1, 2, 3], maxlen=3)
q.append(4)
q.rotate(1)
q.rotate(-1)

maxlen automatically discards items from the opposite end when the deque is full. rotate shifts items right for a positive argument and left for a negative one.

Min heap
import heapq
h = [4, 1, 7, 2]
heapq.heapify(h)
heapq.heappush(h, 0)
smallest = heapq.heappop(h)

heapq implements a min-heap, so the smallest item is always at the root. heapify runs in O(n), while push and pop take O(log n).

Heap selection and max heap
import heapq
nums = [4, 1, 7, 2]
largest = heapq.nlargest(2, nums)
smallest = heapq.nsmallest(2, nums)
h = [-x for x in nums]
heapq.heapify(h)
maximum = -heapq.heappop(h)

nlargest and nsmallest select k extreme items without fully sorting the input. A max-heap is commonly simulated by storing numbers with the opposite sign.

Counter operations
from collections import Counter
a = Counter("banana")
a.update("band")
top = a.most_common(2)
b = Counter("an")
added = a + b
common = a & b

Counter stores element frequencies and supports most_common, update, and operations between counters. Arithmetic operations create a new Counter and generally omit zero or negative results.

Default dictionary
from collections import defaultdict
groups = defaultdict(list)
counts = defaultdict(int)
seen = defaultdict(set)
groups["a"].append(1)
value = counts["missing"]

defaultdict creates a value with its factory when a missing key is accessed. Reading through square brackets mutates the mapping, so use in or get when only checking.

Ordered dictionary
from collections import OrderedDict
d = {"a": 1, "b": 2}
od = OrderedDict(d)
od.move_to_end("a")
first = od.popitem(last=False)

Regular dict already preserves insertion order, so OrderedDict is mainly useful for explicit reordering and removal from a chosen end. Equality between two OrderedDict instances also considers item order.

Named tuple
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(2, 3)
x = p.x

namedtuple creates a compact immutable type whose fields are accessible by name and index. For richer models with methods and defaults, a dataclass is usually more convenient.

Array and bytes
from array import array
nums = array("i", [1, 2, 3])
raw = bytes([65, 66, 67])
mutable = bytearray(raw)
mutable[0] = 90

array stores values of one primitive type more compactly than a regular list, but is rarely needed in algorithm problems. bytes is immutable, while bytearray allows individual bytes to be changed.

Chain map
from collections import ChainMap
defaults = {"timeout": 30}
overrides = {"timeout": 10}
config = ChainMap(overrides, defaults)
value = config["timeout"]

ChainMap searches multiple mappings from left to right without copying or merging them. Assignments modify only the first mapping by default.

Itération et générateurs10

Generator expression and list comprehension
squares_list = [x * x for x in values]
squares_gen = (x * x for x in values)

A list comprehension builds the entire list immediately, while a generator expression produces values on demand. A generator saves memory but is normally consumed only once.

Yield
def countdown(n):
    while n > 0:
        yield n
        n -= 1

Using yield makes a function a generator function: calling it returns a generator, and its body runs during iteration. Execution resumes after each yield from the saved position.

Yield from
def flatten(chunks):
    for chunk in chunks:
        yield from chunk

yield from forwards items from a nested iterable without a manual inner loop. Each use flattens only one level.

Iter, next, and StopIteration
it = iter([10])
first = next(it)
missing = next(it, None)

try:
    next(it)
except StopIteration:
    ...

iter obtains an iterator, while next retrieves the next item and advances its state. Supplying a default prevents StopIteration when the iterator is exhausted.

Count, cycle, and islice
from itertools import count, cycle, islice

numbers = list(islice(count(10, 2), 4))
pattern = list(islice(cycle("ab"), 5))

count and cycle create potentially infinite iterators, while islice limits them without materializing the full sequence. islice does not support negative indices or a negative step.

Chain
from itertools import chain

merged = list(chain([1, 2], [3, 4]))
flat = list(chain.from_iterable([[1, 2], [3, 4]]))

chain lazily iterates through several iterables one after another. chain.from_iterable accepts an outer iterable and flattens one level.

Product, permutations, and combinations
from itertools import combinations, permutations, product

pairs = list(product([0, 1], repeat=2))
orders = list(permutations([1, 2, 3], 2))
choices = list(combinations([1, 2, 3], 2))

product builds a Cartesian product, permutations considers order, and combinations does not. They return iterators, but the number of results can grow combinatorially.

Groupby
from itertools import groupby

items = [1, 1, 2, 2, 2, 3]
groups = [(key, list(group)) for key, group in groupby(items)]

groupby groups only consecutive items with the same key. For global grouping, sort the data by the same key first.

Reversed
a = [1, 2, 3]
it = reversed(a)
b = list(it)

reversed returns a reverse iterator and does not modify the original sequence. The object must support reverse iteration or provide indexed access and a length.

Generator aggregation
total = sum(x * x for x in values)
has_negative = any(x < 0 for x in values)
all_even = all(x % 2 == 0 for x in values)

sum consumes every value from the generator, while any and all stop as soon as the result is known. For an empty source, any returns False and all returns True.

Nombres et mathématiques10

Arbitrary-precision integers
n = 10**100
n.bit_length()

The `int` type stores integers of arbitrary size, so there is no fixed-width overflow. Memory and computation time are the practical limits, but since Python 3.11 converting between `int` and `str` is capped at 4300 decimal digits and raises `ValueError` beyond it.

Floating-point precision
0.1 + 0.2 == 0.3  # False
x = float("inf")

The `float` type uses binary approximations, so many decimal fractions cannot be represented exactly. Use `math.isclose` for comparisons and `decimal.Decimal` when exact decimal arithmetic is required.

Bankers rounding
round(2.5)  # 2
round(3.5)  # 4
round(12.345, 2)

`round` resolves an exact halfway case toward the nearest even value rather than always rounding up. Results for `float` values can also be affected by binary representation error.

Absolute value and powers
abs(-7)  # 7
2**10  # 1024
pow(2, 10)  # 1024
pow(2, 10, 1000)  # 24

`abs` returns an absolute value, while `**` and `pow` perform exponentiation. The three-argument `pow(a, b, mod)` efficiently computes a modular power without creating a huge intermediate integer.

Math roots and number theory
from math import sqrt, isqrt, gcd, factorial
sqrt(81)  # 9.0
isqrt(80)  # 8
gcd(18, 24)  # 6
factorial(5)  # 120

`sqrt` returns a floating-point root, while `isqrt` returns the exact integer floor of the root without `float` errors. `gcd` computes the greatest common divisor, and `factorial` accepts only non-negative integers.

Floor, ceiling and infinity
from math import ceil, floor, inf
ceil(2.1)  # 3
floor(2.9)  # 2
inf > 10**100  # True

`ceil` rounds toward positive infinity, while `floor` rounds toward negative infinity, which matters especially for negative values. `math.inf` compares as infinity but is not produced automatically by division by zero.

Division by zero
try:
    1 / 0
except ZeroDivisionError:
    ...

Division, floor division and modulo with a zero divisor raise `ZeroDivisionError`. Python does not convert such a division into infinity.

Numeric conversions and bases
int("101", 2)  # 5
int("0xff", 0)  # 255
int(3.9)  # 3
float("2.5")  # 2.5
str(42)  # "42"
bin(10)  # "0b1010"
hex(255)  # "0xff"
oct(8)  # "0o10"

`int` can parse strings using bases from 2 to 36, while base `0` detects a standard prefix automatically. Converting a `float` to `int` truncates toward zero, and `bin`, `hex` and `oct` return prefixed strings.

Bitwise operations
x = 0b1100
y = 0b1010
x & y           # 8
x | y           # 14
x ^ y           # 6
~x              # -13
x << 2          # 48
x >> 2          # 3
x.bit_count()   # 2

Bitwise operations work with the binary representation of integers and are commonly used for masks and state sets. Integers have no fixed width, and `~x` is always equal to `-x - 1`.

Pseudo-random values
import random
rng = random.Random(42)
rng.randrange(1, 10)
rng.randint(1, 10)
rng.choice([1, 2, 3])
a = [1, 2, 3]
rng.shuffle(a)

`random` generates reproducible pseudo-random values: `randrange` excludes its stop value, while `randint` includes both endpoints. The module is not suitable for passwords or cryptography.

Tri et recherche6

sorted / list.sort
b = sorted(a)
a.sort()
sorted(a, reverse=True)

sorted returns a new list; sort mutates in place and returns None. The sort is stable.

Sort key
sorted(words, key=len)
sorted(pairs, key=lambda p: p[1])
sorted(a, key=lambda x: (-x[1], x[0]))

key is called once per element. A tuple gives multiple levels; a minus sign reverses a numeric field.

Stable sort
a.sort(key=lambda x: x.age)
a.sort(key=lambda x: x.name)

Equal elements keep their original order, so you can sort by the least significant key first.

itemgetter
from operator import itemgetter
sorted(rows, key=itemgetter(1, 0))

Same as a lambda but faster, and clearer when sorting by several fields.

bisect
from bisect import bisect_left, bisect_right, insort
i = bisect_left(a, x)
j = bisect_right(a, x)
insort(a, x)

Binary search over a sorted list in O(log n). bisect_left gives the first position of x and bisect_right the one just after it, so j - i is the occurrence count. insort inserts while keeping order.

Binary search by hand
lo, hi = 0, len(a) - 1
while lo <= hi:
    mid = (lo + hi) // 2
    if a[mid] == x:
        return mid
    if a[mid] < x:
        lo = mid + 1
    else:
        hi = mid - 1
return -1

The list must be sorted. Move the bounds past mid, or the loop never terminates.

Pile, file et deux pointeurs9

List as stack
stack = []
stack.append(1)
stack.append(2)
top = stack.pop()

A list efficiently implements a LIFO stack with append and pop from the end in amortized O(1). Removing from the front of a list takes O(n).

Deque as queue
from collections import deque
q = deque([1, 2])
q.append(3)
first = q.popleft()

A FIFO queue is efficiently implemented with deque: append adds on the right and popleft removes on the left in O(1). A list with pop(0) is unsuitable for large queues because elements must be shifted.

Monotonic stack
def next_greater(nums):
    result = [-1] * len(nums)
    stack = []
    for i, x in enumerate(nums):
        while stack and nums[stack[-1]] < x:
            result[stack.pop()] = x
        stack.append(i)
    return result

A monotonic stack keeps values or indices ordered within the stack and finds nearest greater or smaller elements in O(n). Store indices when distances or positions are needed.

Balanced brackets
def is_balanced(s):
    pairs = {")": "(", "]": "[", "}": "{"}
    stack = []
    for ch in s:
        if ch in "([{":
            stack.append(ch)
        elif ch in pairs and (not stack or stack.pop() != pairs[ch]):
            return False
    return not stack

Opening brackets are pushed onto a stack, and each closing bracket must match the top. The stack must be empty afterward, or some brackets remain unclosed.

Opposite pointers
def has_pair(nums, target):
    left, right = 0, len(nums) - 1
    while left < right:
        total = nums[left] + nums[right]
        if total == target:
            return True
        if total < target:
            left += 1
        else:
            right -= 1
    return False

Pointers from opposite ends shrink the search space when the current result determines which pointer should move. For pair-sum search, the array must be sorted.

Fast and slow pointers
def middle_node(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
    return slow

A fast pointer moves quicker than a slow pointer, allowing middle or cycle detection without extra memory. Check both fast and its next node before advancing twice.

Remove duplicates in place
def remove_duplicates(nums):
    if not nums:
        return 0
    write = 1
    for read in range(1, len(nums)):
        if nums[read] != nums[write - 1]:
            nums[write] = nums[read]
            write += 1
    return write

A read pointer scans the array while a write pointer builds the valid prefix without another array. This version assumes equal values are adjacent, usually because the input is sorted.

Reverse in place
def reverse_in_place(nums):
    left, right = 0, len(nums) - 1
    while left < right:
        nums[left], nums[right] = nums[right], nums[left]
        left += 1
        right -= 1

Swapping elements from both ends reverses a mutable sequence in O(n) time and O(1) extra space. A string cannot be modified this way because it is immutable.

Merge sorted sequences
def merge_sorted(a, b):
    i = j = 0
    result = []
    while i < len(a) and j < len(b):
        if a[i] <= b[j]:
            result.append(a[i])
            i += 1
        else:
            result.append(b[j])
            j += 1
    result.extend(a[i:])
    result.extend(b[j:])
    return result

Two pointers merge sorted sequences in O(n + m) by repeatedly choosing the smaller current item. After the main loop, append the unprocessed tail.

Fenêtre glissante et sommes de préfixes9

Fixed sliding window
def window_sums(nums, k):
    if k > len(nums):
        return []
    current = sum(nums[:k])
    result = [current]
    for right in range(k, len(nums)):
        current += nums[right] - nums[right - k]
        result.append(current)
    return result

A fixed window updates its state by adding the entering item and removing the leaving one, processing all windows in O(n). Define behavior explicitly when k is zero or larger than the input.

Variable sliding window
def min_length(nums, target):
    left = 0
    total = 0
    answer = len(nums) + 1
    for right, x in enumerate(nums):
        total += x
        while total >= target:
            answer = min(answer, right - left + 1)
            total -= nums[left]
            left += 1
    return 0 if answer > len(nums) else answer

A variable window expands on the right and shrinks from the left while the required condition holds. Sum-based versions usually require nonnegative values, otherwise window monotonicity breaks.

Window frequency counter
from collections import Counter

def anagram_windows(s, pattern):
    need = Counter(pattern)
    window = Counter()
    result = []
    k = len(pattern)
    for right, ch in enumerate(s):
        window[ch] += 1
        if right >= k:
            left_ch = s[right - k]
            window[left_ch] -= 1
            if window[left_ch] == 0:
                del window[left_ch]
        if window == need:
            result.append(right - k + 1)
    return result

Window frequencies are updated only for the entering and leaving items, avoiding repeated counting. Removing zero counts keeps comparisons and state easier to reason about.

Maximum window sum
def max_window_sum(nums, k):
    current = sum(nums[:k])
    best = current
    for right in range(k, len(nums)):
        current += nums[right] - nums[right - k]
        best = max(best, current)
    return best

The next window sum is derived by subtracting the left item and adding the new right item. Initialize the maximum with the first complete window rather than zero when values may be negative.

Prefix sums
def range_sum(prefix, left, right):
    return prefix[right] - prefix[left]

nums = [2, 4, 1]
prefix = [0]
for x in nums:
    prefix.append(prefix[-1] + x)
value = range_sum(prefix, 1, 3)

A prefix array with an initial zero returns the sum of the half-open range [left, right) in O(1). The extra zero simplifies boundaries and ranges starting at the first item.

Adjacent differences
nums = [3, 8, 10, 15]
diffs = [b - a for a, b in zip(nums, nums[1:])]

Adjacent differences describe changes between positions and can reconstruct values through accumulation. The result contains one fewer item than the original sequence.

Running accumulation
from itertools import accumulate
prefix = list(accumulate([2, 4, 1], initial=0))
products = list(accumulate([2, 3, 4], lambda a, b: a * b))

accumulate lazily yields intermediate accumulated results and accepts a custom binary function. initial adds a starting value to the output and changes its length.

Target subarray sum
def has_subarray_sum(nums, target):
    seen = {0}
    prefix = 0
    for x in nums:
        prefix += x
        if prefix - target in seen:
            return True
        seen.add(prefix)
    return False

A subarray sums to target when two prefix sums differ by target. A set of seen prefixes gives O(n) and works with negative values, unlike a standard sum-based sliding window.

Binary search on answer
def first_true(low, high, feasible):
    while low < high:
        mid = (low + high) // 2
        if feasible(mid):
            high = mid
        else:
            low = mid + 1
    return low

Binary search on the answer finds the boundary where a monotonic predicate changes from False to True. The main challenge is choosing consistent inclusive bounds and proving that feasible is monotonic.

Récursion et programmation dynamique10

Recursive base and step
def factorial(n):
    if n <= 1:
        return 1
    return n * factorial(n - 1)

The base case stops recursion, while the recursive step reduces the problem to a smaller instance. Without a reachable base case, calls end with RecursionError.

Recursion depth limit
import sys
sys.setrecursionlimit(200000)

Python limits stack depth and raises RecursionError when it is exceeded. Increase the limit carefully because very deep recursion can exhaust the system stack.

Tail recursion
def factorial_tail(n, acc=1):
    if n <= 1:
        return acc
    return factorial_tail(n - 1, acc * n)

A tail call is the final operation of a function, but Python does not optimize tail calls. Tail recursion still consumes stack space and is usually replaced with a loop.

Memoization decorators
from functools import cache, lru_cache

@lru_cache(maxsize=None)
def fib_lru(n):
    return n if n < 2 else fib_lru(n - 1) + fib_lru(n - 2)

@cache
def fib_cache(n):
    return n if n < 2 else fib_cache(n - 1) + fib_cache(n - 2)

These decorators store results by arguments and eliminate repeated computations. cache is equivalent to an unlimited lru_cache, and all arguments must be hashable.

Bottom-up dynamic programming
def fib(n):
    if n < 2:
        return n
    dp = [0] * (n + 1)
    dp[1] = 1
    for i in range(2, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]
    return dp[n]

Bottom-up DP computes states from simpler to more complex ones and stores them in an array. It avoids the recursion stack, although the array can sometimes be reduced to a few variables.

Two-dimensional dynamic programming
def grid_paths(rows, cols):
    dp = [[0] * cols for _ in range(rows)]
    for r in range(rows):
        dp[r][0] = 1
    for c in range(cols):
        dp[0][c] = 1
    for r in range(1, rows):
        for c in range(1, cols):
            dp[r][c] = dp[r - 1][c] + dp[r][c - 1]
    return dp[-1][-1]

A two-dimensional table is used when a state depends on two parameters, such as a row and column position. Its boundaries must be initialized correctly before filling the remaining cells.

DP answer reconstruction
def min_coins(coins, amount):
    inf = amount + 1
    dp = [0] + [inf] * amount
    prev = [-1] * (amount + 1)
    for total in range(1, amount + 1):
        for coin in coins:
            if coin <= total and dp[total - coin] + 1 < dp[total]:
                dp[total] = dp[total - coin] + 1
                prev[total] = coin
    if dp[amount] == inf:
        return None
    answer = []
    while amount > 0:
        coin = prev[amount]
        answer.append(coin)
        amount -= coin
    return answer

Besides the optimal value, DP can store the choice used to reach each state. The actual answer is then reconstructed by walking backward from the final state.

Divide and conquer
def merge_sort(a):
    if len(a) <= 1:
        return a
    mid = len(a) // 2
    left = merge_sort(a[:mid])
    right = merge_sort(a[mid:])
    result = []
    i = j = 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    return result + left[i:] + right[j:]

The problem is split into independent subproblems whose recursive solutions are combined afterward. Efficiency depends on balanced splitting and the cost of combining results.

Backtracking permutations
def permutations(a):
    result = []

    def backtrack(start):
        if start == len(a):
            result.append(a.copy())
            return
        for i in range(start, len(a)):
            a[start], a[i] = a[i], a[start]
            backtrack(start + 1)
            a[start], a[i] = a[i], a[start]

    backtrack(0)
    return result

Backtracking chooses an option, recurses, and then restores the modified state. Generating every permutation requires factorial time.

Backtracking subsets
def subsets(a):
    result = []
    current = []

    def backtrack(index):
        if index == len(a):
            result.append(current.copy())
            return
        backtrack(index + 1)
        current.append(a[index])
        backtrack(index + 1)
        current.pop()

    backtrack(0)
    return result

For each element, recursion considers two choices: include it or skip it. The current result must be copied when saved and rolled back after the recursive call.

Graphes et arbres10

Adjacency list
edges = [(0, 1), (0, 2), (1, 2)]
graph = {0: [], 1: [], 2: []}
for u, v in edges:
    graph[u].append(v)
    graph[v].append(u)

An adjacency list stores the neighbors of every vertex and usually requires O(V + E) memory. For a directed graph, the reverse edge must not be added automatically.

Shortest path in an unweighted graph
from collections import deque

def shortest_path(graph, start, target):
    queue = deque([start])
    parent = {start: None}
    while queue:
        node = queue.popleft()
        if node == target:
            break
        for neighbor in graph[node]:
            if neighbor not in parent:
                parent[neighbor] = node
                queue.append(neighbor)
    if target not in parent:
        return None
    path = []
    node = target
    while node is not None:
        path.append(node)
        node = parent[node]
    return path[::-1]

BFS first reaches a vertex through a path with the minimum number of edges. Store the parent of each newly visited vertex to reconstruct the path itself.

Tree depth-first traversals
def preorder(node, result):
    if node is None:
        return
    result.append(node.val)
    preorder(node.left, result)
    preorder(node.right, result)

def inorder(node, result):
    if node is None:
        return
    inorder(node.left, result)
    result.append(node.val)
    inorder(node.right, result)

def postorder(node, result):
    if node is None:
        return
    postorder(node.left, result)
    postorder(node.right, result)
    result.append(node.val)

Preorder processes a node before its children, inorder between the left and right subtrees, and postorder after its children. Inorder is sorted only for a valid binary search tree.

Tree level-order traversal
from collections import deque

def level_order(root):
    if root is None:
        return []
    queue = deque([root])
    levels = []
    while queue:
        level = []
        for _ in range(len(queue)):
            node = queue.popleft()
            level.append(node.val)
            if node.left is not None:
                queue.append(node.left)
            if node.right is not None:
                queue.append(node.right)
        levels.append(level)
    return levels

Level-order traversal is BFS applied to a tree and uses a queue. The queue size at the start of an iteration fixes the number of nodes in the current level.

Directed cycle detection
def has_cycle(graph):
    state = {}

    def dfs(node):
        state[node] = 1
        for neighbor in graph[node]:
            if state.get(neighbor, 0) == 1:
                return True
            if state.get(neighbor, 0) == 0 and dfs(neighbor):
                return True
        state[node] = 2
        return False

    return any(state.get(node, 0) == 0 and dfs(node) for node in graph)

In a directed graph, an edge to a vertex currently in the DFS stack indicates a cycle. Three states are commonly used: unvisited, processing, and fully processed.

Topological sort
from collections import deque

def topological_sort(graph):
    indegree = {node: 0 for node in graph}
    for node in graph:
        for neighbor in graph[node]:
            indegree[neighbor] += 1
    queue = deque(node for node in graph if indegree[node] == 0)
    order = []
    while queue:
        node = queue.popleft()
        order.append(node)
        for neighbor in graph[node]:
            indegree[neighbor] -= 1
            if indegree[neighbor] == 0:
                queue.append(neighbor)
    return order if len(order) == len(graph) else None

Kahn algorithm repeatedly removes vertices with zero indegree. If not every vertex is processed, the graph contains a cycle and no topological order exists.

Disjoint set union
class DSU:
    def __init__(self, n):
        self.parent = list(range(n))
        self.size = [1] * n

    def find(self, x):
        while x != self.parent[x]:
            self.parent[x] = self.parent[self.parent[x]]
            x = self.parent[x]
        return x

    def union(self, a, b):
        a = self.find(a)
        b = self.find(b)
        if a == b:
            return False
        if self.size[a] < self.size[b]:
            a, b = b, a
        self.parent[b] = a
        self.size[a] += self.size[b]
        return True

Union-Find quickly checks whether vertices belong to the same component and merges components. Path compression and union by size provide nearly constant amortized operation time.

Exceptions10

Try, except, else, finally
try:
    value = int(text)
except ValueError:
    value = 0
else:
    value += 1
finally:
    closed = True

except runs after an exception, else only after successful completion of try, and finally always runs. Keep only operations whose exceptions are expected inside the try block.

Catch a specific exception
try:
    item = data[key]
except KeyError:
    item = None

Catch a specific exception type to avoid hiding unrelated program errors. A bare except is appropriate only in rare cases where the exception is handled correctly or re-raised.

Catch multiple exception types
try:
    result = values[index] / divisor
except (IndexError, ZeroDivisionError):
    result = None

Multiple exception types with identical handling can be listed in a tuple in one except clause. Use separate except clauses when their handling differs.

Raise and re-raise
def load_number(text):
    try:
        return int(text)
    except ValueError as exc:
        raise ValueError("invalid number") from exc

def parse_number(text):
    try:
        return load_number(text)
    except ValueError:
        raise

raise creates an exception, while raise without arguments re-raises the current one. The raise ... from ... form preserves an explicit causal chain between exceptions.

Custom exception
class InvalidStateError(Exception):
    pass

def require_ready(ready):
    if not ready:
        raise InvalidStateError("not ready")

Custom exceptions inherit from Exception and distinguish domain errors from built-in failures. An empty class with a clear name is often sufficient.

Common built-in exceptions
def built_in_errors(data, key, index, value, divisor):
    if not isinstance(value, int):
        raise TypeError
    if divisor == 0:
        raise ZeroDivisionError
    if key not in data:
        raise KeyError(key)
    if not -len(data) <= index < len(data):
        raise IndexError(index)
    if value < 0:
        raise ValueError(value)

ValueError indicates an invalid value of an acceptable type, while TypeError indicates an invalid type; KeyError and IndexError represent missing keys or indexes. ZeroDivisionError occurs when dividing or taking a remainder by zero.

Assert statement
def divide(a, b):
    assert b != 0
    return a / b

assert checks internal invariants and raises AssertionError when one fails. Assertions are removed when Python runs with -O, so they must not validate user input.

With context manager
with open("data.txt", encoding="utf-8") as file:
    text = file.read()

A context manager guarantees resource cleanup after normal completion and after an exception. For files, this means automatic closing when the with block ends.

Suppress exceptions
from contextlib import suppress
from pathlib import Path

path = Path("data.txt")
with suppress(FileNotFoundError):
    path.unlink()

contextlib.suppress ignores only explicitly listed exception types and suits short, expected cases. Do not use it to hide failures that require handling or logging.

EAFP and LBYL
def eafp(data, key):
    try:
        return data[key]
    except KeyError:
        return None

def lbyl(data, key):
    if key in data:
        return data[key]
    return None

EAFP performs the operation first and handles an expected failure, while LBYL checks a condition beforehand. EAFP is often more idiomatic in Python and avoids a gap between checking and acting.

Classes et objets10

Class, initializer, and instance method
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def move(self, dx, dy):
        self.x += dx
        self.y += dy

A class describes object structure, while __init__ initializes each new instance. self is passed automatically and refers to the current object.

Instance and class attributes
class Counter:
    total = 0

    def __init__(self):
        self.value = 0
        Counter.total += 1

A class attribute is shared by all instances, while an attribute on self belongs to one object. Mutating a shared class attribute can unexpectedly affect every instance.

Property
class Account:
    def __init__(self, balance):
        self.balance = balance

    @property
    def balance(self):
        return self._balance

    @balance.setter
    def balance(self, value):
        if value < 0:
            raise ValueError
        self._balance = value

@property exposes method logic through normal attribute access and can validate reads or writes. The property usually stores data under another attribute name to avoid infinite recursion.

Static and class methods
class Temperature:
    def __init__(self, value):
        self.value = value

    @staticmethod
    def is_valid(value):
        return value >= -273.15

    @classmethod
    def zero(cls):
        return cls(0)

@staticmethod receives neither an instance nor a class, while @classmethod receives the class as cls. Class methods are useful as alternative constructors and preserve subclass behavior.

Inheritance and super
class Base:
    def __init__(self, value):
        self.value = value

class Child(Base):
    def __init__(self, value, extra):
        super().__init__(value)
        self.extra = extra

A subclass inherits behavior from its base class and can extend or override it. super() calls the next implementation in the method resolution order, which matters especially with multiple inheritance.

String representations
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'Point(x={self.x}, y={self.y})'

    def __str__(self):
        return f'{self.x}, {self.y}'

__repr__ is intended for developers and debugging, while __str__ provides user-friendly output. If __str__ is missing, str() falls back to __repr__.

Equality and hashing
class User:
    def __init__(self, user_id):
        self.user_id = user_id

    def __eq__(self, other):
        return isinstance(other, User) and self.user_id == other.user_id

    def __hash__(self):
        return hash(self.user_id)

__eq__ defines logical equality, while __hash__ allows an object to be used in sets and as a dictionary key. Equal objects must have equal hashes, and hashed fields must not change.

Object ordering with __lt__
class Item:
    def __init__(self, score, name):
        self.score = score
        self.name = name

    def __lt__(self, other):
        return (self.score, self.name) < (other.score, other.name)

items = sorted([Item(2, 'b'), Item(1, 'a')])

__lt__ defines the < operator and lets sorted() compare objects directly. Tuple comparison conveniently expresses primary and tie-breaking sort criteria.

Frozen dataclass with defaults
from dataclasses import dataclass, field

@dataclass(frozen=True)
class Node:
    value: int
    tags: tuple[str, ...] = ()

@dataclass
class Basket:
    items: list[str] = field(default_factory=list)

@dataclass generates methods such as __init__ and __repr__, while frozen=True blocks normal field assignment. An immutable default can be written directly, but a mutable one such as [] raises ValueError at class creation and must use default_factory.

Slots
class Point:
    __slots__ = ('x', 'y')

    def __init__(self, x, y):
        self.x = x
        self.y = y

__slots__ restricts available instance attributes and often reduces memory usage. Unless __dict__ is included, arbitrary new attributes cannot be added dynamically.

Annotations de types10

Variable and function annotations
count: int = 3

def repeat(text: str, times: int) -> str:
    return text * times

count = '3'

Annotations describe expected types for static analyzers, IDEs, and documentation. Python does not enforce them automatically at runtime and still permits values of another type.

Collection annotations
items: list[int] = [1, 2]
scores: dict[str, int] = {'a': 1}
point: tuple[int, int] = (2, 3)

Built-in collections can be parameterized with element, key, and value types. tuple[int, int] describes a fixed structure, while tuple[int, ...] describes an arbitrary-length tuple.

Optional values
from typing import Optional

left: Optional[int] = None
right: int | None = None

Optional[int] and int | None both mean that a value may be an int or None. Optional does not make a parameter optional; that requires a default argument value.

Union types
from typing import Union

value_a: Union[int, str] = 1
value_b: int | str = 'x'

Union[int, str] and int | str allow one of several types. Very broad unions make type narrowing and exhaustive handling more difficult.

Any
from typing import Any

value: Any = 1
value = 'x'
value = [1, 2]

Any disables static checks for operations on a value and is compatible with every type in both directions. Overusing it hides errors and removes much of the benefit of annotations.

Callable
from collections.abc import Callable

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

Callable[[A, B], R] describes a callable taking two arguments and returning R. Callable[..., R] is used when the precise parameter list is unknown.

Iterable and Sequence
from collections.abc import Iterable, Sequence

def total(values: Iterable[int]) -> int:
    return sum(values)

def first(values: Sequence[int]) -> int:
    return values[0]

Iterable guarantees iteration but not indexing or repeated traversal. Sequence additionally provides length and indexed access, making it a stricter contract.

Type aliases and Literal
from typing import Literal, TypeAlias

UserId: TypeAlias = int
Mode: TypeAlias = Literal['fast', 'safe']

TypeAlias gives a readable name to another type but does not create a distinct incompatible type. Literal restricts a value to specific literals and is useful for modes and states.

TypedDict
from typing import TypedDict

class UserData(TypedDict):
    name: str
    age: int

user: UserData = {'name': 'a', 'age': 20}

TypedDict describes required keys and value types for an ordinary dictionary. At runtime the object remains a dict, and its structure is checked only by static tools.

Generic and TypeVar
from typing import Generic, TypeVar

T = TypeVar('T')

class Box(Generic[T]):
    def __init__(self, value: T):
        self.value = value

    def get(self) -> T:
        return self.value

TypeVar links types across a signature, while Generic creates a class parameterized by that type. Unlike Any, a type checker preserves the concrete type for each use.

Tests et débogage10

Pytest test and assert
def add(a, b):
    return a + b

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

pytest discovers functions whose name starts with test (default python_functions = test*; test_ by convention) and treats a failed assert as a test failure. Assertions should compare concrete results so failure reports can show useful differences.

Expected exception with pytest.raises
import pytest

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

pytest.raises verifies that a block raises the expected exception type. The test fails if no exception is raised or if its type differs.

Parameterized test
import pytest

@pytest.mark.parametrize('x, expected', [(1, 1), (2, 4), (3, 9)])
def test_square(x, expected):
    assert x * x == expected

parametrize runs one test function for every supplied data set. Cases remain independent, so the report identifies the exact failing input.

Pytest fixture
import pytest

@pytest.fixture
def sample():
    return [1, 2, 3]

def test_length(sample):
    assert len(sample) == 3

A fixture prepares data or resources and is injected into a test through a matching parameter name. Overly broad fixtures create hidden dependencies and make tests harder to understand.

Approximate numeric comparison
import pytest

def test_ratio():
    assert 0.1 + 0.2 == pytest.approx(0.3)

pytest.approx compares numbers using an allowed absolute or relative tolerance. Direct float equality is unreliable because many decimal fractions have no exact binary representation.

Unittest TestCase
import unittest

class MathTest(unittest.TestCase):
    def test_add(self):
        self.assertEqual(2 + 3, 5)

unittest.TestCase is the standard-library class-based testing model with methods such as assertEqual, setUp, and tearDown. A test method name must start with the prefix test (TestLoader.testMethodPrefix) for the default loader to discover it; test_ is the usual convention.

Doctest
def square(x):
    """
    >>> square(3)
    9
    """
    return x * x

if __name__ == '__main__':
    import doctest
    doctest.testmod()

doctest executes examples from docstrings and compares their output with the recorded result. It is useful for small examples but can be fragile when output formatting changes.

Benchmarking with timeit
from timeit import timeit

elapsed = timeit('sum(range(100))', number=10_000)

timeit repeatedly executes a small statement and reduces the impact of random timing noise. Comparisons must use equivalent work under the same conditions rather than relying on one run.

Printing a traceback
import traceback

try:
    1 / 0
except ZeroDivisionError:
    traceback.print_exc()

traceback.print_exc() prints the active traceback from inside an exception handler. Catching an error without logging or re-raising it is dangerous because the original failure becomes hidden.

Entrée, sortie et fichiers10

Read one input line
name = input().strip()

`input` reads one line from standard input, removes the trailing newline and always returns a `str`. Leading and trailing spaces remain unless `strip` is called separately.

Read all standard input
import sys
tokens = sys.stdin.buffer.read().split()

`sys.stdin.buffer.read` quickly reads all input as `bytes`, and `split` separates it on arbitrary whitespace. This approach is convenient for algorithmic problems but keeps the entire input in memory.

Read standard input line by line
import sys
while line := sys.stdin.readline():
    line = line.rstrip("\r\n")

`sys.stdin.readline` preserves the trailing newline and returns an empty string at end of input. Reading line by line avoids loading the entire input into memory.

Configure printed output
import sys
print(a, b, sep=",", end="\n", file=sys.stderr)

`sep` defines the separator between arguments, `end` defines the output suffix, and `file` selects the target text stream. Many individual `print` calls can be slower than building one string with `join`.

Read and write text files
with open("input.txt", "r", encoding="utf-8") as f:
    text = f.read()
with open("output.txt", "w", encoding="utf-8") as f:
    f.write(text)

`with open` guarantees that a file is closed even if an exception occurs, and an explicit encoding makes behaviour portable. Mode `w` truncates an existing file before writing.

JSON serialization
import json
data = json.loads(text)
text = json.dumps(data, ensure_ascii=False)

`json.loads` parses a JSON string into Python objects, while `json.dumps` performs the reverse conversion. JSON does not preserve the distinction between lists and tuples, and object keys are strings after parsing.

CSV reading and writing
import csv
with open("data.csv", newline="", encoding="utf-8") as f:
    rows = list(csv.reader(f))
with open("out.csv", "w", newline="", encoding="utf-8") as f:
    csv.writer(f).writerows(rows)

`csv.reader` and `csv.writer` correctly handle delimiters, quoting and line breaks inside fields. CSV files should be opened with `newline=""`, and read values remain strings by default.

Path objects
from pathlib import Path
path = Path("data") / "input.txt"
text = path.read_text(encoding="utf-8")
path.write_text(text, encoding="utf-8")

`pathlib.Path` creates portable path objects and joins path components with `/`. `read_text` and `write_text` are convenient for small files but process the entire content at once.

Environment variables
import os
token = os.environ.get("API_TOKEN")

`os.environ` exposes process environment variables as a mutable mapping of strings. Square-bracket access raises `KeyError` for a missing key, while `get` returns `None` or a supplied default.

Command-line arguments
import sys
script, *args = sys.argv

`sys.argv` contains command-line arguments as strings, with index `0` usually holding the executed script name. It performs no validation or conversion; use `argparse` for a structured command-line interface.