Справочник по Python
Синтаксис, примеры и короткие пояснения — от основ языка до алгоритмических приёмов, классов и тестов.
Основы языка13
n = 42
ratio = 1.5
name = "Ada"
flag = True
nothing = NoneТип выводится из значения, объявлять его не нужно. Проверить: type(x), isinstance(x, int).
f"{name} solved {n} tasks"
f"{ratio:.2f}"
f"{n:>5}"
f"{n=}"Подстановка выражений в строку. :.2f — два знака после точки, :>5 — выравнивание, {n=} печатает и имя, и значение.
if items:
...
if not s:
...
bool(0), bool(""), bool([]) # FalseПустые коллекции, 0, "" и None ложны. Пишут if items, а не if len(items) > 0.
a, b = b, a
first, *rest = [1, 2, 3, 4]
x = y = 0Обмен значений без временной переменной; *rest забирает остаток последовательности.
sign = "+" if n >= 0 else "-"Условие в одну строку. Возвращает значение, поэтому его можно присвоить или вернуть.
7 // 2 # 3
7 % 2 # 1
divmod(7, 2) # (3, 1)
-7 // 2 # -4// округляет вниз, а не к нулю: -7 // 2 это -4. divmod отдаёт частное и остаток сразу.
len([10, 20, 30]) # 3`len()` — встроенная функция, которая использует протокол `__len__`, поэтому одинаково работает с разными контейнерами. Метода `.len()` в Python нет.
print("a", "b", 3, sep="-", end="\n")`print()` принимает несколько значений, вставляет `sep` между ними и добавляет `end` после последнего. По умолчанию используются пробел и перевод строки.
n = int(input())`input()` всегда возвращает строку, даже если пользователь ввёл число. `int()` выбросит `ValueError`, если строка не является корректным целым числом.
s = str(42)
n = int("42")
x = float("3.5")
h = int("ff", 16)`str`, `int` и `float` создают значение нужного типа, а второй аргумент `int()` задаёт основание системы счисления. Некорректная строка приводит к `ValueError`.
isinstance(True, int) # True
type(True) is int # False`isinstance()` учитывает наследование и обычно подходит для проверки типа лучше. `type(x) is T` проверяет только точное совпадение типа.
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items`None` используют как безопасный маркер, когда изменяемый объект нужно создавать отдельно при каждом вызове. Проверять его следует через `is None`, а не через `== None`.
a = [1]
b = [1]
a == b # True
a is b # False
x = 1
y = 1
x is y # True`==` сравнивает значения, а `is` проверяет, являются ли ссылки одним объектом. Малые целые числа могут переиспользоваться интерпретатором, поэтому `is` нельзя применять для сравнения чисел.
Условия и циклы10
if score >= 90:
grade = "A"
elif score >= 75:
grade = "B"
else:
grade = "C"Условия проверяются сверху вниз, выполняется первая подходящая ветка. Более общий случай может скрыть расположенный ниже частный.
a == b
a != b
a < b
a <= b
a is None
0 <= x < n
a < b == c
x not in blockedЦепочка сравнений эквивалентна нескольким проверкам через and, но среднее выражение вычисляется один раз. С None обычно сравнивают через is или is not.
value = cached or compute()
result = ready and data
flag = not itemsand и or используют короткое замыкание и возвращают один из операндов, а не обязательно bool. Оператор not всегда возвращает bool.
for item in items:
process(item)
while left < right:
left += 1for перебирает элементы итерируемого объекта, а while повторяется, пока условие истинно. В while состояние должно изменяться, иначе цикл может стать бесконечным.
range(stop)
range(start, stop)
range(start, stop, step)
range(n - 1, -1, -1)range создаёт ленивую целочисленную последовательность и не включает stop. Для движения назад нужен отрицательный step, а нулевой шаг запрещён.
for x in items:
if x == target:
breakbreak немедленно завершает ближайший цикл. Во вложенных циклах внешний цикл продолжит работу.
for x in items:
if x < 0:
continue
process(x)continue пропускает остаток текущей итерации и переходит к следующей. В while перед continue нельзя забывать обновлять переменные цикла.
for x in items:
if x == target:
found = True
break
else:
found = Falseelse у цикла выполняется, только если цикл завершился без break. Для пустого итерируемого объекта блок else тоже выполняется.
match token:
case 0:
kind = "zero"
case int() if token > 0:
kind = "positive"
case _:
kind = "other"match выбирает первый подходящий шаблон, а _ служит универсальным случаем. Перехода в следующую ветку после совпадения нет.
for key, value in pairs:
process(key, value)
for i, (x, y) in enumerate(points):
process(i, x, y)Цель цикла может сразу распаковывать вложенную структуру. Количество и форма элементов должны совпадать, если не используется цель со звёздочкой.
Функции10
def bounds(values):
return min(values), max(values)
low, high = bounds(items)Несколько возвращаемых значений фактически упаковываются в кортеж. При распаковке количество переменных должно совпадать с количеством элементов.
def power(base, exponent=2):
return base ** exponentЗначение по умолчанию используется, когда аргумент не передан. Выражение значения по умолчанию вычисляется один раз при создании функции.
def add_item(item, items=[]):
items.append(item)
return itemsИзменяемый объект по умолчанию сохраняется между вызовами, поэтому данные неожиданно накапливаются. Обычно вместо него используют None и создают объект внутри функции.
def collect(first, *args, **kwargs):
return first, args, kwargs*args собирает лишние позиционные аргументы в кортеж, а **kwargs — именованные в словарь. Обычные параметры связываются раньше этих коллекций.
def clamp(value, *, low=0, high=100):
return max(low, min(value, high))Параметры после * можно передавать только по имени. Это предотвращает ошибки из-за неочевидного порядка нескольких похожих аргументов.
key = lambda item: item[1]
result = sorted(items, key=key)lambda создаёт небольшую функцию из одного выражения и не поддерживает обычные инструкции. Для сложной логики читаемее использовать def.
def make_adder(delta):
def add(value):
return value + delta
return addЗамыкание сохраняет доступ к именам из внешней функции после её завершения. Имена захватываются по ссылке, поэтому при создании функций в цикле возможна ловушка позднего связывания.
count = 0
def outer():
total = 0
def update():
global count
nonlocal total
count += 1
total += 1
return total
return updateglobal изменяет имя уровня модуля, а nonlocal — имя из ближайшей внешней области видимости, где это имя уже определено. Без этих объявлений присваивание создаёт локальное имя.
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 сохраняет результаты вызовов и особенно полезен для повторяющихся рекурсивных подзадач. Аргументы должны быть хешируемыми, а maxsize=None создаёт неограниченный кеш.
def binary_search(a: list[int], target: int) -> int | None:
...Аннотации описывают ожидаемые типы параметров и результата, но сами по себе не проверяют их во время выполнения. Объединение X | None означает, что результат может отсутствовать.
Строки7
s[1:4]
s[:3]
s[-2:]
s[::-1]
s[::2]Срез не включает правую границу. s[::-1] разворачивает строку, s[::2] берёт каждый второй символ, отрицательные индексы считают с конца.
"a,b,c".split(",") # ["a", "b", "c"]
"a b c".split()
",".join(["a", "b"]) # "a,b"split() без аргумента режет по любым пробелам и отбрасывает пустые куски. join собирает обратно.
s.strip()
s.lower()
s.upper()
s.replace("a", "b")
s.startswith("ab")
s.endswith(".py")
s.count("a")
s.find("a") # -1Строки неизменяемы: методы возвращают новую строку. find отдаёт -1, если не нашёл, index бросает ошибку.
s.isdigit()
s.isalpha()
s.isalnum()
c.isupper()
c.islower()
"ABC123".isupper() # True
"123".isupper() # False
"ab" in sisdigit/isalpha/isalnum истинны, только если строка непустая и подходит целиком. isupper/islower проверяют все символы с регистром: цифры и знаки игнорируются, но хотя бы один такой символ нужен. in проверяет, есть ли символ или подстрока.
sorted(s)
sorted(s1) == sorted(s2)
set("aab") == set("ab") # True
sorted("aab") == sorted("ab") # Falsesorted(s) даёт отсортированный список символов: равенство таких списков — обычная проверка на анаграмму. Множество для этого не годится: оно теряет количество повторов. Оба варианта различают регистр, пробелы и знаки.
parts = []
for w in words:
parts.append(w)
result = "".join(parts)Не складывайте строки в цикле через += — каждая операция копирует всё. Копите в список и склейте один раз.
ord('a') # 97
chr(97) # 'a'
ord(c) - ord('a')Код символа и обратно. ord(c) - ord("a") даёт позицию буквы — частый приём для счётчика из 26 ячеек.
Строки: продвинутое10
"{name}: {score:.2f}".format(name="Ada", score=9.876)
score = 9.876
f"{score:.2f}"`str.format` подставляет значения в шаблон, а f-строка вычисляет выражения непосредственно в текущей области видимости. Спецификаторы управляют точностью, знаком и представлением, а литеральные фигурные скобки записываются как `{{` и `}}`.
x = "42"
f"{x:>8}"
f"{x:<8}"
f"{x:*^8}"Спецификаторы `<`, `>` и `^` выравнивают значение влево, вправо и по центру, а символ перед ними задает заполнение. Ширина является минимальной и не обрезает слишком длинное значение.
table = str.maketrans({"a": "@", "e": "3"})
"peace".translate(table)`str.maketrans` создает таблицу преобразований, а `translate` применяет ее ко всем символам за один проход. Значение `None` в таблице удаляет соответствующий символ.
head, sep, tail = "key=value".partition("=")`partition` делит строку только по первому вхождению и всегда возвращает три элемента, включая сам разделитель. Если разделитель не найден, исходная строка находится в первом элементе, а два остальных пусты.
"a\nb\r\nc".splitlines() # ["a", "b", "c"]`splitlines` распознает разные виды переносов строк и по умолчанию удаляет их. В отличие от `split("\n")`, метод корректно обрабатывает несколько стандартных разделителей строк.
"42".zfill(5) # "00042"
"-42".zfill(5) # "-0042"`zfill` дополняет строку нулями слева до минимальной ширины и размещает нули после знака числа. Метод не обрезает строку, если она уже длиннее указанной ширины.
"unhappy".removeprefix("un") # "happy"
"report.csv".removesuffix(".csv") # "report"`removeprefix` и `removesuffix` удаляют одно точное совпадение только с соответствующего края строки. В отличие от `strip`, их аргумент не интерпретируется как набор отдельных символов.
import re
re.match(r"\d+", text)
re.search(r"\d+", text)
re.findall(r"\d+", text)
re.sub(r"\s+", " ", text)`match` проверяет совпадение только с начала строки, `search` ищет первое совпадение в любом месте, `findall` собирает все совпадения, а `sub` заменяет их. Для многократно используемого шаблона выгодно один раз вызвать `re.compile`.
data = "café".encode("utf-8")
text = data.decode("utf-8")`encode` преобразует `str` в `bytes`, а `decode` выполняет обратное преобразование с указанной кодировкой. Использование разных кодировок может вызвать ошибку или испортить текст.
"Straße".lower() # "straße"
"Straße".casefold() # "strasse"`lower` выполняет обычное преобразование в нижний регистр, а `casefold` агрессивнее свёртывает регистр (ß → ss) для регистронезависимого сравнения. Это не нормализация Unicode: `é` одним символом и `e` с комбинирующим ударением после `casefold` по-прежнему не равны — для этого нужен `unicodedata.normalize`.
Списки и срезы13
a = [0, 1, 2, 3, 4]
a[1:4] # [1, 2, 3]
a[:3]
a[-2:]
a[::-1]
b = a[:]Срез создаёт новый список. a[:] копирует верхний уровень — вложенные списки останутся общими.
a.append(x) # O(1)
a.extend([x, y])
a.insert(0, x) # O(n)
a.pop() # O(1)
a.pop(0) # O(n)Вставка и удаление в начале списка стоят O(n). Если нужны обе стороны — берите collections.deque.
for i, x in enumerate(a):
...
for i, x in enumerate(a, start=1):
...Индекс и значение сразу. Заменяет for i in range(len(a)) и не даёт промахнуться на границе.
for x, y in zip(a, b):
...
list(zip(a, b))
zip(*matrix)Идёт по нескольким последовательностям параллельно и останавливается на самой короткой. zip(*matrix) транспонирует матрицу.
[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}Короче и быстрее цикла с append. Два for подряд разворачивают вложенный список в плоский, фигурные скобки с двоеточием дают словарь. Порядок вложенных for такой же, как в обычном цикле.
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 останавливаются на первом решающем элементе. key задаёт, по чему сравнивать; default спасает от ошибки на пустом списке.
grid = [[0] * m for _ in range(n)] # ✓
grid = [[0] * m] * n # ✗Второй вариант неверен: умножение списка копирует ссылку, а не содержимое, и все строки окажутся одной и той же. Стройте через включение.
a = [1, 2, 3]
b = list(range(3))
zeros = [0] * 5
rows = [[0] * 3] * 2Литерал и `list()` создают список, а умножение повторяет его элементы. При повторении вложенного изменяемого объекта копируются ссылки, поэтому строки в `rows` связаны между собой.
a = [4, 2, 4]
i = a.index(4)
n = a.count(4)`index()` возвращает позицию первого совпадения, а `count()` — число совпадений. Если значения нет, `index()` выбрасывает `ValueError`.
a = [10, 20, 30, 20]
a.remove(20)
del a[0]
x = a.pop()`remove()` удаляет первое совпавшее значение, `del` удаляет по индексу или срезу, а `pop()` удаляет и возвращает элемент. Отсутствующее значение для `remove()` и неверный индекс приводят к исключению.
from copy import deepcopy
a = [[1], [2]]
b = a.copy()
c = deepcopy(a)
a[0].append(9)`copy()` создаёт новый внешний список, но сохраняет ссылки на вложенные объекты. `deepcopy()` рекурсивно копирует вложенное содержимое, но обычно требует больше времени и памяти.
def add(a, b):
return a + b
nums = [2, 3]
result = add(*nums)Оператор `*` передаёт элементы списка как отдельные позиционные аргументы. Их количество должно подходить сигнатуре функции, если она не принимает `*args`.
point = (2, 3)
seen = {point}Кортеж похож на список, но его элементы нельзя заменить, добавить или удалить. Кортеж можно поместить в множество или использовать как ключ словаря, только если все его элементы хешируемы.
Словари и множества13
d[k] # KeyError
d.get(k) # None
d.get(k, 0)
d.setdefault(k, []).append(x)get не бросает исключение. setdefault создаёт значение, если ключа ещё нет, и сразу возвращает его.
for k in d:
...
for k, v in d.items():
...
d.keys()
d.values()Перебор по словарю идёт по ключам. Порядок вставки сохраняется начиная с Python 3.7.
from collections import Counter
c = Counter(a)
c[x] # 0
c.most_common(2)
Counter(s1) == Counter(s2)Готовый счётчик частот. Отсутствующий ключ даёт 0, а не ошибку; most_common отдаёт топ по убыванию.
from collections import defaultdict
g = defaultdict(list)
g[k].append(x)
cnt = defaultdict(int)
cnt[k] += 1Значение создаётся само при первом обращении. Удобно для группировки и списков смежности графа.
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Проверка вхождения за O(1) вместо O(n) у списка. & это пересечение, | объединение, - разность. Множество не хранит порядок и не допускает повторов.
seen = set()
for x in a:
if x in seen:
return True
seen.add(x)Поиск дубликата за один проход и O(n). Тот же приём даёт «два слагаемых»: искать target - x в seen.
d1 = {"a": 1}
d2 = dict(a=1)
d3 = dict([("a", 1)])
d4 = {k: 0 for k in ("a", "b")}Словарь можно создать литералом, через `dict()`, из последовательности пар или словарным включением. Именованные аргументы `dict()` подходят только для строковых ключей, являющихся допустимыми идентификаторами.
d = {"a": 1, "c": 3}
"a" in d # True
x = d.pop("a", 0)
y = d.pop("b", 0)
del d["c"]Оператор `in` для словаря проверяет ключи, а не значения. `del` выбрасывает `KeyError` для отсутствующего ключа, тогда как `pop(key, default)` возвращает запасное значение.
d = {"a": 1, "b": 2}
keys = d.keys()
values = d.values()
pairs = d.items()
d["c"] = 3
list(keys) # ["a", "b", "c"]Методы `keys()`, `values()` и `items()` возвращают динамические представления, а не списки. Они отражают последующие изменения словаря; для независимого снимка нужна явная конвертация в `list`.
a = {"x": 1}
b = {"x": 2, "y": 3}
c = a | b
a.update(b)Оператор `|` создаёт новый словарь, а `update()` изменяет существующий. При совпадении ключей побеждает значение из правого словаря или переданного источника.
d = {"a": 3, "b": 1, "c": 2}
pairs = sorted(d.items(), key=lambda item: item[1])Для сортировки по значениям сортируют пары из `items()` с ключом по второму элементу. Результатом является список кортежей, а не автоматически отсортированный словарь.
freq = {}
for x in [2, 1, 2]:
freq[x] = freq.get(x, 0) + 1Частоты можно считать обычным словарём, получая ноль для ещё не встречавшегося ключа. Без значения по умолчанию `get()` вернёт `None`, которое нельзя складывать с числом.
edge = frozenset({2, 5})
weights = {edge: 7}`frozenset` — неизменяемое и хешируемое множество, поэтому его можно использовать как ключ словаря или элемент другого множества. Обычный `set` для этого не подходит.
Коллекции стандартной библиотеки10
from collections import deque
q = deque([2, 3])
q.appendleft(1)
q.append(4)
left = q.popleft()
right = q.pop()deque позволяет добавлять и удалять элементы с обоих концов за O(1). Для очереди используй append и popleft, а не pop(0) у списка.
from collections import deque
q = deque([1, 2, 3], maxlen=3)
q.append(4)
q.rotate(1)
q.rotate(-1)maxlen автоматически удаляет элементы с противоположного конца при переполнении. rotate сдвигает элементы вправо при положительном аргументе и влево при отрицательном.
import heapq
h = [4, 1, 7, 2]
heapq.heapify(h)
heapq.heappush(h, 0)
smallest = heapq.heappop(h)heapq реализует минимальную кучу: на вершине всегда находится наименьший элемент. heapify перестраивает список за O(n), а push и pop работают за O(log n).
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 и nsmallest удобны для выбора k крайних элементов без полной сортировки. Максимальную кучу обычно имитируют, сохраняя числа с противоположным знаком.
from collections import Counter
a = Counter("banana")
a.update("band")
top = a.most_common(2)
b = Counter("an")
added = a + b
common = a & bCounter хранит частоты элементов и поддерживает most_common, update и операции между счетчиками. Арифметические операции создают новый Counter и обычно исключают нулевые и отрицательные результаты.
from collections import defaultdict
groups = defaultdict(list)
counts = defaultdict(int)
seen = defaultdict(set)
groups["a"].append(1)
value = counts["missing"]defaultdict создаёт значение через фабрику при обращении к отсутствующему ключу. Простое чтение через квадратные скобки изменяет словарь, поэтому для проверки существования используй in или get.
from collections import OrderedDict
d = {"a": 1, "b": 2}
od = OrderedDict(d)
od.move_to_end("a")
first = od.popitem(last=False)Обычный dict уже сохраняет порядок вставки, поэтому OrderedDict нужен главным образом для явного перемещения и удаления с нужного конца. Сравнение двух OrderedDict также учитывает порядок элементов.
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(2, 3)
x = p.xnamedtuple создаёт компактный неизменяемый тип с доступом к полям по имени и индексу. Для сложных моделей с методами и значениями по умолчанию обычно удобнее dataclass.
from array import array
nums = array("i", [1, 2, 3])
raw = bytes([65, 66, 67])
mutable = bytearray(raw)
mutable[0] = 90array хранит значения одного примитивного типа плотнее обычного списка, но редко нужен в алгоритмических задачах. bytes неизменяем, а bytearray допускает изменение отдельных байтов.
from collections import ChainMap
defaults = {"timeout": 30}
overrides = {"timeout": 10}
config = ChainMap(overrides, defaults)
value = config["timeout"]ChainMap выполняет поиск по нескольким словарям слева направо без их копирования и объединения. Запись по умолчанию изменяет только первый словарь цепочки.
Итерация и генераторы10
squares_list = [x * x for x in values]
squares_gen = (x * x for x in values)Списковое включение сразу создаёт весь список, а генераторное выражение выдаёт элементы по запросу. Генератор экономит память, но обычно проходит только один раз.
def countdown(n):
while n > 0:
yield n
n -= 1Наличие yield превращает функцию в генераторную: вызов возвращает генератор, а тело выполняется при переборе. После каждого yield выполнение продолжается с сохранённого места.
def flatten(chunks):
for chunk in chunks:
yield from chunkyield from передаёт элементы вложенного итерируемого объекта без ручного внутреннего цикла. Он разворачивает только один уровень за каждое применение.
it = iter([10])
first = next(it)
missing = next(it, None)
try:
next(it)
except StopIteration:
...iter получает итератор, а next извлекает следующий элемент и сдвигает его состояние. Значение по умолчанию предотвращает StopIteration при исчерпании.
from itertools import count, cycle, islice
numbers = list(islice(count(10, 2), 4))
pattern = list(islice(cycle("ab"), 5))count и cycle создают потенциально бесконечные итераторы, а islice ограничивает их без материализации всей последовательности. islice не поддерживает отрицательные индексы и отрицательный шаг.
from itertools import chain
merged = list(chain([1, 2], [3, 4]))
flat = list(chain.from_iterable([[1, 2], [3, 4]]))chain лениво перебирает несколько итерируемых объектов один за другим. chain.from_iterable принимает внешний итерируемый объект и разворачивает один уровень.
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 строит декартово произведение, permutations учитывает порядок, а combinations — нет. Все функции возвращают итераторы, но число результатов может расти комбинаторно.
from itertools import groupby
items = [1, 1, 2, 2, 2, 3]
groups = [(key, list(group)) for key, group in groupby(items)]groupby объединяет только соседние элементы с одинаковым ключом. Для общей группировки данные обычно сначала сортируют по тому же ключу.
a = [1, 2, 3]
it = reversed(a)
b = list(it)reversed возвращает итератор обратного обхода и не изменяет исходную последовательность. Объект должен поддерживать обратную итерацию или доступ по индексам и длину.
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 потребляет все значения генератора, а any и all завершаются при первом достаточном результате. Для пустого источника any возвращает False, а all — True.
Числа и математика10
n = 10**100
n.bit_length()Тип `int` хранит целые числа произвольного размера, поэтому переполнения фиксированной разрядности нет. Ограничения определяются памятью и временем вычислений, но начиная с Python 3.11 преобразование между `int` и `str` ограничено 4300 знаками и сверх этого вызывает `ValueError`.
0.1 + 0.2 == 0.3 # False
x = float("inf")Тип `float` использует двоичное приближение, поэтому многие десятичные дроби хранятся неточно. Для сравнений обычно применяют `math.isclose`, а для точной десятичной арифметики — `decimal.Decimal`.
round(2.5) # 2
round(3.5) # 4
round(12.345, 2)`round` округляет точную середину к ближайшему четному значению, а не всегда вверх. Результат для `float` также зависит от погрешности двоичного представления.
abs(-7) # 7
2**10 # 1024
pow(2, 10) # 1024
pow(2, 10, 1000) # 24`abs` возвращает модуль, а `**` и `pow` возводят число в степень. Трехаргументный `pow(a, b, mod)` эффективно вычисляет степень по модулю без создания огромного промежуточного числа.
from math import sqrt, isqrt, gcd, factorial
sqrt(81) # 9.0
isqrt(80) # 8
gcd(18, 24) # 6
factorial(5) # 120`sqrt` возвращает вещественный корень, а `isqrt` — точную целую часть корня без ошибок `float`. `gcd` вычисляет НОД, а `factorial` принимает только неотрицательные целые числа.
from math import ceil, floor, inf
ceil(2.1) # 3
floor(2.9) # 2
inf > 10**100 # True`ceil` округляет к положительной бесконечности, а `floor` — к отрицательной, что особенно важно для отрицательных чисел. `math.inf` сравнивается как бесконечность, но не возникает автоматически при делении на ноль.
try:
1 / 0
except ZeroDivisionError:
...Деление, целочисленное деление и остаток с нулевым делителем вызывают `ZeroDivisionError`. Python не превращает такое деление в бесконечность.
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` может разбирать строки с основанием от 2 до 36, а основание `0` распознает стандартный префикс автоматически. Преобразование `float` в `int` отбрасывает дробную часть к нулю, а `bin`, `hex` и `oct` возвращают строки с префиксами.
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Побитовые операции работают с двоичным представлением целых чисел и часто используются для масок и множеств состояний. Целые числа не имеют фиксированной ширины, а `~x` всегда равно `-x - 1`.
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` генерирует воспроизводимые псевдослучайные значения: `randrange` исключает правую границу, а `randint` включает ее. Модуль не подходит для паролей и криптографии.
Сортировка и поиск6
b = sorted(a)
a.sort()
sorted(a, reverse=True)sorted возвращает новый список, sort меняет исходный и возвращает None. Сортировка устойчивая.
sorted(words, key=len)
sorted(pairs, key=lambda p: p[1])
sorted(a, key=lambda x: (-x[1], x[0]))key вызывается по разу на элемент. Кортеж задаёт несколько уровней, минус разворачивает числовое поле.
a.sort(key=lambda x: x.age)
a.sort(key=lambda x: x.name)Равные элементы сохраняют исходный порядок. Поэтому сортировать можно последовательно, начиная с младшего ключа.
from operator import itemgetter
sorted(rows, key=itemgetter(1, 0))То же, что lambda, но быстрее и читается лучше при сортировке по нескольким полям.
from bisect import bisect_left, bisect_right, insort
i = bisect_left(a, x)
j = bisect_right(a, x)
insort(a, x)Двоичный поиск по отсортированному списку за O(log n). bisect_left даёт первую позицию x, bisect_right — позицию сразу за ним, поэтому j - i это количество вхождений. insort вставляет, сохраняя порядок.
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Список должен быть отсортирован. Границы двигают за mid, иначе цикл зациклится.
Стек, очередь и два указателя9
stack = []
stack.append(1)
stack.append(2)
top = stack.pop()Список эффективно реализует стек LIFO через append и pop с конца за амортизированное O(1). Удаление с начала списка требует O(n).
from collections import deque
q = deque([1, 2])
q.append(3)
first = q.popleft()Очередь FIFO удобно реализуется через deque: append добавляет справа, а popleft удаляет слева за O(1). Список с pop(0) для большой очереди не подходит из-за сдвига элементов.
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Монотонный стек хранит элементы или индексы в отсортированном по стеку порядке и находит ближайший больший или меньший элемент за O(n). В стек часто нужно класть индексы, чтобы вычислять расстояния и позиции.
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Открывающие скобки кладутся в стек, а закрывающая должна совпасть с его вершиной. После обработки стек обязан быть пустым, иначе остались незакрытые скобки.
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Указатели с двух концов сокращают область поиска, когда направление движения можно определить по текущему результату. Для поиска пары по сумме массив должен быть отсортирован.
def middle_node(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slowБыстрый указатель движется быстрее медленного, что позволяет искать середину или цикл без дополнительной памяти. Условия цикла должны проверять и fast, и следующий узел перед двойным переходом.
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Указатель чтения просматривает массив, а указатель записи формирует корректный префикс без дополнительного массива. Этот вариант предполагает, что одинаковые элементы стоят рядом, обычно после сортировки.
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Обмен элементов с двух концов разворачивает изменяемую последовательность за O(n) времени и O(1) дополнительной памяти. Строку так изменить нельзя, потому что она неизменяема.
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Два указателя сливают отсортированные последовательности за O(n + m), каждый раз выбирая меньший текущий элемент. После завершения основного цикла нужно добавить оставшийся хвост.
Скользящее окно и префиксные суммы9
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Фиксированное окно обновляет состояние, добавляя новый элемент и удаляя вышедший, поэтому все окна обрабатываются за O(n). Нужно отдельно определить поведение для k, равного нулю или превышающего длину массива.
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Переменное окно расширяется справа и сжимается слева, пока выполняется нужное условие. Метод с суммой обычно требует неотрицательных элементов, иначе монотонность окна нарушается.
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Частоты в окне обновляются только для входящего и выходящего элементов, что избавляет от повторного подсчёта. Нулевые значения лучше удалять, иначе сравнение Counter может стать менее очевидным.
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Сумма следующего окна получается из предыдущей вычитанием левого и добавлением правого элемента. Инициализируй максимум первым полным окном, а не нулём, если числа могут быть отрицательными.
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)Префиксный массив с начальным нулём позволяет получать сумму полуинтервала [left, right) за O(1). Дополнительный ноль упрощает границы и запросы, начинающиеся с первого элемента.
nums = [3, 8, 10, 15]
diffs = [b - a for a, b in zip(nums, nums[1:])]Разности соседних элементов описывают изменения между позициями и помогают восстанавливать значения через накопление. Результат короче исходной последовательности на один элемент.
from itertools import accumulate
prefix = list(accumulate([2, 4, 1], initial=0))
products = list(accumulate([2, 3, 4], lambda a, b: a * b))accumulate лениво выдаёт промежуточные результаты накопления и поддерживает пользовательскую бинарную функцию. initial добавляет начальное значение в результат и влияет на его длину.
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Подотрезок имеет сумму target, если два префикса отличаются на target. Множество префиксов даёт O(n) и работает с отрицательными числами, в отличие от обычного окна по сумме.
def first_true(low, high, feasible):
while low < high:
mid = (low + high) // 2
if feasible(mid):
high = mid
else:
low = mid + 1
return lowДвоичный поиск по ответу ищет границу, где монотонный предикат меняется с False на True. Главная сложность — правильно выбрать включительные границы и доказать монотонность feasible.
Рекурсия и динамическое программирование10
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)Базовый случай останавливает рекурсию, а рекурсивный шаг сводит задачу к меньшему экземпляру. Без достижимого базового случая вызовы закончатся RecursionError.
import sys
sys.setrecursionlimit(200000)Python ограничивает глубину стека и при превышении выбрасывает RecursionError. Повышать лимит нужно осторожно: слишком глубокая рекурсия может исчерпать системный стек.
def factorial_tail(n, acc=1):
if n <= 1:
return acc
return factorial_tail(n - 1, acc * n)Хвостовой вызов является последней операцией функции, но Python не оптимизирует такие вызовы. Поэтому хвостовая рекурсия все равно расходует стек и обычно заменяется циклом.
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)Декораторы сохраняют результаты вызовов по аргументам и устраняют повторные вычисления. cache эквивалентен lru_cache с неограниченным размером, а аргументы должны быть хешируемыми.
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]Подход снизу вверх вычисляет состояния от простых к сложным и хранит их в массиве. Он не использует рекурсивный стек, но иногда массив можно сократить до нескольких переменных.
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]Двумерная таблица используется, когда состояние зависит от двух параметров, например позиции в строке и столбце. Важно правильно инициализировать границы до заполнения остальных ячеек.
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Помимо оптимального значения DP может хранить выбор, из которого получено каждое состояние. Затем ответ восстанавливается обратным проходом от конечного состояния.
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:]Задача делится на независимые подзадачи, их решения вычисляются рекурсивно и затем объединяются. Эффективность зависит от сбалансированности разбиения и стоимости объединения.
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 resultBacktracking выбирает вариант, запускает рекурсию и обязательно откатывает измененное состояние. Полная генерация перестановок требует факториального времени.
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Для каждого элемента рекурсия рассматривает два выбора: взять его или пропустить. Список текущего ответа нужно копировать при сохранении и откатывать после рекурсивного вызова.
Графы и деревья10
edges = [(0, 1), (0, 2), (1, 2)]
graph = {0: [], 1: [], 2: []}
for u, v in edges:
graph[u].append(v)
graph[v].append(u)Список смежности хранит для каждой вершины ее соседей и обычно требует O(V + E) памяти. Для ориентированного графа обратное ребро добавлять не нужно.
from collections import deque
def bfs(graph, start):
queue = deque([start])
visited = {start}
order = []
while queue:
node = queue.popleft()
order.append(node)
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
return orderBFS обходит граф по слоям с помощью очереди и находит кратчайшие расстояния в невзвешенном графе. Вершину лучше отмечать посещенной при добавлении в очередь, чтобы не добавлять ее повторно.
def dfs_recursive(graph, start):
visited = set()
order = []
def dfs(node):
visited.add(node)
order.append(node)
for neighbor in graph[node]:
if neighbor not in visited:
dfs(neighbor)
dfs(start)
return orderРекурсивный DFS полностью исследует одну ветвь перед переходом к следующей. На длинных цепочках он может превысить лимит рекурсии Python.
def dfs_iterative(graph, start):
stack = [start]
visited = set()
order = []
while stack:
node = stack.pop()
if node in visited:
continue
visited.add(node)
order.append(node)
stack.extend(reversed(graph[node]))
return orderИтеративный DFS использует явный стек и не зависит от лимита рекурсии. Порядок обхода зависит от порядка добавления соседей в стек.
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 первым достигает вершины по пути с минимальным числом ребер. Для восстановления самого пути достаточно сохранить родителя каждой впервые посещенной вершины.
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 обрабатывает узел до детей, inorder между левым и правым поддеревьями, а postorder после детей. Inorder дает отсортированный порядок только для корректного бинарного дерева поиска.
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Обход по уровням является BFS по дереву и использует очередь. Размер очереди в начале итерации фиксирует число узлов текущего уровня.
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)В ориентированном графе ребро в вершину текущего стека DFS означает цикл. Обычно используются три состояния: не посещена, обрабатывается и полностью обработана.
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Алгоритм Кана последовательно удаляет вершины с нулевой входящей степенью. Если обработаны не все вершины, граф содержит цикл и топологического порядка не существует.
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 TrueUnion-Find быстро проверяет принадлежность вершин одной компоненте и объединяет компоненты. Сжатие путей и объединение по размеру дают почти постоянное амортизированное время операции.
Исключения10
try:
value = int(text)
except ValueError:
value = 0
else:
value += 1
finally:
closed = Trueexcept выполняется при исключении, else только при успешном завершении try, а finally выполняется всегда. В блок try лучше помещать только операции, исключения которых действительно ожидаются.
try:
item = data[key]
except KeyError:
item = NoneПерехватывайте конкретный тип исключения, чтобы не скрывать посторонние ошибки программы. Голый except допустим только в редких случаях, когда исключение затем корректно обрабатывается или повторно выбрасывается.
try:
result = values[index] / divisor
except (IndexError, ZeroDivisionError):
result = NoneНесколько типов с одинаковой обработкой перечисляются кортежем в одном except. Если обработка различается, следует использовать отдельные блоки except.
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:
raiseraise создает исключение, а raise без аргументов повторно выбрасывает текущее. Конструкция raise ... from ... сохраняет явную причинную цепочку между исключениями.
class InvalidStateError(Exception):
pass
def require_ready(ready):
if not ready:
raise InvalidStateError("not ready")Собственные исключения наследуются от Exception и позволяют отличать ошибки предметной области от встроенных ошибок. Обычно достаточно пустого класса с понятным названием.
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 означает неподходящее значение корректного типа, TypeError — неподходящий тип, KeyError и IndexError — отсутствующий ключ или индекс. ZeroDivisionError возникает при делении или взятии остатка по нулю.
def divide(a, b):
assert b != 0
return a / bassert предназначен для проверки внутренних инвариантов и при нарушении вызывает AssertionError. При запуске Python с оптимизацией -O проверки assert удаляются, поэтому ими нельзя валидировать пользовательские данные.
with open("data.txt", encoding="utf-8") as file:
text = file.read()Контекстный менеджер гарантирует освобождение ресурса при обычном завершении и при исключении. Для файлов это означает автоматическое закрытие после выхода из блока with.
from contextlib import suppress
from pathlib import Path
path = Path("data.txt")
with suppress(FileNotFoundError):
path.unlink()contextlib.suppress игнорирует только явно перечисленные типы исключений и подходит для коротких ожидаемых случаев. Его не следует использовать для сокрытия ошибок, которые требуют обработки или журналирования.
def eafp(data, key):
try:
return data[key]
except KeyError:
return None
def lbyl(data, key):
if key in data:
return data[key]
return NoneEAFP сначала выполняет операцию и обрабатывает ожидаемую ошибку, а LBYL сначала проверяет условие. EAFP часто естественнее для Python и избегает расхождения между проверкой и самой операцией.
Классы и объекты10
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def move(self, dx, dy):
self.x += dx
self.y += dyКласс описывает структуру объектов, а __init__ заполняет состояние нового экземпляра. self передается методу автоматически и указывает на конкретный объект.
class Counter:
total = 0
def __init__(self):
self.value = 0
Counter.total += 1Атрибут класса общий для всех экземпляров, а атрибут self принадлежит отдельному объекту. Изменение изменяемого атрибута класса может неожиданно затронуть все экземпляры.
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 позволяет обращаться к методу как к обычному атрибуту и добавлять проверку чтения или записи. Внутри свойства обычно используют отдельный атрибут, иначе возникнет бесконечная рекурсия.
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 не получает ни экземпляр, ни класс, а @classmethod получает класс через cls. Методы класса удобны для альтернативных конструкторов и корректно работают с наследниками.
class Base:
def __init__(self, value):
self.value = value
class Child(Base):
def __init__(self, value, extra):
super().__init__(value)
self.extra = extraНаследник получает поведение базового класса и может расширять или переопределять его. super() вызывает следующую реализацию по порядку разрешения методов, что особенно важно при множественном наследовании.
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__ предназначен для разработчика и отладки, а __str__ — для читаемого вывода пользователю. Если __str__ отсутствует, str() использует __repr__.
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__ определяет логическое равенство, а __hash__ позволяет использовать объект как ключ словаря или элемент множества. Равные объекты обязаны иметь одинаковый хеш, а поля хеширования не должны меняться.
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__ определяет оператор < и позволяет sorted() сравнивать объекты напрямую. Сравнение кортежей удобно задает основной и дополнительные критерии сортировки.
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 автоматически создаёт методы вроде __init__ и __repr__, а frozen=True запрещает обычное изменение полей. Неизменяемое значение по умолчанию задаётся напрямую, а изменяемое, например [], вызывает ValueError при создании класса и требует default_factory.
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y__slots__ ограничивает допустимые атрибуты экземпляра и часто уменьшает расход памяти. Без включения __dict__ объекту нельзя динамически добавлять другие атрибуты.
Аннотации типов10
count: int = 3
def repeat(text: str, times: int) -> str:
return text * times
count = '3'Аннотации описывают ожидаемые типы для анализаторов, IDE и документации. Python не проверяет их автоматически во время выполнения и допускает присваивание значения другого типа.
items: list[int] = [1, 2]
scores: dict[str, int] = {'a': 1}
point: tuple[int, int] = (2, 3)Встроенные коллекции параметризуются типами элементов, ключей и значений. tuple[int, int] описывает фиксированную структуру, а tuple[int, ...] — кортеж произвольной длины.
from typing import Optional
left: Optional[int] = None
right: int | None = NoneOptional[int] и int | None означают, что значение может быть int или None. Optional не означает необязательный аргумент: для этого у параметра должно быть значение по умолчанию.
from typing import Union
value_a: Union[int, str] = 1
value_b: int | str = 'x'Union[int, str] и int | str разрешают один из нескольких типов. Слишком широкие объединения усложняют сужение типов и обработку всех вариантов.
from typing import Any
value: Any = 1
value = 'x'
value = [1, 2]Any отключает статическую проверку операций со значением и совместим с любым типом в обе стороны. Его избыток скрывает ошибки и сводит пользу аннотаций к минимуму.
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] описывает вызываемый объект с двумя аргументами и возвращаемым типом R. Callable[..., R] используют, когда точный набор аргументов неизвестен.
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 гарантирует возможность обхода, но не индексирование и не повторный проход. Sequence дополнительно поддерживает длину и доступ по индексу, поэтому является более строгим контрактом.
from typing import Literal, TypeAlias
UserId: TypeAlias = int
Mode: TypeAlias = Literal['fast', 'safe']TypeAlias дает сложному типу понятное имя, но не создает новый несовместимый тип. Literal ограничивает значение конкретным набором литералов и полезен для режимов и состояний.
from typing import TypedDict
class UserData(TypedDict):
name: str
age: int
user: UserData = {'name': 'a', 'age': 20}TypedDict описывает обязательные ключи и типы значений обычного словаря. Во время выполнения объект остается dict, а структура проверяется только статическими инструментами.
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.valueTypeVar связывает типы в разных частях сигнатуры, а Generic создает класс, параметризованный этим типом. В отличие от Any, анализатор сохраняет конкретный тип для каждого использования.
Тесты и отладка10
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5pytest обнаруживает функции, имя которых начинается с test (по умолчанию python_functions = test*, обычно пишут test_), и считает упавший assert ошибкой теста. В assert лучше сравнивать конкретный результат, чтобы отчёт показывал полезную разницу.
import pytest
def test_zero_division():
with pytest.raises(ZeroDivisionError):
1 / 0pytest.raises проверяет, что блок выбрасывает исключение нужного типа. Если исключение не возникло или его тип отличается, тест падает.
import pytest
@pytest.mark.parametrize('x, expected', [(1, 1), (2, 4), (3, 9)])
def test_square(x, expected):
assert x * x == expectedparametrize запускает одну тестовую функцию для каждого набора входных данных. Наборы остаются независимыми, поэтому отчет показывает конкретный упавший случай.
import pytest
@pytest.fixture
def sample():
return [1, 2, 3]
def test_length(sample):
assert len(sample) == 3Фикстура подготавливает данные или ресурсы и передается тесту по имени аргумента. Избыточно общие фикстуры создают скрытые зависимости и усложняют понимание теста.
import pytest
def test_ratio():
assert 0.1 + 0.2 == pytest.approx(0.3)pytest.approx сравнивает числа с допустимой абсолютной или относительной погрешностью. Прямое равенство float ненадежно из-за двоичного представления дробей.
import unittest
class MathTest(unittest.TestCase):
def test_add(self):
self.assertEqual(2 + 3, 5)unittest.TestCase — встроенная классовая модель тестирования с методами assertEqual, setUp и tearDown. Имя тестового метода должно начинаться с префикса test (TestLoader.testMethodPrefix), иначе стандартный загрузчик его не обнаружит; по соглашению пишут test_.
def square(x):
"""
>>> square(3)
9
"""
return x * x
if __name__ == '__main__':
import doctest
doctest.testmod()doctest выполняет примеры из docstring и сравнивает вывод с записанным результатом. Он удобен для коротких примеров, но хрупок при изменениях форматирования.
import logging
value = 3
print(value)
logging.basicConfig(level=logging.DEBUG)
logging.debug('%s', value)print удобен для быстрой локальной проверки, а logging поддерживает уровни, форматирование и перенаправление сообщений. В библиотечном и серверном коде logging обычно предпочтительнее.
from timeit import timeit
elapsed = timeit('sum(range(100))', number=10_000)timeit многократно выполняет небольшой фрагмент и уменьшает влияние случайного шума замера. Сравнивать нужно эквивалентные операции в одинаковом окружении, а не делать вывод по одному запуску.
import traceback
try:
1 / 0
except ZeroDivisionError:
traceback.print_exc()traceback.print_exc() выводит текущую трассировку внутри обработчика исключения. Перехватывать ошибку без логирования или повторного выброса опасно, потому что причина сбоя теряется.
Ввод, вывод и файлы10
name = input().strip()`input` читает одну строку из стандартного ввода, удаляет завершающий перенос и всегда возвращает `str`. Пробелы по краям остаются, если отдельно не вызвать `strip`.
import sys
tokens = sys.stdin.buffer.read().split()`sys.stdin.buffer.read` быстро считывает весь ввод как `bytes`, а `split` разбивает его по любым пробельным символам. Такой подход удобен в алгоритмических задачах, но хранит весь ввод в памяти.
import sys
while line := sys.stdin.readline():
line = line.rstrip("\r\n")`sys.stdin.readline` сохраняет завершающий перенос строки и возвращает пустую строку при достижении конца ввода. Построчное чтение не требует загружать все данные в память.
import sys
print(a, b, sep=",", end="\n", file=sys.stderr)`sep` задает разделитель между аргументами, `end` — окончание вывода, а `file` — целевой текстовый поток. Большое количество вызовов `print` может быть медленнее формирования одной строки через `join`.
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` гарантирует закрытие файла даже при исключении, а явная кодировка делает поведение переносимым. Режим `w` полностью очищает существующий файл перед записью.
import json
data = json.loads(text)
text = json.dumps(data, ensure_ascii=False)`json.loads` разбирает JSON-строку в объекты Python, а `json.dumps` выполняет обратное преобразование. JSON не сохраняет различие между списками и кортежами, а ключи объектов после чтения являются строками.
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` и `csv.writer` корректно обрабатывают разделители, кавычки и переносы внутри полей. Файлы CSV следует открывать с `newline=""`, а прочитанные значения по умолчанию остаются строками.
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` создает переносимые объекты путей и объединяет части оператором `/`. `read_text` и `write_text` удобны для небольших файлов, но загружают или записывают содержимое целиком.
import os
token = os.environ.get("API_TOKEN")`os.environ` предоставляет переменные окружения процесса как изменяемое отображение строк. Доступ через квадратные скобки вызывает `KeyError` при отсутствии ключа, а `get` возвращает `None` или заданное значение по умолчанию.
import sys
script, *args = sys.argv`sys.argv` содержит аргументы командной строки как строки, причем элемент с индексом `0` обычно является именем запущенного скрипта. Модуль не выполняет проверку и преобразование аргументов; для сложного интерфейса используют `argparse`.