Skip to content

Python Шпаргалка

Универсальный, читаемый язык для веба, данных, ИИ и автоматизации.

01

Начало работы

Hello World и комментарии

Python использует # для комментариев и тройные кавычки для докстрок. Функция print() поддерживает параметры sep и end для настройки форматирования вывода. Докстроки служат документацией, доступной через help() и __doc__.

python
# This is a single-line comment
"""
This is a
multi-line comment (docstring)
"""
print("Hello, World!")        # print to stdout
print("A", "B", "C", sep="-") # A-B-C
print("No newline", end="")   # suppress newline

Отступы и блоки кода

В отличие от большинства языков, Python использует отступы, а не фигурные скобки для определения блоков кода. Последовательность критически важна — смешивание табов и пробелов вызывает SyntaxError. PEP 8 рекомендует 4 пробела на уровень.

python
# Python uses indentation (4 spaces) to define blocks
if True:
    print("inside if")
    if True:
        print("nested block")
print("outside block")

# No braces! Indentation IS the syntax
def func():
    x = 1
    return x + 1

Ввод и вывод

input() читает из stdin как строку — всегда преобразуйте, когда нужно число. Используйте int(), float() и т.д. для преобразования. F-строки (Python 3.6+) — предпочтительный способ форматирования строк.

python
# input() always returns a string
name = input("Enter your name: ")
age = int(input("Enter your age: "))  # convert to int

print(f"Hello {name}, you are {age} years old")

# formatted output
print("Pi is approximately {:.2f}".format(3.14159))
print(f"{1000000:,}")  # 1,000,000 with thousands separator

Несколько операторов и продолжение строк

Используйте точки с запятой для разделения операторов в одной строке (редко в идиоматичном Python). Длинные строки можно продолжать обратной косой чертой или автоматически внутри (), [], {}. Предпочитайте неявное продолжение для читаемости.

python
# multiple statements on one line (discouraged)
a = 1; b = 2; c = 3

# explicit line continuation
total = 1 + 2 + 3 + \
        4 + 5 + 6

# implicit continuation inside brackets
nums = [
    1, 2, 3,
    4, 5, 6
]

result = (1 + 2
          + 3 + 4)

Выполнение Python

Скрипты Python запускаются с помощью 'python script.py'. REPL позволяет интерактивное experimentation. Всегда используйте python3 в системах, где python указывает на Python 2. Строка shebang делает скрипты исполняемыми на Unix.

python
# Run a script
# $ python script.py

# Run interactively (REPL)
# $ python
# >>> 2 + 2
# 4

# Shebang line for Unix scripts
#!/usr/bin/env python3

# Check Python version
import sys
print(sys.version)
print(sys.version_info.major)  # 3
02

Переменные и типы данных

Переменные и динамическая типизация

Python использует динамическую типизацию — переменные могут менять тип во время выполнения. Используйте type() для проверки, isinstance() для верификации. Python 3.6+ поддерживает аннотации типов (name: str = 'Alice') для поддержки IDE без принуждения во время выполнения.

python
# Python is dynamically typed - no declaration needed
name = "Alice"          # str
age = 30                # int
height = 5.7            # float
is_active = True        # bool
items = [1, 2, 3]       # list

# Type checking
print(type(name))       # <class 'str'>
print(isinstance(age, int))  # True

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

Аннотации типов (Python 3.6+)

Аннотации типов улучшают читаемость кода и включают автодополнение в IDE и статический анализ с mypy. Они НЕ принуждаются во время выполнения — Python остаётся динамически типизированным. Используйте Optional[X] для значений, которые могут быть None.

python
# Variable annotations
name: str = "Alice"
age: int = 30
scores: list[float] = [90.5, 85.0]

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

# Optional and Union
from typing import Optional, Union
def find(id: int) -> Optional[str]:
    return "Alice" if id == 1 else None

# mypy for static type checking
# $ mypy script.py

Преобразование типов

В Python есть встроенные функции преобразования: int(), float(), str(), bool(), list(), tuple(), set(), dict(). Ложные значения включают 0, '', [], {}, None, False. int() усекает в сторону нуля, тогда как round() использует банковское округление.

python
# String to number
num_str = str(42)          # "42"
num = int("42")            # 42
float_num = float("3.14")  # 3.14

# Number conversions
print(int(3.99))           # 3 (truncates toward zero)
print(int(-3.99))          # -3
print(round(3.14159, 2))   # 3.14

# Boolean conversion
print(bool(0))             # False
print(bool(""))            # False
print(bool([]))            # False
print(bool("anything"))    # True

# Collection conversions
print(list("abc"))         # ['a', 'b', 'c']
print(tuple([1, 2, 3]))    # (1, 2, 3)
print(set([1, 1, 2]))      # {1, 2}

Числовые типы

Целые числа Python имеют произвольную точность (без переполнения). Числа с плавающей точкой — это IEEE 754 doubles с обычными проблемами точности. Комплексные числа встроены. Булевы значения являются подклассом int (True==1, False==0). Используйте подчёркивания в числовых литералах для читаемости.

python
# Integers (arbitrary precision)
big = 10 ** 100  # no overflow
print(type(big))  # <class 'int'>

# Floats (IEEE 754 double)
pi = 3.14159
print(0.1 + 0.2)  # 0.30000000000000004

# Complex numbers
z = 3 + 4j
print(z.real, z.imag)  # 3.0 4.0
print(abs(z))          # 5.0

# Boolean is subclass of int
print(isinstance(True, int))  # True
print(True + True)            # 2

# Underscores in numbers (3.6+)
million = 1_000_000
binary = 0b_1010_1010

Константы и соглашения об именовании

В Python нет ключевого слова const — имена в ALL_CAPS являются константами только по соглашению (ничто не предотвращает переназначение). PEP 8 определяет именование: snake_case для переменных/функций, PascalCase для классов, ALL_CAPS для констант. Ведущее подчёркивание означает 'приватное' по соглашению; двойное подчёркивание вызывает name mangling.

python
# Python has no true constants - convention only
MAX_SIZE = 100          # ALL_CAPS for constants
PI = 3.14159

# Naming conventions (PEP 8)
variable_name = "snake_case"      # variables, functions
ClassName = "PascalCase"          # classes
CONSTANT_VALUE = 100              # constants
_private_var = "underscore prefix" # private (convention)
__name_mangled = "double underscore" # name mangling

# dunder names (reserved)
__name__, __main__, __init__
03

Строки

Строковые методы

Строки неизменяемы — методы возвращают новые строки. find() возвращает -1, если не найдено, тогда как index() вызывает ValueError. Используйте isalpha()/isdigit()/isalnum() для валидации. Класс str имеет более 40 методов — изучите через dir(str).

python
s = "Hello, World"

# Case operations
print(s.upper())          # HELLO, WORLD
print(s.lower())          # hello, world
print(s.title())          # Hello, World
print(s.capitalize())     # Hello, world
print(s.swapcase())       # hELLO, wORLD

# Search & replace
print(s.find("World"))    # 7 (index, -1 if not found)
print(s.index("World"))   # 7 (raises ValueError if not found)
print(s.replace("o", "0"))  # Hell0, W0rld
print(s.count("l"))       # 3

# Validation
print("abc".isalpha())    # True
print("123".isdigit())    # True
print("  ".isspace())     # True

Форматирование строк

F-строки — современный, самый быстрый и читаемый способ форматирования строк. Они поддерживают спецификаторы формата после двоеточия: :.2f для 2 знаков после запятой, :>10 для выравнивания по правому краю шириной 10, :, для разделителя тысяч. Избегайте %-форматирования в новом коде.

python
name = "Alice"
age = 30

# f-strings (Python 3.6+) - PREFERRED
print(f"Hello, {name}! You are {age}.")
print(f"{name.upper()} is {age * 365} days old")
print(f"{3.14159:.2f}")      # 3.14
print(f"{42:>10}")           # right-align
print(f"{42:<10}")           # left-align
print(f"{42:^10}")           # center
print(f"{1000000:,}")        # 1,000,000

# str.format() method
print("Hello, {}!".format(name))
print("{name} is {age}".format(name="Bob", age=25))

# Old style (avoid in new code)
print("Hello, %s!" % name)

Срезы и индексация

Синтаксис срезов Python [start:stop:step] мощный — stop исключительный. Отрицательные индексы отсчитываются с конца. s[::-1] — идиоматичный способ перевернуть строку. Срезы безопасны: индексы вне диапазона возвращают пустые строки вместо ошибок.

python
s = "Hello, World"

# Indexing (0-based, negative from end)
print(s[0])        # H
print(s[-1])       # d
print(s[7])        # W

# Slicing [start:stop:step]
print(s[0:5])      # Hello
print(s[7:])       # World
print(s[:5])       # Hello
print(s[::2])      # HloWrd (every 2nd char)
print(s[::-1])     # dlroW ,olleH (reverse!)

# Length
print(len(s))      # 12

# Slicing never raises IndexError
print(s[100:200])  # '' (empty string)

Разделение и объединение

split() разделяет строку в список, join() объединяет список в строку. Всегда используйте join() для эффективного объединения множества строк — оператор + создаёт промежуточные строки. partition() разделяет ровно на 3 части (до, разделитель, после).

python
# Split
csv = "a,b,c,d"
print(csv.split(","))          # ['a', 'b', 'c', 'd']
print(csv.split(",", 2))       # ['a', 'b', 'c,d'] (max 2 splits)

# Splitlines
text = "line1\nline2\nline3"
print(text.splitlines())       # ['line1', 'line2', 'line3']

# Partition (splits on first occurrence)
print("[email protected]".partition("@"))
# ('user', '@', 'domain.com')

# Join
words = ["Hello", "World"]
print(" ".join(words))         # Hello World
print("-".join(["2024", "01", "15"]))  # 2024-01-15
print("".join(["a", "b", "c"])) # abc

# String concatenation
s = "Hello" + " " + "World"
parts = ["a"]
parts += "b"  # NOT string concat - adds chars to list!

Обрезка и заполнение

strip() удаляет ведущие/завершающие пробелы по умолчанию или указанные символы. zfill() заполняет ведущими нулями (полезно для ID). rjust/ljust/center заполняют до указанной ширины с опциональным символом заполнения.

python
# Strip whitespace (or specified chars)
s = "  hello  "
print(s.strip())    # "hello"
print(s.lstrip())   # "hello  "
print(s.rstrip())   # "  hello"

# Strip specific characters
print("xxxhelloxxx".strip("x"))  # hello

# Padding / centering
print("42".zfill(5))      # 00042
print("hi".rjust(10))     # "        hi"
print("hi".ljust(10, "-")) # "hi--------"
print("hi".center(10, "*")) # "****hi****"

# expandtabs
print("a\tb".expandtabs(4))  # "a   b"

Сырые строки и экранирование

Сырые строки (r'...') воспринимают обратные косые черты буквально — необходимы для шаблонов regex и путей к файлам Windows. Строки в тройных кавычках сохраняют переносы строк. Строки поддерживают оператор * для повторения и + для объединения.

python
# Escape sequences
print("Line1\nLine2")     # newline
print("Tab\there")        # tab
print("Quote: \"hi\"")   # escaped quotes
print("Backslash: \\")   # literal backslash

# Raw strings (ignore escapes) - great for regex
path = r"C:\Users\name\file.txt"
regex = r"\d{3}-\d{4}"
print(path)  # C:\Users\name\file.txt

# Triple-quoted strings
multi = """
Multiple
lines
"""

# String multiplication
print("ab" * 3)  # ababab
04

Числа и математика

Арифметические операторы

В Python 7 арифметических операторов. / всегда возвращает float, // — целочисленное деление (округляет в сторону минус бесконечности). ** — возведение в степень (не ^ — это XOR). Результат оператора % принимает знак делителя, в отличие от C/Java.

python
# Basic operators
print(7 + 3)   # 10  addition
print(7 - 3)   # 4   subtraction
print(7 * 3)   # 21  multiplication
print(7 / 3)   # 2.333...  true division (always float)
print(7 // 3)  # 2   floor division
print(7 % 3)   # 1   modulo (remainder)
print(7 ** 3)  # 343 exponentiation

# Floor division with negatives
print(-7 // 3)  # -3 (rounds toward negative infinity)
print(-7 % 3)   # 2 (result has same sign as divisor)

# Augmented assignment
x = 10
x += 5   # x = x + 5
x **= 2  # x = x ** 2

Модуль math

Модуль math предоставляет математические функции и константы. Все тригонометрические функции используют радианы — преобразуйте через math.radians()/degrees(). math.gcd() находит наибольший общий делитель. Для комплексных чисел используйте модуль cmath.

python
import math

# Constants
print(math.pi)       # 3.141592653589793
print(math.e)        # 2.718281828459045
print(math.inf)      # inf
print(math.nan)      # nan

# Functions
print(math.sqrt(16))     # 4.0
print(math.pow(2, 10))   # 1024.0
print(math.log(100, 10)) # 2.0 (log base 10)
print(math.log(math.e))  # 1.0 (natural log)
print(math.factorial(5)) # 120
print(math.gcd(12, 8))   # 4

# Rounding
print(math.floor(3.7))   # 3
print(math.ceil(3.2))    # 4
print(math.trunc(-3.7))  # -3 (toward zero)

# Trigonometry (radians)
print(math.sin(math.pi / 2))  # 1.0
print(math.degrees(math.pi))  # 180.0

Модуль random

Модуль random использует PRNG Mersenne Twister — НЕ криптостойкий. Используйте модуль secrets для безопасности. random.sample() выбирает уникальные элементы, random.choices() допускает дубликаты. Установите seed для воспроизводимых результатов в тестировании.

python
import random

# Random integers
print(random.randint(1, 100))    # 1 to 100 inclusive
print(random.randrange(0, 10, 2)) # even number 0,2,4,6,8

# Random floats
print(random.random())           # 0.0 to 1.0
print(random.uniform(1.0, 10.0)) # random float in range

# Choice & sampling
colors = ["red", "green", "blue"]
print(random.choice(colors))           # one random item
print(random.sample(colors, 2))        # 2 unique items
print(random.choices(colors, k=5))     # 5 items (with replacement)

# Shuffle (in-place)
nums = [1, 2, 3, 4, 5]
random.shuffle(nums)
print(nums)

# Reproducible randomness
random.seed(42)  # same seed = same sequence

Decimal и Fractions

Используйте Decimal для финансовых вычислений, где ошибки точности float недопустимы (например, деньги). Используйте Fraction для точной рациональной арифметики. Оба медленнее float, но избегают ошибок округления. Всегда создавайте Decimal из строк, а не из float.

python
from decimal import Decimal, getcontext
from fractions import Fraction

# Float precision issues
print(0.1 + 0.2)  # 0.30000000000000004

# Decimal for exact decimal arithmetic
a = Decimal("0.1")
b = Decimal("0.2")
print(a + b)  # 0.3 (exact!)

# Set precision
getcontext().prec = 6
print(Decimal(1) / Decimal(7))  # 0.142857

# Fractions for exact rational arithmetic
f1 = Fraction(1, 3)
f2 = Fraction(1, 6)
print(f1 + f2)  # 1/2
print(float(f1))  # 0.3333...

# Fraction from string
print(Fraction("3/4"))  # 3/4

Побитовые операторы

Побитовые операторы манипулируют отдельными битами целых чисел. Целые числа Python имеют произвольную точность, поэтому сдвиги работают иначе, чем в языках с фиксированной шириной. Частые применения: флаги, маски, низкоуровневый разбор протоколов. x & (x-1) == 0 проверяет, является ли x степенью 2.

python
# Bitwise operators work on integers
a = 0b1010  # 10
b = 0b1100  # 12

print(a & b)   # 8  (0b1000) AND
print(a | b)   # 14 (0b1110) OR
print(a ^ b)   # 6  (0b0110) XOR
print(~a)      # -11 (NOT, two's complement)
print(a << 2)  # 40 (left shift, multiply by 4)
print(a >> 1)  # 5  (right shift, divide by 2)

# Binary representation
print(bin(10))   # 0b1010
print(hex(255))  # 0xff
print(oct(8))    # 0o10
print(int("1010", 2))  # 10 (parse binary)

# Common tricks
print(5 & 1)     # 1 (check odd: nonzero = odd)
print(8 & (8-1)) # 0 (check power of 2)
05

Структуры данных

Списки

Списки — самая универсальная структура данных Python — упорядоченные, изменяемые и гетерогенные. append() — O(1), insert(0, x) — O(n). Используйте collections.deque для быстрых операций на обоих концах. sort() на месте, sorted() возвращает новый список.

python
# Lists are ordered, mutable sequences
nums = [1, 2, 3, 4, 5]
mixed = [1, "hello", True, 3.14]

# Adding elements
nums.append(6)           # [1,2,3,4,5,6]
nums.insert(0, 0)        # [0,1,2,3,4,5,6]
nums.extend([7, 8])      # extend with another list

# Removing elements
nums.remove(0)           # remove by value
popped = nums.pop()      # remove & return last
popped = nums.pop(0)     # remove & return by index
del nums[0]              # delete by index
nums.clear()             # remove all

# Slicing (same as strings)
nums = [1, 2, 3, 4, 5]
print(nums[1:3])    # [2, 3]
print(nums[::-1])   # [5, 4, 3, 2, 1] reverse

# Sorting
nums.sort()              # in-place sort
nums.sort(reverse=True)  # descending
sorted_nums = sorted(nums)  # returns new list

Кортежи

Кортежи неизменяемы и быстрее списков. Используйте их для фиксированных коллекций, множественных возвращаемых значений и ключей словаря (списки не могут быть ключами). Именованные кортежи предоставляют имена полей для читаемости. Одноэлементные кортежи требуют завершающей запятой.

python
# Tuples are ordered, IMMUTABLE sequences
point = (3, 4)
single = (42,)     # note the comma for single-element tuple
empty = ()

# Packing & unpacking
coordinates = 10, 20, 30  # packing
x, y, z = coordinates     # unpacking
x, y = y, x               # swap values!

# Multiple return values
def min_max(nums):
    return min(nums), max(nums)

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

# Named tuples (readable)
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y)       # 3 4
print(p[0], p[1])     # 3 4

# Tuples are immutable but can contain mutable objects
t = (1, [2, 3])
t[1].append(4)  # OK: (1, [2, 3, 4])

Словари

Словари — это хеш-таблицы — O(1) в среднем для поиска/вставки/удаления. Ключи должны быть хешируемыми (неизменяемыми). Начиная с Python 3.7 словари сохраняют порядок вставки. Используйте get(), чтобы избежать KeyError. dict comprehension элегантно создаёт словари.

python
# Dicts are key-value mappings (insertion-ordered since 3.7)
user = {"name": "Alice", "age": 30}

# Access
print(user["name"])          # Alice
print(user.get("email"))     # None (no KeyError)
print(user.get("email", "N/A"))  # N/A (default)

# Add/update
user["email"] = "[email protected]"    # add
user["age"] = 31             # update
user.setdefault("role", "user")  # set if missing

# Delete
del user["email"]
val = user.pop("age")        # remove & return
# user.clear()               # remove all

# Iteration
for key in user:             # keys
    print(key)
for k, v in user.items():    # key-value pairs
    print(k, v)
for v in user.values():      # values
    print(v)

# Dict comprehension
squares = {x: x**2 for x in range(5)}

# Merge dicts (3.9+)
merged = {"a": 1} | {"b": 2}

Множества

Множества — неупорядоченные коллекции уникальных, хешируемых элементов. Они превосходят в проверке принадлежности (O(1) против O(n) для списков) и алгебре множеств (объединение, пересечение, разность). frozenset неизменяем и хешируем. Порядок не гарантируется.

python
# Sets are unordered collections of unique elements
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

# Set operations
print(a | b)    # union: {1, 2, 3, 4, 5, 6}
print(a & b)    # intersection: {3, 4}
print(a - b)    # difference: {1, 2}
print(a ^ b)    # symmetric difference: {1, 2, 5, 6}

# Methods
a.add(5)        # add element
a.discard(10)   # remove if present (no error)
a.remove(1)     # remove (KeyError if missing)
a.update([6, 7]) # add multiple

# Membership test (O(1) - faster than list)
print(3 in a)   # True

# Frozen set (immutable)
fs = frozenset([1, 2, 3])

# Common use: deduplicate
unique = list(set([1, 1, 2, 2, 3]))  # [1, 2, 3]

Компрехенсии

Компрехенсии — Pythonic способ лаконично создавать коллекции. Генераторные выражения (скобки вместо квадратных скобок) ленивые — они производят значения по требованию, экономя память. Предпочитайте компрехенсии map()/filter() для читаемости.

python
# List comprehension
squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
pairs = [(x, y) for x in range(3) for y in range(3)]

# Dict comprehension
square_map = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# Set comprehension
unique_lens = {len(w) for w in ["a", "ab", "abc", "ab"]}

# Generator expression (lazy, memory-efficient)
gen = (x**2 for x in range(1000000))
print(next(gen))  # 0
print(next(gen))  # 1
total = sum(x**2 for x in range(100))  # no extra list

# Nested comprehension (matrix)
matrix = [[i * 3 + j for j in range(3)] for i in range(3)]
# [[0,1,2], [3,4,5], [6,7,8]]

Модуль collections

Модуль collections предоставляет специализированные контейнеры. Counter подсчитывает хешируемые элементы. defaultdict автоматически создаёт отсутствующие ключи. deque предлагает O(1) append/pop на обоих концах (против O(n) для списков). Они необходимы для чистого, эффективного кода.

python
from collections import Counter, defaultdict, deque, OrderedDict

# Counter - counting
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
cnt = Counter(words)
print(cnt)  # Counter({'apple': 3, 'banana': 2, 'cherry': 1})
print(cnt.most_common(2))  # [('apple', 3), ('banana', 2)]

# defaultdict - no KeyError
dd = defaultdict(list)
dd["fruits"].append("apple")
dd["fruits"].append("banana")
# dd["vegs"] automatically creates empty list

# deque - fast double-ended queue
dq = deque([1, 2, 3])
dq.appendleft(0)   # [0, 1, 2, 3]
dq.append(4)       # [0, 1, 2, 3, 4]
dq.popleft()       # 0, deque is now [1, 2, 3, 4]
dq.rotate(1)       # rotate right

# OrderedDict (less needed since 3.7, dicts are ordered)
od = OrderedDict([("a", 1), ("b", 2)])
06

Управление потоком

If / Elif / Else

Python использует if/elif/else — обратите внимание 'elif', а не 'elseif'. Отступы определяют блоки. Тернарный оператор 'x if cond else y' — это выражение. Python рассматривает пустые коллекции, 0, None и False как ложные — полезно для лаконичных условий.

python
score = 85

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

print(f"Grade: {grade}")  # Grade: B

# Conditional expression (ternary)
status = "pass" if score >= 60 else "fail"

# Truthy/falsy values
# Falsy: False, 0, 0.0, "", [], {}, (), None
# Everything else is truthy
if []:    # False
    print("never")
if [0]:   # True (non-empty list)
    print("always")

Циклы for и итерация

Цикл for в Python итерирует по любому итерируемому объекту. range() генерирует числа (исключающий stop). enumerate() связывает элементы с индексами. zip() итерирует несколько последовательностей параллельно. Используйте .items() для итерации пар ключ-значение словаря.

python
# range(start, stop, step)
for i in range(5):        # 0, 1, 2, 3, 4
    print(i)
for i in range(2, 10, 2): # 2, 4, 6, 8
    print(i)
for i in range(10, 0, -1): # countdown
    print(i)

# Iterate over collections
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    print(fruit)

# enumerate for index + value
for idx, fruit in enumerate(fruits):
    print(f"{idx}: {fruit}")

# zip to iterate multiple sequences
names = ["Alice", "Bob"]
ages = [30, 25]
for name, age in zip(names, ages):
    print(f"{name}: {age}")

# Iterate dict
user = {"name": "Alice", "age": 30}
for key, value in user.items():
    print(f"{key} = {value}")

Циклы while и break/continue

Циклы while повторяются, пока условие истинно. break немедленно выходит из цикла, continue переходит к следующей итерации. Конструкция for/else выполняет блок else только если цикл не был прерван. pass — no-op заглушка для пустых блоков.

python
# Basic while
count = 0
while count < 5:
    print(count)
    count += 1

# break - exit loop
while True:
    cmd = input("> ")
    if cmd == "quit":
        break
    print(f"You said: {cmd}")

# continue - skip to next iteration
for i in range(10):
    if i % 2 == 0:
        continue  # skip even numbers
    print(i)  # prints 1, 3, 5, 7, 9

# else clause (runs if no break)
for i in range(5):
    if i == 10:
        break
else:
    print("Loop completed without break")

# pass - do nothing (placeholder)
for i in range(5):
    pass  # TODO: implement

Оператор match (Python 3.10+)

Оператор match (Python 3.10+) — мощное структурное сопоставление шаблонов, далеко выходящее за switch в C. Он может сопоставлять последовательности, отображения, экземпляры классов и связывать переменные. Шаблон _ — wildcard (по умолчанию). Гарды с 'if' добавляют условия.

python
# Structural pattern matching (like switch)
def handle_command(cmd):
    match cmd.split():
        case ["quit"]:
            return "Goodbye"
        case ["hello", name]:
            return f"Hello, {name}!"
        case ["move", direction] if direction in "NSEW":
            return f"Moving {direction}"
        case ["add", x, y]:
            return int(x) + int(y)
        case _:
            return "Unknown command"

print(handle_command("hello Alice"))  # Hello, Alice!

# Matching data structures
match point:
    case (0, 0):
        print("origin")
    case (0, y):
        print(f"on y-axis at {y}")
    case (x, 0):
        print(f"on x-axis at {x}")
    case (x, y):
        print(f"at ({x}, {y})")

Итераторы и генераторы

Генераторы лениво производят значения с помощью yield — они не вычисляют все значения заранее, экономя память. Они реализуют протокол итератора (iter() и next()). После исчерпания они завершены. Используйте генераторы для больших/бесконечных последовательностей, конвейеров и потоковых данных.

python
# Iterator protocol
nums = [1, 2, 3]
it = iter(nums)
print(next(it))  # 1
print(next(it))  # 2
print(next(it))  # 3
# next(it)  # StopIteration

# Generator function (uses yield)
def count_up_to(n):
    count = 1
    while count <= n:
        yield count
        count += 1

for num in count_up_to(5):
    print(num)  # 1, 2, 3, 4, 5

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

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

# Generator expression
squares = (x**2 for x in range(10))
07

Функции

Определение и вызов функций

Функции определяются с помощью def. Аргументы по умолчанию используют =. Python поддерживает именованные аргументы для ясности. Функции могут возвращать несколько значений (как кортеж). Докстроки (в тройных кавычках) документируют функции и доступны через help().

python
# Basic function
def greet(name):
    return f"Hello, {name}!"

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

# Default arguments
def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

print(greet("Bob"))              # Hello, Bob!
print(greet("Bob", "Hi"))        # Hi, Bob!

# Keyword arguments
print(greet(name="Carol", greeting="Hey"))

# Return multiple values (tuple)
def stats(nums):
    return min(nums), max(nums), sum(nums) / len(nums)

lo, hi, avg = stats([1, 2, 3, 4, 5])

# Docstrings
def add(a, b):
    """Add two numbers and return the result.
    
    Args:
        a: First number
        b: Second number
    Returns:
        Sum of a and b
    """
    return a + b

Аргументы: *args и **kwargs

*args собирает дополнительные позиционные аргументы в кортеж, **kwargs собирает дополнительные именованные аргументы в dict. Имена args/kwargs — соглашение. Можно распаковывать последовательности с * и словари с ** при вызове функций. Порядок: позиционные, *args, именованные, **kwargs.

python
# *args - variable positional arguments (tuple)
def sum_all(*args):
    return sum(args)

print(sum_all(1, 2, 3))       # 6
print(sum_all(1, 2, 3, 4, 5)) # 15

# **kwargs - variable keyword arguments (dict)
def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=30, role="admin")

# Combining all
def func(a, b, *args, **kwargs):
    print(f"a={a}, b={b}")
    print(f"args={args}")
    print(f"kwargs={kwargs}")

func(1, 2, 3, 4, x=5, y=6)
# a=1, b=2, args=(3, 4), kwargs={'x': 5, 'y': 6}

# Unpacking arguments
nums = [1, 2, 3]
print(sum_all(*nums))  # unpack list as args

opts = {"name": "Alice", "age": 30}
print_info(**opts)  # unpack dict as kwargs

Lambda и функции высшего порядка

Lambda ограничены одним выражением — используйте def для сложной логики. Они блистают как аргументы функций высшего порядка вроде sorted(), map(), filter(). Однако генераторные списки часто более читаемы, чем map/filter. reduce() находится в functools.

python
# Lambda - anonymous function (single expression)
square = lambda x: x ** 2
print(square(5))  # 25

# Common with sorted, map, filter, reduce
students = [("Alice", 85), ("Bob", 92), ("Carol", 78)]

# Sort by score (key function)
sorted_by_score = sorted(students, key=lambda s: s[1])
# [('Carol', 78), ('Alice', 85), ('Bob', 92)]

# map - apply function to each item
nums = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, nums))
# [2, 4, 6, 8, 10]

# filter - keep items where function returns True
evens = list(filter(lambda x: x % 2 == 0, nums))
# [2, 4]

# reduce - accumulate to single value
from functools import reduce
product = reduce(lambda a, b: a * b, nums)
# 120 (1*2*3*4*5)

# Prefer comprehensions over map/filter
doubled = [x * 2 for x in nums]  # more Pythonic
evens = [x for x in nums if x % 2 == 0]

Декораторы

Декораторы обёртывают функции для добавления поведения без изменения исходного кода. Синтаксис @ — синтаксический сахар. Декораторы с аргументами требуют дополнительного уровня вложенности. Всегда используйте functools.wraps для сохранения метаданных исходной функции (имя, докстрока).

python
# A decorator modifies a function's behavior
def uppercase_result(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper()
    return wrapper

@uppercase_result
def greet(name):
    return f"hello, {name}"

print(greet("alice"))  # HELLO, ALICE

# Decorator with arguments
def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def say_hi():
    print("Hi!")

say_hi()  # prints "Hi!" three times

# Practical: timing decorator
import time
def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.time() - start:.4f}s")
        return result
    return wrapper

# Use functools.wraps to preserve metadata
from functools import wraps
def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

Область видимости и замыкания

Python разрешает имена в порядке LEGB: Local, Enclosing, Global, Built-in. Используйте 'global' для переназначения глобальной переменной внутри функции. Используйте 'nonlocal' (Python 3) для изменения переменной в объемлющей области видимости. Замыкания запоминают свою объемлющую область видимости.

python
# LEGB rule: Local, Enclosing, Global, Built-in
x = "global"

def outer():
    x = "enclosing"
    
    def inner():
        x = "local"
        print(x)  # local
    
    inner()
    print(x)  # enclosing

outer()
print(x)  # global

# global keyword - modify global variable
count = 0
def increment():
    global count
    count += 1

# nonlocal keyword - modify enclosing variable
def make_counter():
    count = 0
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

c = make_counter()
print(c())  # 1
print(c())  # 2
print(c())  # 3
08

ООП и классы

Классы и объекты

Классы объединяют данные (атрибуты) и поведение (методы). __init__ — конструктор. self ссылается на экземпляр (как 'this' в других языках). Переменные класса общие; переменные экземпляра — для каждого объекта. __str__ для пользователей, __repr__ для разработчиков.

python
class Dog:
    # Class variable (shared by all instances)
    species = "Canis familiaris"
    
    # Constructor
    def __init__(self, name, age):
        # Instance variables
        self.name = name
        self.age = age
    
    # Instance method
    def bark(self):
        return f"{self.name} says Woof!"
    
    # String representation
    def __str__(self):
        return f"Dog({self.name}, {self.age})"
    
    # Official representation (for debugging)
    def __repr__(self):
        return f"Dog(name='{self.name}', age={self.age})"

# Create instances
buddy = Dog("Buddy", 3)
lucy = Dog("Lucy", 5)

print(buddy.bark())       # Buddy says Woof!
print(buddy.name)         # Buddy
print(buddy.species)      # Canis familiaris
print(str(buddy))         # Dog(Buddy, 3)

Наследование и полиморфизм

Наследование позволяет классам повторно использовать и расширять поведение. Python поддерживает множественное наследование с MRO (Method Resolution Order) для разрешения конфликтов. Полиморфизм позволяет единообразно обращаться с разными типами. Используйте isinstance() для проверки типа, а не type().

python
class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        raise NotImplementedError("Subclass must implement")

class Dog(Animal):
    def speak(self):
        return f"{self.name} says Woof!"

class Cat(Animal):
    def speak(self):
        return f"{self.name} says Meow!"

# Polymorphism - same interface, different behavior
def animal_sound(animal):
    print(animal.speak())

animals = [Dog("Buddy"), Cat("Whiskers")]
for a in animals:
    animal_sound(a)
# Buddy says Woof!
# Whiskers says Meow!

# Multiple inheritance
class Swimmer:
    def swim(self):
        return "swimming"

class Flyer:
    def fly(self):
        return "flying"

class Duck(Animal, Swimmer, Flyer):
    pass

duck = Duck("Donald")
print(duck.swim())  # swimming
print(duck.fly())   # flying

# Check inheritance
print(isinstance(duck, Animal))  # True
print(issubclass(Dog, Animal))   # True

Свойства и инкапсуляция

В Python нет настоящих private/protected — используются соглашения. Одинарное подчёркивание _ означает 'внутреннее'. Двойное подчёркивание __ вызывает name mangling (не настоящая приватность). @property превращает методы в атрибуты с геттерами/сеттерами, обеспечивая валидацию и вычисляемые свойства.

python
class Temperature:
    def __init__(self, celsius=0):
        self.celsius = celsius  # uses setter below
    
    # Getter
    @property
    def celsius(self):
        return self._celsius
    
    # Setter
    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("Below absolute zero!")
        self._celsius = value
    
    # Computed property
    @property
    def fahrenheit(self):
        return self._celsius * 9/5 + 32
    
    @fahrenheit.setter
    def fahrenheit(self, value):
        self.celsius = (value - 32) * 5/9

temp = Temperature(25)
print(temp.fahrenheit)  # 77.0
temp.fahrenheit = 100
print(temp.celsius)     # 37.78...

# Name conventions:
# _name   - protected (convention, not enforced)
# __name  - private (name mangling: _ClassName__name)
# __name__ - dunder (reserved by Python)

Методы класса и статические методы

@staticmethod — просто функция в пространстве имён класса — без неявного первого аргумента. @classmethod получает класс (cls) как первый аргумент, полезно для альтернативных конструкторов (фабричные методы) и поведения с учётом наследования. Используйте classmethod для конструкторов, staticmethod для утилит.

python
class MathUtils:
    pi = 3.14159
    
    # Static method - no self/cls, lives in class namespace
    @staticmethod
    def add(a, b):
        return a + b
    
    # Class method - receives the class as first argument
    @classmethod
    def circle_area(cls, radius):
        return cls.pi * radius ** 2
    
    # Alternative constructor (common classmethod use)
    @classmethod
    def from_diameter(cls, diameter):
        return cls()  # would configure instance

# Static: called on class or instance, no special first arg
print(MathUtils.add(2, 3))  # 5

# Class: often used for alternative constructors
print(MathUtils.circle_area(5))  # 78.54...

# Factory pattern with classmethod
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    @classmethod
    def origin(cls):
        return cls(0, 0)
    
    @classmethod
    def from_tuple(cls, coords):
        return cls(*coords)

p1 = Point.origin()
p2 = Point.from_tuple((3, 4))

Магические методы (Dunder)

Магические методы (dunder методы) реализуют перегрузку операторов и поведение протоколов. __add__ для +, __eq__ для ==, __len__ для len(), __iter__ для итерации/распаковки. Они позволяют вашим объектам работать со встроенным синтаксисом и функциями Python естественно.

python
class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    # String representation
    def __str__(self):
        return f"Vector({self.x}, {self.y})"
    
    # Operator overloading
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)
    
    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)
    
    # Equality
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y
    
    # Length
    def __len__(self):
        return int((self.x**2 + self.y**2) ** 0.5)
    
    # Make it iterable
    def __iter__(self):
        yield self.x
        yield self.y
    
    # Index access
    def __getitem__(self, index):
        return (self.x, self.y)[index]

v1 = Vector(2, 3)
v2 = Vector(4, 5)
print(v1 + v2)        # Vector(6, 8)
print(v1 * 3)         # Vector(6, 9)
print(v1 == Vector(2, 3))  # True
print(len(v1))        # 3
x, y = v1             # unpacking via __iter__
09

Обработка ошибок

Try / Except / Finally

try/except/else/finally: try выполняет рискованный код, except ловит ошибки, else выполняется при отсутствии исключения, finally всегда выполняется (очистка). Ловите конкретные исключения, а не пустой 'except:'. Блок else полезен, когда очистка должна выполняться только при успехе. Exception — базовый для большинства ловимых ошибок.

python
# Basic exception handling
try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"Error: {e}")  # division by zero
finally:
    print("This always runs")

# Multiple exception types
try:
    value = int("abc")
except (ValueError, TypeError) as e:
    print(f"Conversion error: {e}")

# Different handlers for different exceptions
try:
    f = open("nonexistent.txt")
    data = f.read()
except FileNotFoundError:
    print("File not found")
except PermissionError:
    print("No permission")
except Exception as e:
    print(f"Unexpected: {e}")
else:
    print("No exception occurred")
    f.close()
finally:
    print("Cleanup (always runs)")

# Exception hierarchy
# BaseException
#  ├── SystemExit
#  ├── KeyboardInterrupt
#  └── Exception
#       ├── ValueError
#       ├── TypeError
#       ├── KeyError
#       └── ...

Возбуждение исключений

Используйте raise для возбуждения исключений. 'raise' без аргументов повторно возбуждает текущее исключение (в блоке except). 'raise X from Y' связывает исключения, сохраняя исходную причину. Всегда возбуждайте конкретные типы исключений. Избегайте использования исключений для обычного потока управления.

python
# Raise an exception
def divide(a, b):
    if b == 0:
        raise ZeroDivisionError("Cannot divide by zero!")
    return a / b

# Re-raise the current exception
def process(data):
    try:
        return parse(data)
    except ValueError:
        print("Logging parse error...")
        raise  # re-raises the same exception

# Raise with context (from)
try:
    int("abc")
except ValueError as e:
    raise RuntimeError("Failed to process input") from e

# Common built-in exceptions
raise ValueError("invalid value")
raise TypeError("wrong type")
raise KeyError("missing key")
raise IndexError("out of range")
raise RuntimeError("something went wrong")
raise NotImplementedError("override this")
raise FileNotFoundError("no such file")

# Exception with custom args
class ValidationError(Exception):
    pass

raise ValidationError("field is required", "email")

Пользовательские исключения

Создавайте пользовательские исключения, наследуя Exception (или более конкретный встроенный). Спроектируйте иерархию так, чтобы вызывающие могли ловить на нужном уровне. Добавьте пользовательские атрибуты для контекста. Наследуйтесь от Exception, а не BaseException (который включает SystemExit/KeyboardInterrupt).

python
# Custom exception hierarchy
class AppError(Exception):
    """Base exception for the application."""
    pass

class DatabaseError(AppError):
    def __init__(self, message, query=None):
        super().__init__(message)
        self.query = query

class ValidationError(AppError):
    def __init__(self, field, message):
        super().__init__(f"{field}: {message}")
        self.field = field
        self.message = message

class AuthenticationError(AppError):
    pass

# Usage
def login(username, password):
    if not username:
        raise ValidationError("username", "is required")
    if password != "secret":
        raise AuthenticationError("Invalid credentials")

# Catching by hierarchy
try:
    login("", "x")
except ValidationError as e:
    print(f"Validation failed: {e.field}")
except AppError as e:
    print(f"App error: {e}")

# Access exception info
import traceback
try:
    1 / 0
except:
    traceback.print_exc()
    print(repr(sys.exc_info()[1]))

Менеджеры контекста (оператор with)

Менеджеры контекста (оператор 'with') гарантируют очистку через __enter__ и __exit__. Они необходимы для ресурсов вроде файлов, блокировок и подключений к БД. contextlib.contextmanager упрощает создание с помощью генератора. __exit__ может подавить исключение, вернув True.

python
# Context managers handle setup and cleanup
with open("file.txt") as f:
    content = f.read()
# file is automatically closed, even if an error occurs

# Multiple context managers
with open("input.txt") as fin, open("output.txt", "w") as fout:
    fout.write(fin.read())

# Creating a context manager (class-based)
class Timer:
    def __enter__(self):
        import time
        self.start = time.time()
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        import time
        self.elapsed = time.time() - self.start
        print(f"Elapsed: {self.elapsed:.4f}s")
        return False  # don't suppress exceptions

with Timer() as t:
    # code to time
    sum(range(1000000))

# contextlib for simpler context managers
from contextlib import contextmanager

@contextmanager
def open_db(url):
    db = connect(url)
    try:
        yield db
    finally:
        db.close()

with open_db("localhost") as db:
    db.query("SELECT 1")

Утверждения и логирование

Операторы assert предназначены для отладки инвариантов — они удаляются при запуске Python с -O (оптимизация). Никогда не используйте assert для валидации ввода. Используйте модуль logging вместо print() в production-коде — он поддерживает уровни, форматирование и направления вывода.

python
# Assertions - for debugging (removed with -O flag)
def divide(a, b):
    assert b != 0, "Divisor cannot be zero"
    return a / b

# Never use assertions for data validation (they can be disabled)
# Use them for invariant checks during development

# Logging (better than print for production)
import logging

# Configure logging
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s [%(levelname)s] %(message)s"
)

logger = logging.getLogger(__name__)

logger.debug("Detailed info for debugging")
logger.info("Confirmation things are working")
logger.warning("Something unexpected happened")
logger.error("A serious problem")
logger.critical("A fatal error")

# Log exceptions with traceback
try:
    1 / 0
except:
    logger.exception("Failed to divide")  # includes traceback
10

Файловый ввод-вывод

Чтение файлов

Всегда используйте 'with' для открытия файлов — он автоматически закрывает их даже при ошибке. Указывайте encoding='utf-8' для избежания проблем с кодировкой, зависящей от платформы. Для больших файлов итерируйте построчно вместо read() для экономии памяти. readlines() загружает весь файл в память.

python
# Read entire file
with open("file.txt", "r", encoding="utf-8") as f:
    content = f.read()
    print(content)

# Read line by line (memory-efficient for large files)
with open("file.txt", "r") as f:
    for line in f:
        print(line.strip())  # strip removes trailing newline

# Read all lines into a list
with open("file.txt") as f:
    lines = f.readlines()  # ['line1\n', 'line2\n', ...]

# Read specific number of characters
with open("file.txt") as f:
    chunk = f.read(100)  # first 100 chars

# File modes:
# "r"  read (default)
# "w"  write (truncate)
# "a"  append
# "x"  exclusive create (fails if exists)
# "b"  binary mode (e.g., "rb", "wb")
# "+"  read and write (e.g., "r+")

Запись файлов

Режим 'w' усекает файл (удаляет содержимое); используйте 'a' для добавления. writelines() не добавляет переносы строк — добавляйте их вручную. Используйте 'rb'/'wb' для бинарных файлов (изображения и т.д.). seek() перемещает курсор; tell() возвращает его позицию. Всегда указывайте кодировку для текстовых файлов.

python
# Write text (overwrites existing)
with open("output.txt", "w") as f:
    f.write("First line\n")
    f.write("Second line\n")
    
    # writelines doesn't add newlines
    f.writelines(["line3\n", "line4\n"])

# Append to a file
with open("log.txt", "a") as f:
    f.write("New log entry\n")

# Write binary data
with open("data.bin", "wb") as f:
    f.write(b"\x00\x01\x02\x03")

# Read and write simultaneously
with open("file.txt", "r+") as f:
    content = f.read()
    f.seek(0)           # move to beginning
    f.write("Updated")  # overwrite
    f.truncate()        # cut off remaining

# Check if file exists
import os
if os.path.exists("file.txt"):
    print("File exists")

Обработка путей (pathlib)

pathlib (Python 3.4+) — современный объектно-ориентированный способ работы с путями — предпочитайте его os.path. Оператор / объединяет пути платформо-независимо. Объекты Path имеют методы read_text()/write_text(), которые handle открытие/закрытие за вас. rglob() рекурсивно ищет.

python
from pathlib import Path

# Create Path objects (preferred over os.path)
p = Path("src/main.py")
home = Path.home()           # /home/user or C:\Users\user
cwd = Path.cwd()             # current working directory

# Path components
print(p.name)        # main.py
print(p.stem)        # main
print(p.suffix)      # .py
print(p.parent)      # src
print(p.parts)       # ('src', 'main.py')

# Joining paths (use / operator)
config = home / ".config" / "app" / "config.json"

# Existence and type
print(p.exists())    # True/False
print(p.is_file())
print(p.is_dir())

# Listing directories
for f in Path(".").iterdir():
    print(f)

# Glob patterns
for py_file in Path(".").rglob("*.py"):
    print(py_file)

# Create directories
Path("new/dir").mkdir(parents=True, exist_ok=True)

# Read/write (Path methods)
content = Path("file.txt").read_text()
Path("output.txt").write_text("Hello!")

JSON

json.dumps() сериализует в строку, json.loads() десериализует. dump()/load() работают с файлами. Используйте indent для красивого вывода. Пользовательские объекты требуют default сериализатора. JSON поддерживает только базовые типы — используйте default= для datetime и других сложных объектов.

python
import json

# Python dict to JSON string
data = {"name": "Alice", "age": 30, "skills": ["Python", "SQL"]}
json_str = json.dumps(data, indent=2)
print(json_str)

# JSON string to Python dict
parsed = json.loads('{"name": "Bob", "active": true}')
print(parsed["name"])  # Bob
print(parsed["active"])  # True (Python bool)

# Write JSON to file
with open("data.json", "w") as f:
    json.dump(data, f, indent=2, ensure_ascii=False)

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

# Custom serialization (e.g., datetime)
from datetime import datetime
def json_default(obj):
    if isinstance(obj, datetime):
        return obj.isoformat()
    raise TypeError

json.dumps({"time": datetime.now()}, default=json_default)

# Type mapping:
# JSON object  <-> Python dict
# JSON array   <-> Python list
# JSON string  <-> Python str
# JSON number  <-> Python int/float
# JSON boolean <-> Python bool
# JSON null    <-> Python None

CSV и другие форматы

Модуль csv обрабатывает CSV с правильным экранированием. Используйте newline='' при открытии CSV-файлов на Windows. DictReader/DictWriter работают с именами столбцов. pickle может сериализовать любой объект Python, но является Python-специфичным и небезопасным — никогда не распаковывайте данные из недоверенных источников.

python
import csv

# Write CSV
with open("data.csv", "w", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["name", "age", "city"])
    writer.writerows([
        ["Alice", 30, "NYC"],
        ["Bob", 25, "LA"],
    ])

# Read CSV
with open("data.csv") as f:
    reader = csv.reader(f)
    header = next(reader)  # first row
    for row in reader:
        print(row)  # ['Alice', '30', 'NYC']

# DictReader/DictWriter (column access by name)
with open("data.csv") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row["name"], row["age"])

# Pickle (Python-specific, can store any object)
import pickle
with open("data.pkl", "wb") as f:
    pickle.dump({"complex": [1, 2, {"a": 3}]}, f)

with open("data.pkl", "rb") as f:
    obj = pickle.load(f)

# WARNING: pickle is insecure - never unpickle untrusted data!
11

Модули и пакеты

Импорт модулей

Импорты подключают модули. 'import X' сохраняет пространство имён чистым. 'from X import Y' удобно, но может вызвать коллизии имён. Псевдонимы (import X as Y) распространены для библиотек с соглашениями (np, pd). Избегайте 'from X import *' — это загрязняет пространство имён.

python
# Import entire module
import math
print(math.sqrt(16))

# Import specific names
from datetime import datetime, timedelta
now = datetime.now()

# Import with alias
import numpy as np
import pandas as pd

# Import all names (discouraged - pollutes namespace)
# from os import *

# Conditional import (try/except)
try:
    import cjson as json
except ImportError:
    import json

# Check what's in a module
import os
print(dir(os))         # list all attributes
print(os.__file__)     # module location
print(os.__name__)     # module name

# Reload a module (during development)
import importlib
importlib.reload(my_module)

Создание модулей и пакетов

Модуль — это .py файл; пакет — каталог с __init__.py. Файл __init__.py может быть пустым или настраивать пакет. __all__ в __init__.py контролирует, что экспортирует 'from package import *'. Современный Python (3.3+) поддерживает namespace packages без __init__.py.

python
# A module is just a .py file
# mymath.py
def add(a, b):
    return a + b

PI = 3.14159

# A package is a directory with __init__.py
# mypackage/
#   __init__.py
#   module1.py
#   module2.py
#   subpackage/
#     __init__.py
#     module3.py

# __init__.py can be empty or contain package initialization
# mypackage/__init__.py
from .module1 import ClassA
from .module2 import func_b
__version__ = "1.0.0"
__all__ = ["ClassA", "func_b"]

# Using the package
from mypackage import ClassA
from mypackage.subpackage import module3

# __all__ controls 'from package import *'
# Without __all__, * imports only what's in __init__.py

__name__ == '__main__'

Идиома if __name__ == '__main__' позволяет файлу служить и скриптом, и модулем. При прямом запуске __name__ равно '__main__'; при импорте — имя модуля. Этот паттерн необходим для создания переиспользуемых модулей, которые также могут выполняться автономно.

python
# script.py
def main():
    print("Running main")

def helper():
    print("Helper function")

if __name__ == "__main__":
    # This code only runs when the file is executed directly
    # NOT when imported as a module
    main()

# When you run: python script.py
# __name__ is "__main__" -> main() runs

# When you: import script
# __name__ is "script" -> main() does NOT run
# This lets the module be both a script and an importable library

# Common pattern for CLI tools
def main():
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("--name", required=True)
    args = parser.parse_args()
    print(f"Hello, {args.name}!")

if __name__ == "__main__":
    main()

Основные модули стандартной библиотеки

Стандартная библиотека Python огромна и включена в комплект. os/sys для системного взаимодействия, datetime для дат, collections для специализированных контейнеров, itertools для инструментов итератора, functools для функционального программирования. Изучите документацию на docs.python.org/3/library/.

python
# os - operating system interface
import os
os.getcwd()           # current directory
os.listdir(".")       # list files
os.environ.get("HOME")  # environment variables

# sys - system-specific
import sys
sys.argv              # command-line arguments
sys.exit(0)           # exit with status code
sys.path              # module search path

# datetime - date and time
from datetime import datetime, timedelta
now = datetime.now()

# collections - specialized containers
from collections import Counter, defaultdict, deque

# itertools - iterator tools
from itertools import chain, cycle, repeat, product

# functools - higher-order functions
from functools import lru_cache, reduce, partial

# typing - type hints
from typing import List, Dict, Optional, Union, Any

# pathlib - path handling
from pathlib import Path

# subprocess - run external commands
import subprocess
result = subprocess.run(["ls", "-l"], capture_output=True, text=True)

Pip и виртуальные окружения

Всегда используйте виртуальные окружения для изоляции зависимостей проекта. venv встроен; альтернативы включают virtualenv, conda и uv. Закрепляйте версии в requirements.txt для воспроизводимости. Никогда не устанавливайте пакеты глобально с --user или от root — используйте venv.

python
# Create a virtual environment
# $ python -m venv venv

# Activate it
# Windows: venv\Scripts\activate
# Unix:    source venv/bin/activate

# Install packages
# $ pip install requests
# $ pip install requests==2.28.0
# $ pip install "requests>=2.25,<3.0"

# Install from requirements file
# $ pip install -r requirements.txt

# requirements.txt example:
# requests==2.31.0
# numpy>=1.21.0
# pandas~=2.0.0  # compatible release

# List installed packages
# $ pip list
# $ pip freeze > requirements.txt

# Uninstall
# $ pip uninstall requests

# Show package info
# $ pip show requests

# Modern alternative: uv (faster)
# $ uv pip install requests
12

Дата и время

Модуль datetime

Модуль datetime предоставляет классы date, time, datetime и timedelta. datetime.now() возвращает локальное время; используйте datetime.now(timezone.utc) для UTC. weekday() возвращает 0-6 (Пн-Вс). Всегда используйте timezone-aware datetime в production для избежания неоднозначности.

python
from datetime import datetime, date, time, timedelta

# Current date and time
now = datetime.now()          # local time
utc_now = datetime.utcnow()   # UTC (deprecated in 3.12)
utc = datetime.now(timezone.utc)  # preferred

# Current date
today = date.today()

# Create specific date/time
dt = datetime(2024, 1, 15, 10, 30, 0)
d = date(2024, 1, 15)
t = time(10, 30, 0)

# Access components
print(now.year, now.month, now.day)
print(now.hour, now.minute, now.second)
print(now.weekday())  # 0=Monday, 6=Sunday

# From timestamp
ts = 1705315200
dt = datetime.fromtimestamp(ts)

# To timestamp
print(datetime.now().timestamp())

Форматирование и разбор

strftime (string format time) преобразует datetime в строку; strptime (string parse time) преобразует строку в datetime. ISO 8601 формат (isoformat/fromisoformat) — лучший выбор для хранения дат — он однозначный и сортируемый. Запомните общие коды: %Y %m %d %H %M %S.

python
from datetime import datetime

# Format datetime to string (strftime)
dt = datetime(2024, 1, 15, 10, 30)
print(dt.strftime("%Y-%m-%d"))         # 2024-01-15
print(dt.strftime("%Y/%m/%d %H:%M"))   # 2024/01/15 10:30
print(dt.strftime("%B %d, %Y"))        # January 15, 2024
print(dt.strftime("%A"))               # Monday

# Parse string to datetime (strptime)
dt = datetime.strptime("2024-01-15", "%Y-%m-%d")
dt = datetime.strptime("15/01/2024 10:30", "%d/%m/%Y %H:%M")

# Common format codes:
# %Y  year (2024)     %m  month (01)
# %d  day (15)        %H  hour (14)
# %M  minute (30)     %S  second (00)
# %B  month name      %b  month abbrev
# %A  weekday name    %a  weekday abbrev
# %I  12-hour         %p  AM/PM
# %j  day of year     %U  week number

# ISO format (recommended for storage)
iso = dt.isoformat()  # "2024-01-15T10:30:00"
dt = datetime.fromisoformat("2024-01-15T10:30:00")

timedelta и арифметика

timedelta представляет длительность. Можно добавлять/вычитать timedelta из datetime и вычитать два datetime для получения timedelta. timedelta нормализует: days=1, hours=25 становится days=2, hours=1. total_seconds() возвращает всю длительность в секундах.

python
from datetime import datetime, timedelta

now = datetime.now()

# Add/subtract time
tomorrow = now + timedelta(days=1)
last_week = now - timedelta(weeks=1)
in_2_hours = now + timedelta(hours=2)
in_90_days = now + timedelta(days=90)

# Difference between dates
date1 = datetime(2024, 1, 15)
date2 = datetime(2024, 6, 18)
diff = date2 - date1
print(diff.days)         # 155
print(diff.total_seconds())

# timedelta components
td = timedelta(days=5, hours=3, minutes=30)
print(td.days)           # 5
print(td.seconds)        # 12600 (3h 30m in seconds)
print(td.total_seconds())

# Comparisons
if now > date1:
    print("now is later")

# Business day calculation (using numpy)
# import numpy as np
# business_days = np.busday_count(date1.date(), date2.date())

Часовые пояса

Всегда используйте timezone-aware datetime (Python 3.9+ ZoneInfo предпочтительнее pytz). Храните даты в UTC и преобразуйте в локальное время только для отображения. Naive datetime (без tzinfo) вызывают тонкие ошибки. ZoneInfo использует базу данных часовых поясов IANA, обрабатывая DST автоматически.

python
from datetime import datetime, timezone, timedelta

# Timezone-aware datetime
utc_time = datetime.now(timezone.utc)
print(utc_time)  # 2024-01-15 10:30:00+00:00

# Create a timezone (offset-based)
tz_ny = timezone(timedelta(hours=-5), "EST")
ny_time = datetime.now(tz_ny)

# Convert between timezones
utc_time = datetime.now(timezone.utc)
ny_time = utc_time.astimezone(timezone(timedelta(hours=-5)))
tokyo_time = utc_time.astimezone(timezone(timedelta(hours=9)))

# Use zoneinfo (Python 3.9+) for IANA timezones
from zoneinfo import ZoneInfo
tz = ZoneInfo("America/New_York")
dt = datetime.now(tz)
print(dt.tzname())  # EST or EDT

# Common IANA timezones:
# "UTC"
# "America/New_York", "America/Los_Angeles"
# "Europe/London", "Europe/Paris"
# "Asia/Tokyo", "Asia/Shanghai"
# "Australia/Sydney"

# Best practice: store UTC, convert for display
utc_stored = datetime.now(timezone.utc)
local_display = utc_stored.astimezone(ZoneInfo("Asia/Shanghai"))
13

Регулярные выражения

Основы модуля re

re.search() находит первое совпадение где угодно; re.match() только в начале; re.fullmatch() требует совпадения всей строки. Используйте сырые строки (r'...') для шаблонов во избежание проблем с экранированием обратной косой черты. Объекты совпадения предоставляют group(), start(), end() и span().

python
import re

# re.search - find first match anywhere in string
m = re.search(r"\d{4}", "Order #2024 was placed")
if m:
    print(m.group())  # 2024
    print(m.start(), m.end())  # 9 13

# re.match - match at beginning of string
m = re.match(r"Hello", "Hello, World")
print(m.group())  # Hello

# re.fullmatch - entire string must match
m = re.fullmatch(r"\d+", "12345")
print(bool(m))  # True

# re.findall - all matches as list
emails = re.findall(r"\S+@\S+", text)
numbers = re.findall(r"\d+", "a1b22c333")  # ['1', '22', '333']

# re.finditer - all matches as iterator (with positions)
for m in re.finditer(r"\w+", "Hello World"):
    print(m.group(), m.span())

# Match object methods
m = re.search(r"(\w+)@(\w+)", "[email protected]")
print(m.group())   # user@domain (whole match)
print(m.group(1))  # user (first group)
print(m.group(2))  # domain (second group)
print(m.groups())  # ('user', 'domain')

Синтаксис шаблонов

Синтаксис regex: [] для классов символов, \d \w \s для общих наборов, кванторы (* + ? {}) для повторения, ^ $ \b для якорей, () для групп, | для чередования. Используйте сырые строки (r'...'), чтобы обратные косые черты были буквальными. Жадные кванторы совпадают максимально; добавьте ? для ленивых (например, *?).

python
import re

# Character classes
re.findall(r"[aeiou]", "hello")      # vowels
re.findall(r"[^aeiou]", "hello")     # non-vowels
re.findall(r"[a-z]", "Hello123")     # lowercase
re.findall(r"[A-Za-z0-9]", "Hi-1!")  # alphanumeric

# Predefined classes
# .   any char except newline
# \d  digit [0-9]      \D  non-digit
# \w  word char [a-zA-Z0-9_]  \W  non-word
# \s  whitespace       \S  non-whitespace

# Quantifiers
# *     0 or more
# +     1 or more
# ?     0 or 1
# {n}   exactly n
# {n,}  n or more
# {n,m} between n and m
re.findall(r"\d{3}", "1234567")      # ['123', '456']
re.findall(r"\d{2,4}", "12345678")   # ['1234', '5678']

# Anchors
# ^  start of string    $  end of string
# \b word boundary
re.findall(r"^\w+", "Hello World")   # ['Hello']
re.findall(r"\b\w+\b", "hi there") # ['hi', 'there']

# Groups & alternation
re.findall(r"(cat|dog)", "cat and dog")  # ['cat', 'dog']
re.findall(r"(\w+)@(\w+\.\w+)", "[email protected]")

Подстановка и разделение

re.sub() заменяет совпадения — используйте обратные ссылки (\1, \2) для групп или функцию для динамической замены. re.split() мощнее str.split() — принимает regex-шаблоны. Захватывающие группы в шаблоне включаются в результат. Используйте re.subn() для получения (результат, количество).

python
import re

# re.sub - replace matches
result = re.sub(r"\d+", "#", "a1b22c333")
# 'a#b#c#'

# Replace with count limit
result = re.sub(r"\d", "X", "a1b2c3", count=2)
# 'aXbXc3'

# Use backreferences in replacement
result = re.sub(r"(\w+)@(\w+)", r"\2.\1", "user@domain")
# 'domain.user'

# Use function as replacement
def upper(m):
    return m.group().upper()

result = re.sub(r"\b[a-z]", upper, "hello world")
# 'Hello World' (capitalize first letter of each word)

# re.split - split by pattern
parts = re.split(r"[,;\s]+", "a, b; c d")
# ['a', 'b', 'c', 'd']

# Split with capture groups (keeps delimiters)
parts = re.split(r"([,;])", "a,b;c")
# ['a', ',', 'b', ';', 'c']

# Split with maxsplit
parts = re.split(r",", "a,b,c,d", maxsplit=2)
# ['a', 'b', 'c,d']

Компиляция и флаги

Компилируйте шаблоны с re.compile() при многократном использовании — это быстрее. Флаги модифицируют поведение: IGNORECASE, MULTILINE, DOTALL, VERBOSE (разрешает комментарии/пробелы в шаблонах). Именованные группы (?P<name>...) улучшают читаемость. Lookahead (?=) и lookbehind (?<=) совпадают без поглощения.

python
import re

# Compile pattern for reuse (faster when used many times)
email_re = re.compile(r"^[\w.+-]+@([\w-]+\.)+[\w-]+$")
print(email_re.match("[email protected]"))  # match object
print(email_re.match("invalid"))           # None

# Common flags
re.IGNORECASE  # case-insensitive
re.MULTILINE   # ^ and $ match line boundaries
re.DOTALL      # . matches newline too
re.VERBOSE     # allow whitespace & comments in pattern

# Combine flags with |
pattern = re.compile(r"""
    ^           # start of line
    (\w+)      # capture word
    \s+        # whitespace
    (\d+)      # capture number
""", re.VERBOSE | re.MULTILINE)

# Named groups (more readable)
m = re.match(r"(?P<year>\d{4})-(?P<month>\d{2})", "2024-01")
print(m.group("year"))   # 2024
print(m.group("month"))  # 01
print(m.groupdict())     # {'year': '2024', 'month': '01'}

# Lookahead/lookbehind
re.findall(r"\d+(?= dollars)", "100 dollars, 200 euros")
# ['100']  (positive lookahead)
re.findall(r"(?<=\$)\d+", "$100 and $200")
# ['100', '200']  (positive lookbehind)
14

Асинхронность и конкурентность

Основы asyncio

asyncio — асинхронный I/O фреймворк Python. 'async def' определяет корутину; 'await' приостанавливает до готовности результата. asyncio.run() запускает цикл событий. asyncio.gather() выполняет корутины конкурентно. Корутины обеспечивают высококонкурентный I/O без потоков.

python
import asyncio

# Define a coroutine
async def greet(name, delay):
    await asyncio.sleep(delay)  # non-blocking sleep
    return f"Hello, {name}!"

# Run a coroutine
async def main():
    result = await greet("Alice", 1)
    print(result)

# Run the event loop
asyncio.run(main())

# Concurrent execution with gather
async def main():
    # Run coroutines concurrently
    results = await asyncio.gather(
        greet("Alice", 2),
        greet("Bob", 1),
        greet("Carol", 3)
    )
    print(results)  # all complete after 3 seconds (max delay)

asyncio.run(main())

# asyncio.create_task - schedule without awaiting immediately
async def main():
    task = asyncio.create_task(greet("Alice", 1))
    # do other work here
    result = await task  # await when needed
    print(result)

Асинхронный HTTP и таймауты

asyncio.timeout() (Python 3.11+) отменяет операции, занимающие слишком много времени. Для HTTP используйте aiohttp (асинхронный) вместо requests (синхронный). asyncio.Queue включает паттерны producer-consumer. Async идеален для I/O-bound работы (сеть, диск) — не для CPU-bound работы.

python
import asyncio

# Async timeout
async def fetch_with_timeout(url, timeout=5):
    try:
        async with asyncio.timeout(timeout):
            # simulate async operation
            await asyncio.sleep(2)
            return f"Data from {url}"
    except asyncio.TimeoutError:
        return "Request timed out"

# Using aiohttp (third-party) for HTTP
# import aiohttp
# 
# async def fetch(url):
#     async with aiohttp.ClientSession() as session:
#         async with session.get(url) as response:
#             return await response.text()

# Producer-consumer pattern
async def producer(queue):
    for i in range(5):
        await asyncio.sleep(0.1)
        await queue.put(f"item-{i}")
    await queue.put(None)  # sentinel

async def consumer(queue):
    while True:
        item = await queue.get()
        if item is None:
            break
        print(f"Processed: {item}")
        queue.task_done()

async def main():
    queue = asyncio.Queue()
    await asyncio.gather(producer(queue), consumer(queue))

asyncio.run(main())

Потоки (Threading)

Threading для I/O-bound конкурентности (сеть, файловый I/O). GIL Python предотвращает истинное параллельное CPU-выполнение в потоках. Используйте Lock для защиты общего состояния от гонок. Для CPU-bound работы используйте multiprocessing. Потоки разделяют память; процессы — нет.

python
import threading
import time

# Basic threading
def worker(name, delay):
    print(f"Worker {name} starting")
    time.sleep(delay)
    print(f"Worker {name} done")

# Create and start threads
t1 = threading.Thread(target=worker, args=("A", 2))
t2 = threading.Thread(target=worker, args=("B", 1))
t1.start()
t2.start()

# Wait for threads to complete
t1.join()
t2.join()
print("All done")

# Thread with Lock (for shared state)
counter = 0
lock = threading.Lock()

def increment():
    global counter
    for _ in range(100000):
        with lock:  # acquire/release lock
            counter += 1

threads = [threading.Thread(target=increment) for _ in range(5)]
for t in threads: t.start()
for t in threads: t.join()
print(f"Counter: {counter}")  # 500000 (correct with lock)

Многопроцессорность

Multiprocessing обходит GIL для истинного CPU-параллелизма — каждый процесс имеет собственный интерпретатор Python. Используйте Pool для параллельных map-операций. concurrent.futures предоставляет унифицированный API для потоков и процессов. Всегда защищайте с if __name__ == '__main__' на Windows.

python
from multiprocessing import Process, Pool, Queue
import os

# Basic process
def worker(name):
    print(f"Process {name} PID: {os.getpid()}")

if __name__ == "__main__":
    p = Process(target=worker, args=("A",))
    p.start()
    p.join()

# Process pool for parallel work
def square(x):
    return x * x

if __name__ == "__main__":
    with Pool(4) as pool:  # 4 worker processes
        results = pool.map(square, range(10))
        print(results)  # [0, 1, 4, 9, ..., 81]
    
    # Asynchronous map
    with Pool(4) as pool:
        result = pool.map_async(square, range(10))
        print(result.get())  # blocks until done

# concurrent.futures (higher-level API)
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor

with ProcessPoolExecutor() as executor:
    results = list(executor.map(square, range(10)))

with ThreadPoolExecutor() as executor:
    futures = [executor.submit(square, i) for i in range(10)]
    results = [f.result() for f in futures]
15

Декораторы

Базовый декоратор

Декораторы обёртывают функцию для расширения или модификации поведения без изменения исходного кода. Синтаксис @ — синтаксический сахар для присваивания результата вызова декоратора обратно имени функции. Используйте *args, **kwargs в обёртке для работы с любой сигнатурой.

python
# A decorator is a function that takes a function and returns a new function
def uppercase_result(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper()
    return wrapper

@uppercase_result
def greet(name):
    return f"hello, {name}"

print(greet("world"))  # HELLO, WORLD
# @uppercase_result is sugar for: greet = uppercase_result(greet)

functools.wraps (сохранение метаданных)

Без @wraps обёрнутая функция теряет исходные __name__, __doc__ и сигнатуру — инструменты отладки и help() показывают 'wrapper'. Всегда используйте @functools.wraps(func) внутри декораторов для сохранения метаданных. Это почти универсальная лучшая практика.

python
from functools import wraps

def log_calls(func):
    @wraps(func)  # copies __name__, __doc__, __module__
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}({args}, {kwargs})")
        return func(*args, **kwargs)
    return wrapper

@log_calls
def add(a, b):
    """Add two numbers."""
    return a + b

print(add.__name__)  # 'add' (not 'wrapper')
print(add.__doc__)   # 'Add two numbers.'
help(add)            # shows original docstring

Декоратор с аргументами

Когда декоратор принимает аргументы, нужны три уровня вложенности: фабрика (принимает args), декоратор (принимает функцию) и обёртка (принимает аргументы вызова). @repeat(3) сначала вызывает repeat(3), который возвращает декоратор, который затем применяется к функции.

python
# A decorator factory: returns the actual decorator
def repeat(times):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            result = None
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def say_hi(name):
    print(f"Hi, {name}!")

say_hi("Alice")
# Hi, Alice! (printed 3 times)

Классовый декоратор

Классовые декораторы используют __init__ для хранения функции и __call__ для перехвата вызовов. Они идеальны, когда декоратору нужно поддерживать состояние (например, счётчик вызовов или кэш). Экземпляр класса заменяет функцию, поэтому вызов запускает __call__.

python
class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0
        wraps(func)(self)  # preserve metadata

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"{self.func.__name__} called {self.count} times")
        return self.func(*args, **kwargs)

@CountCalls
def say_hello():
    print("Hello!")

say_hello()  # count=1
say_hello()  # count=2
say_hello()  # count=3
print(say_hello.count)  # 3

Встроенные декораторы (@property, @staticmethod, @classmethod)

@property превращает метод в вычисляемый атрибут (доступ без скобок). @classmethod получает класс как первый аргумент — идеально для альтернативных конструкторов. @staticmethod не получает неявный первый аргумент — просто функция в пространстве имён класса. Вместе они формируют основу Pythonic ООП.

python
class Circle:
    pi = 3.14159

    def __init__(self, radius):
        self._radius = radius

    @property
    def area(self):
        return Circle.pi * self._radius ** 2

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError("Radius cannot be negative")
        self._radius = value

    @classmethod
    def from_diameter(cls, diameter):
        return cls(diameter / 2)

    @staticmethod
    def is_valid_radius(r):
        return r >= 0

c = Circle(5)
print(c.area)              # 78.54 (no parentheses!)
c.radius = 10             # uses the setter
c2 = Circle.from_diameter(20)  # alternative constructor

Стек декораторов

При стэкинге декораторов они применяются снизу вверх (ближайший к функции выполняется первым), но выполняются сверху вниз при вызове. Так @bold обёртывает @italic, который обёртывает greet. Результат вложен как слои луковицы. Порядок важен — обращение меняет вложенность вывода.

python
from functools import wraps

def bold(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return f"<b>{func(*args, **kwargs)}</b>"
    return wrapper

def italic(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return f"<i>{func(*args, **kwargs)}</i>"
    return wrapper

@bold
@italic
def greet(name):
    return f"Hello, {name}"

print(greet("World"))
# <b><i>Hello, World</i></b>
# Applied bottom-up: italic first, then bold
16

Генераторы и итераторы

Функции-генераторы (yield)

Генераторы лениво производят значения с помощью yield — они приостанавливают выполнение после каждого yield и возобновляются при вызове next(). Это делает их эффективными по памяти для больших или бесконечных последовательностей, поскольку только одно значение существует в памяти за раз. После исчерпания генератор нельзя использовать повторно.

python
# A generator uses 'yield' to produce values lazily, one at a time
def count_up_to(n):
    count = 1
    while count <= n:
        yield count
        count += 1

gen = count_up_to(5)
print(next(gen))  # 1
print(next(gen))  # 2
print(list(gen))  # [3, 4, 5] (exhausts the rest)

# Generators are memory-efficient: they don't build the whole list
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib = fibonacci()
print([next(fib) for _ in range(10)])
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Генераторные выражения

Генераторные выражения — ленивый эквивалент генераторных списков — используйте круглые скобки вместо квадратных. Они используют постоянную память независимо от размера, что делает их идеальными для sum(), max(), any() или подачи в другие итераторы. Предпочитайте их генераторным спискам, когда не нужен случайный доступ.

python
# Like list comprehensions, but lazy (uses parentheses)
squares_list = [x ** 2 for x in range(10)]      # builds full list
squares_gen  = (x ** 2 for x in range(10))      # lazy generator

print(squares_gen)        # <generator object>
print(next(squares_gen))  # 0
print(next(squares_gen))  # 1

# Memory comparison
import sys
print(sys.getsizeof([x for x in range(10000)]))  # ~87616 bytes
print(sys.getsizeof((x for x in range(10000))))  # ~200 bytes (constant!)

# Use in sum(), list(), any() etc.
total = sum(x ** 2 for x in range(100))  # no extra list created
print(total)  # 328350

Протокол итератора (__iter__, __next__)

Протокол итератора требует __iter__ (возвращает итератор) и __next__ (возвращает следующее значение или возбуждает StopIteration). Итерируемые объекты можно обходить в цикле; итераторы производят значения по одному. Для переиспользуемых итерируемых объектов разделяйте итерируемый (возвращает новый итератор) и итератор (хранит состояние).

python
class Range2:
    """A custom iterator that yields even numbers."""
    def __init__(self, start, end):
        self.current = start
        self.end = end

    def __iter__(self):
        return self  # the object is its own iterator

    def __next__(self):
        if self.current >= self.end:
            raise StopIteration
        value = self.current
        self.current += 2
        return value

r = Range2(0, 10)
for num in r:
    print(num)  # 0, 2, 4, 6, 8

# An iterable returns a fresh iterator each time __iter__ is called.
# An iterator returns itself and raises StopIteration when exhausted.

send(), throw(), close()

Продвинутые методы генератора обеспечивают двустороннюю связь: send() передаёт значение в генератор (становится результатом yield), throw() инъектирует исключение в точке yield, а close() завершает генератор. Необходимо 'заправить' генератор через next() перед отправкой. Они лежат в основе корутин и async-фреймворков.

python
def echo():
    while True:
        received = yield  # yield without a value, receives via send()
        print(f"Echo: {received}")

gen = echo()
next(gen)          # prime the generator (advance to first yield)
gen.send("hello")  # Echo: hello
gen.send("world")  # Echo: world

# throw() injects an exception at the yield point
def safe_gen():
    try:
        while True:
            yield
    except ValueError:
        print("Caught ValueError inside generator")

g = safe_gen()
next(g)
g.throw(ValueError, "boom")  # Caught ValueError inside generator

# close() stops the generator (raises GeneratorExit)
gen.close()

Конвейеры генераторов

Конвейеры генераторов связывают ленивых производителей так, что данные проходят через стадии по одному элементу за раз — каждый элемент полностью обрабатывается до чтения следующего. Это избегает создания промежуточных списков и является основой потоковой обработки данных. Unix pipes работают концептуально так же.

python
# Chain generators to build data-processing pipelines
def numbers():
    for i in range(1, 11):
        yield i

def squared(seq):
    for n in seq:
        yield n ** 2

def evens(seq):
    for n in seq:
        if n % 2 == 0:
            yield n

# Each stage processes one item at a time — no intermediate lists
pipeline = evens(squared(numbers()))
print(list(pipeline))  # [4, 16, 36, 64, 100]

# Equivalent with generator expressions:
result = (n for n in (x ** 2 for x in range(1, 11)) if n % 2 == 0)
print(list(result))  # [4, 16, 36, 64, 100]

yield from (делегирование)

yield from делегирует все yield (и send/throw/close) суб-итератору, выравнивая вложенные структуры и составляя корутины. Особенно мощно для рекурсивных генераторов — классический пример — выравнивание произвольно вложенных списков. В async-коде 'await' построен на той же концепции.

python
# yield from delegates to a sub-iterator (Python 3.3+)
def flatten(nested):
    for item in nested:
        if isinstance(item, (list, tuple)):
            yield from flatten(item)  # recursive delegation
        else:
            yield item

data = [1, [2, 3, [4, 5]], 6, [7, [8, [9]]]]
print(list(flatten(data)))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

# yield from also forwards send()/throw() to the sub-generator,
# making it essential for coroutine composition.
17

Менеджеры контекста

Основы оператора with

Оператор with гарантирует освобождение ресурсов (закрытие файлов, снятие блокировок, возврат подключений) даже при исключениях. Он вызывает __enter__ в начале и __exit__ в конце. Всегда предпочитайте 'with' ручному try/finally для управления ресурсами — это безопаснее и читабельнее.

python
# 'with' guarantees cleanup even if an exception occurs
with open("data.txt", "r") as f:
    content = f.read()
# f.close() is called automatically here, even if read() raised

# Without 'with' you must manually close:
f = open("data.txt", "r")
try:
    content = f.read()
finally:
    f.close()  # easy to forget!

# Common built-in context managers:
with open("out.txt", "w") as f, open("in.txt") as g:
    f.write(g.read())  # both files close automatically

Пользовательский менеджер контекста (класс)

Классовый менеджер контекста реализует __enter__ (настройка, возвращает объект контекста) и __exit__(exc_type, exc_val, exc_tb) (очистка). Аргументы __exit__ получают информацию об исключении, если оно произошло; возврат True подавляет его. Этот паттерн идеален для сложной настройки/очистки, например транзакций БД.

python
class Timer:
    def __init__(self, label="Timer"):
        self.label = label

    def __enter__(self):
        import time
        self.start = time.perf_counter()
        return self  # value bound to 'as' variable

    def __exit__(self, exc_type, exc_val, exc_tb):
        import time
        elapsed = time.perf_counter() - self.start
        print(f"{self.label}: {elapsed:.4f}s")
        # Return False (or None) to propagate exceptions
        # Return True to suppress the exception
        return False

with Timer("Processing"):
    total = sum(i ** 2 for i in range(1_000_000))
# Processing: 0.1234s

contextlib.contextmanager

contextlib.contextmanager превращает функцию-генератор в менеджер контекста — код до yield это __enter__, код после yield (в finally) это __exit__. Это лаконичнее класса для простых случаев. Yield значение для передачи в переменную 'as'. Используйте try/finally для гарантии очистки.

python
from contextlib import contextmanager
import time

@contextmanager
def timer(label="Timer"):
    start = time.perf_counter()
    try:
        yield  # code inside the 'with' block runs here
    finally:
        elapsed = time.perf_counter() - start
        print(f"{label}: {elapsed:.4f}s")

with timer("My task"):
    sum(i ** 2 for i in range(1_000_000))
# My task: 0.1234s

# You can also yield a value to bind with 'as'
@contextmanager
def open_db(path):
    db = connect(path)
    try:
        yield db
    finally:
        db.close()

with open_db("app.db") as db:
    db.query("SELECT 1")

Несколько менеджеров контекста

Python 3.10+ разрешает многострочные операторы 'with' в скобках для чистого синтаксиса. Для динамического числа менеджеров контекста contextlib.ExitStack управляет ими как группой и разворачивает все в обратном порядке. ExitStack необходим, когда количество ресурсов неизвестно до выполнения.

python
# Python 3.10+ supports parenthesized context managers
with (
    open("input.txt") as fin,
    open("output.txt", "w") as fout,
):
    fout.write(fin.read())

# Pre-3.10: nest them or use contextlib.ExitStack
from contextlib import ExitStack

files = ["a.txt", "b.txt", "c.txt"]
with ExitStack() as stack:
    handles = [stack.enter_context(open(f)) for f in files]
    for h in handles:
        print(h.read())

Утилиты contextlib (suppress, redirect)

contextlib.suppress заменяет try/except/pass для ожидаемых исключений — гораздо читабельнее. redirect_stdout/redirect_stderr перехватывают вывод, который иначе шёл бы в консоль, полезно для тестирования или логирования. Эти утилиты избегают шаблонности и делают намерение явным.

python
from contextlib import suppress, redirect_stdout, redirect_stderr
import io, warnings

# suppress: ignore specific exceptions (cleaner than try/except/pass)
with suppress(FileNotFoundError):
    os.remove("temp.txt")  # no error if file doesn't exist

# redirect_stdout: capture print output
buffer = io.StringIO()
with redirect_stdout(buffer):
    print("This goes to the buffer, not console")
captured = buffer.getvalue()

# redirect_stderr: capture error/warning output
err_buf = io.StringIO()
with redirect_stderr(err_buf):
    warnings.warn("a warning")
print(err_buf.getvalue())  # the warning text

# Also: contextlib.chdir (3.11+) to temporarily change directory
# from contextlib import chdir
# with chdir("/tmp"): ...

Асинхронные менеджеры контекста

Асинхронные менеджеры контекста используют __aenter__/__aexit__ (обратите внимание на префикс 'a') и оператор 'async with'. Они необходимы для управления асинхронными ресурсами, такими как подключения к БД или HTTP-сессии (например, aiohttp.ClientSession). Очистка выполняется даже при await или исключении внутри блока.

python
import asyncio

class AsyncDB:
    async def __aenter__(self):
        print("connecting...")
        await asyncio.sleep(0.1)  # simulate async connect
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        print("closing...")
        await asyncio.sleep(0.1)  # simulate async close
        return False

    async def query(self, sql):
        return f"result of: {sql}"

async def main():
    async with AsyncDB() as db:
        result = await db.query("SELECT 1")
        print(result)

asyncio.run(main())
# connecting...
# result of: SELECT 1
# closing...
18

Аннотации типов

Базовые аннотации переменных и функций

Аннотации типов документируют ожидаемые типы, но НЕ принуждаются во время выполнения — Python остаётся динамически типизированным. Используйте статический чекер вроде mypy или pyright для поиска ошибок типов до выполнения. Встроенные дженерики (list[str], dict[str, int]) требуют Python 3.9+; старые версии нуждаются в typing.List, typing.Dict.

python
# Variable annotations (Python 3.6+)
name: str = "Alice"
age: int = 30
scores: list[float] = [95.5, 88.0, 92.3]
config: dict[str, int] = {"timeout": 30}

# Function annotations
def greet(name: str, excited: bool = False) -> str:
    punctuation = "!" if excited else "."
    return f"Hello, {name}{punctuation}"

print(greet("World", excited=True))  # Hello, World!

# Annotations are optional and NOT enforced at runtime
def add(a: int, b: int) -> int:
    return a + b
add("2", "3")  # runs fine, returns "23" (no error!)

Модуль typing (List, Dict, Tuple, Optional)

Модуль typing предоставляет generic-псевдонимы для старого Python. С 3.9 можно использовать встроенные типы напрямую (list[str] вместо List[str]). Optional[X] — сокращение для Union[X, None] — используйте для сигнализации, что функция может вернуть None, заставляя вызывающих обрабатывать случай None.

python
from typing import List, Dict, Tuple, Set, FrozenSet

# Pre-3.9 style (still works, needed for older Python)
names: List[str] = ["Alice", "Bob"]
scores: Dict[str, int] = {"Alice": 95}
point: Tuple[int, int] = (10, 20)
mixed: Tuple[str, int, float] = ("a", 1, 2.0)
variadic: Tuple[int, ...] = (1, 2, 3)  # variable-length

# Python 3.9+ built-in generics (preferred)
names: list[str] = ["Alice", "Bob"]
scores: dict[str, int] = {"Alice": 95}
point: tuple[int, int] = (10, 20)

# Optional means "could be None"
from typing import Optional
def find_user(uid: int) -> Optional[str]:
    if uid == 1:
        return "Alice"
    return None  # could also just 'return'

Типы Union и Literal

Union[X, Y] (или X | Y в 3.10+) означает, что значение может быть любого из типов. Literal ограничивает значение конкретными константами — отлично для строковых enum без накладных расходов enum и для диспетчеризации перегруженных функций. mypy использует Literal для сужения типов и поиска недопустимых аргументов при проверке.

python
from typing import Union, Literal, overload

# Union: value can be one of several types
def process(data: Union[str, bytes]) -> str:
    if isinstance(data, bytes):
        return data.decode("utf-8")
    return data

# Python 3.10+ union syntax with | (preferred)
def process2(data: str | bytes) -> str:
    if isinstance(data, bytes):
        return data.decode("utf-8")
    return data

# Literal: restrict to specific constant values
def set_mode(mode: Literal["r", "w", "a"]) -> None:
    print(f"Mode set to {mode}")

set_mode("r")   # OK
# set_mode("x")  # mypy error: not a valid literal

# Literal for boolean-like flags
Direction = Literal["up", "down", "left", "right"]

TypeVar и дженерики

TypeVar создаёт переменные generic-типа, чтобы функции и классы могли сохранять типовые отношения (например, 'возвращает тот же тип, что и вход'). Используйте bound= для ограничения подтипом или укажите constraints вроде TypeVar('T', int, float). Generic-классы используют Generic[T] как базу для параметризованных контейнеров.

python
from typing import TypeVar, Generic, List

T = TypeVar("T")  # a generic type variable

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

# Type inference: T is bound to the argument's type
x: int = first([1, 2, 3])        # T = int
y: str = first(["a", "b", "c"])  # T = str

# Bounded TypeVar: T must be a subtype of Number
from typing import TypeVar
from numbers import Number
N = TypeVar("N", bound=Number)

def sum_all(values: list[N]) -> N:
    total = values[0]
    for v in values[1:]:
        total = total + v
    return total

# Generic class
class Stack(Generic[T]):
    def __init__(self) -> None:
        self._items: list[T] = []
    def push(self, item: T) -> None:
        self._items.append(item)
    def pop(self) -> T:
        return self._items.pop()

s: Stack[int] = Stack()
s.push(1)
# s.push("x")  # mypy error

Callable, псевдонимы типов и Protocol

Callable[[int, str], bool] описывает функцию, принимающую int и str, возвращающую bool. Псевдонимы типов дают описательные имена сложным типам. Protocol обеспечивает структурную (duck) типизацию — любой объект с нужными методами удовлетворяет протоколу, без наследования. Это ответ Python на интерфейсы.

python
from typing import Callable, Protocol, TypeAlias

# Callable signature: Callable[[ArgTypes], ReturnType]
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
    return func(a, b)

apply(lambda x, y: x + y, 3, 4)  # 7

# Type aliases (3.12+ uses 'type' statement; older uses assignment)
type Vector = list[float]          # 3.12+
Vector2: TypeAlias = list[float]   # 3.10+

def magnitude(v: Vector) -> float:
    return sum(x ** 2 for x in v) ** 0.5

# Protocol: structural typing (duck typing with static checks)
class Drawable(Protocol):
    def draw(self) -> None: ...

def render(obj: Drawable) -> None:
    obj.draw()  # any object with a draw() method works

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

render(Circle())  # OK — Circle has draw()

Проверка типов с mypy

mypy — самый популярный статический чекер типов для Python — он анализирует аннотации типов без выполнения кода. Ловит баги обработки None, неверные типы аргументов и отсутствующие возвраты. Начните с постепенной типизации: добавляйте аннотации в новый код и запускайте mypy в CI. Используйте --strict для новых проектов для всеобъемлющих аннотаций.

python
# Save as example.py, then run:  mypy example.py
from typing import Optional

def divide(a: float, b: float) -> Optional[float]:
    if b == 0:
        return None
    return a / b

result = divide(10, 0)
# Without checking, this would crash at runtime:
# print(result + 1)  # TypeError: NoneType + int

# mypy catches it:
# error: Unsupported operand types for + ("None" and "int")
# fix: check for None first
if result is not None:
    print(result + 1)

# Run strict mode for maximum safety:
#   mypy --strict example.py
# Common strict flags: --disallow-untyped-defs, --no-implicit-optional
19

Классы данных

Базовый @dataclass

@dataclass автоматически генерирует __init__, __repr__ и __eq__ на основе аннотированных полей — устраняет шаблонность для классов, хранящих данные. Идеально для value-объектов, конфигов, DTO и записей. Доступно с Python 3.7. Поля должны иметь аннотации типов; аннотация определяет поле.

python
from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

p1 = Point(1.0, 2.0)
p2 = Point(1.0, 2.0)

print(p1)         # Point(x=1.0, y=2.0)  — auto __repr__
print(p1 == p2)   # True  — auto __eq__ (compares fields)
print(p1.x)       # 1.0

# Without @dataclass you'd write all this boilerplate:
# class Point:
#     def __init__(self, x, y): self.x = x; self.y = y
#     def __repr__(self): ...
#     def __eq__(self, other): ...

Значения по умолчанию и default_factory

Изменяемые значения по умолчанию (списки, словари, множества) должны использовать field(default_factory=list) — использование [] напрямую разделило бы один список между всеми экземплярами, классическая ошибка. default_factory вызывается один раз для каждого экземпляра для создания свежего объекта. Простые неизменяемые значения по умолчанию (int, str, bool, None) можно назначать напрямую.

python
from dataclasses import dataclass, field

@dataclass
class Student:
    name: str
    grade: str = "A"          # simple default
    tags: list[str] = field(default_factory=list)  # mutable default!
    scores: dict[str, int] = field(default_factory=dict)

s = Student("Alice")
print(s)  # Student(name='Alice', grade='A', tags=[], scores={})

s.tags.append("honors")
s2 = Student("Bob")
print(s2.tags)  # [] — each instance gets its own list

# NEVER use [] or {} as a direct default — all instances would share
# the same mutable object (classic Python pitfall).

frozen, eq и order

frozen=True делает dataclass неизменяемым — поля нельзя переназначить, а экземпляр становится хешируемым (можно использовать как ключ словаря или элемент множества). order=True добавляет методы сравнения для сортировки. Комбинируйте frozen=True с order=True для неизменяемых, сортируемых value-типов вроде координат, цветов или версий.

python
from dataclasses import dataclass

# frozen=True makes instances immutable (hashable, usable as dict keys)
@dataclass(frozen=True)
class Color:
    r: int
    g: int
    b: int

c = Color(255, 0, 0)
# c.r = 128  # FrozenInstanceError!
print(hash(c))  # works — frozen dataclasses are hashable

# order=True generates __lt__, __le__, __gt__, __ge__ for sorting
@dataclass(order=True)
class Priority:
    level: int

tasks = [Priority(3), Priority(1), Priority(2)]
tasks.sort()
print(tasks)  # [Priority(level=1), Priority(level=2), Priority(level=3)]

# Common combo: frozen + order for immutable comparable values
@dataclass(frozen=True, order=True)
class Version:
    major: int
    minor: int

__post_init__ и кастомизация полей

__post_init__ запускается автоматически после сгенерированного __init__ — используйте для вычисления производных полей, валидации значений или настройки. field(init=False) создаёт поле не в конструкторе (хорошо для вычисляемых/кэшированных значений). field(repr=False, compare=False) скрывает поля из repr и проверок равенства.

python
from dataclasses import dataclass, field

@dataclass
class User:
    email: str
    _email_normalized: str = field(init=False, repr=False)
    id: int = field(default=0)

    def __post_init__(self):
        # runs after __init__; compute derived fields here
        self._email_normalized = self.email.strip().lower()

u = User("  [email protected] ")
print(u.email)             # '  [email protected] '
print(u._email_normalized) # '[email protected]'

# field(init=False) excludes a field from __init__
# field(repr=False) hides it from the repr
# field(compare=False) excludes from __eq__/__hash__
# field(metadata={...}) attaches custom metadata

Наследование и slots

Dataclasses поддерживают наследование — поля дочернего класса добавляются после полей родителя, можно переопределять родительские значения по умолчанию. Внимание: поле со значением по умолчанию в родителе не может сопровождаться полем без значения по умолчанию в дочернем. slots=True (3.10+) предотвращает добавление произвольных атрибутов и значительно снижает память на экземпляр — идеально для миллионов маленьких объектов.

python
from dataclasses import dataclass

@dataclass
class Animal:
    name: str
    sound: str = "..."

@dataclass
class Dog(Animal):
    breed: str = "unknown"
    sound: str = "Woof"  # override parent default

d = Dog("Rex", breed="Labrador")
print(d)  # Dog(name='Rex', sound='Woof', breed='Labrador')

# Python 3.10+: slots=True saves memory (no __dict__)
@dataclass(slots=True)
class Pixel:
    r: int
    g: int
    b: int

p = Pixel(0, 128, 255)
# p.new_field = 1  # AttributeError — slots prevent arbitrary attrs
# Saves ~40-50% memory vs regular dataclass for many instances
20

Collections и itertools

namedtuple

namedtuple создаёт подклассы кортежа с именованными полями — такие же эффективные по памяти, как кортежи, но гораздо читабельнее. Они неизменяемы, используйте _replace() для создания изменённых копий. Предпочитайте typing.NamedTuple для нового кода, так как поддерживает аннотации типов и значения по умолчанию. Отлично для возврата нескольких значений из функций.

python
from collections import namedtuple

# Lightweight immutable class with named fields
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y)       # 3 4  — access by name
print(p[0], p[1])     # 3 4  — also by index
print(p._asdict())    # {'x': 3, 'y': 4}

# More memory-efficient than a full class
# Use _replace to create a modified copy (immutable!)
p2 = p._replace(x=10)
print(p2)  # Point(x=10, y=4)

# Python 3.6+ typing.NamedTuple for type hints:
from typing import NamedTuple
class Point3D(NamedTuple):
    x: float
    y: float
    z: float = 0.0

Counter

Counter — подкласс dict для подсчёта хешируемых объектов — идеально для частотного анализа, гистограмм и голосования. most_common(n) возвращает топ-n элементов. Отсутствующие ключи возвращают 0 вместо KeyError. Counter поддерживает +, -, &, | для set-подобной арифметики счётчиков.

python
from collections import Counter

# Count hashable items
words = "the cat sat on the mat the cat".split()
c = Counter(words)
print(c)  # Counter({'the': 3, 'cat': 2, 'sat': 1, 'on': 1, 'mat': 1})

# Most common elements
print(c.most_common(2))  # [('the', 3), ('cat', 2)]

# Arithmetic on counters
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
print(c1 + c2)  # Counter({'a': 4, 'b': 3})
print(c1 - c2)  # Counter({'a': 2})  (drops zero/negatives)

# Missing keys return 0 (not KeyError)
print(c["dog"])  # 0

# Update and elements
c.update(["cat", "cat"])
print(sorted(c.elements()))  # ['cat','cat','cat','cat','mat','on','sat','the','the','the']

defaultdict

defaultdict автоматически создаёт отсутствующие ключи со значением по умолчанию из фабричной функции — list для группировки, int для подсчёта, set для дедупликации. Это устраняет шаблон 'if key not in dict'. Фабрика вызывается только при отсутствии ключа, а не при каждом доступе.

python
from collections import defaultdict

# Group items by key without checking if key exists
words = ["apple", "banana", "avocado", "blueberry", "cherry"]
by_first = defaultdict(list)
for w in words:
    by_first[w[0]].append(w)
print(dict(by_first))
# {'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}

# Counting with int (default 0)
counts = defaultdict(int)
for w in words:
    counts[w[0]] += 1
print(dict(counts))  # {'a': 2, 'b': 2, 'c': 1}

# Nested defaultdicts
tree = defaultdict(lambda: defaultdict(list))
tree["2024"]["Jan"].append("event1")

# vs regular dict: avoids the key-check boilerplate
# d = {}
# for w in words:
#     if w[0] not in d:
#         d[w[0]] = []
#     d[w[0]].append(w)

OrderedDict и deque

deque обеспечивает O(1) append/pop на обоих концах — используйте для очередей, BFS и скользящих окон вместо списков (list.pop(0) — O(n)). С maxlen deque автоматически отбрасывает старые элементы, идеально для ограниченных буферов. OrderedDict менее нужен с 3.7 (dict упорядочены), но его move_to_end и popitem всё ещё уникально полезны для LRU-кэшей.

python
from collections import OrderedDict, deque

# deque: double-ended queue, O(1) append/pop at both ends
dq = deque([1, 2, 3], maxlen=5)
dq.appendleft(0)   # deque([0, 1, 2, 3])
dq.append(4)       # deque([0, 1, 2, 3, 4])
dq.append(5)       # deque([1, 2, 3, 4, 5]) — oldest dropped (maxlen!)
print(dq.popleft())  # 1
print(dq)            # deque([2, 3, 4, 5])

# deque is ideal for queues, BFS, sliding windows
from collections import deque
queue = deque(["task1", "task2"])
queue.append("task3")
next_task = queue.popleft()  # FIFO — O(1) vs list.pop(0) which is O(n)

# OrderedDict: remembers insertion order (regular dicts do too in 3.7+,
# but OrderedDict has move_to_end and equality is order-sensitive)
od = OrderedDict([("a", 1), ("b", 2)])
od.move_to_end("a")  # move to last
print(list(od))  # ['b', 'a']
od.popitem(last=False)  # pop first item (FIFO)

itertools: chain, product, combinations, permutations

itertools предоставляет быстрые, эффективные по памяти инструменты для комбинаторики. chain лениво выравнивает итерируемые объекты. product даёт декартовы произведения (заменяет вложенные for). combinations/permutations генерируют выборки без построения полного списка — необходимо для больших или бесконечных входов. Все возвращают итераторы, оберните в list() для просмотра.

python
from itertools import chain, product, combinations, permutations

# chain: flatten multiple iterables
list(chain([1, 2], [3, 4], [5]))  # [1, 2, 3, 4, 5]
list(chain.from_iterable([[1, 2], [3, 4]]))  # [1, 2, 3, 4]

# product: Cartesian product (nested loops)
list(product([1, 2], ["a", "b"]))
# [(1,'a'), (1,'b'), (2,'a'), (2,'b')]
list(product("AB", repeat=2))  # [('A','A'),('A','B'),('B','A'),('B','B')]

# combinations: unordered selections (no repeats)
list(combinations("ABC", 2))  # [('A','B'),('A','C'),('B','C')]
list(combinations("AAA", 2))  # [('A','A'),('A','A'),('A','A')]

# permutations: ordered arrangements
list(permutations("ABC", 2))  # [('A','B'),('A','C'),('B','A'),('B','C'),('C','A'),('C','B')]

# combinations_with_replacement: allow picking same element
from itertools import combinations_with_replacement
list(combinations_with_replacement("AB", 2))  # [('A','A'),('A','B'),('B','B')]

itertools: groupby, accumulate, starmap

groupby группирует последовательные элементы с общим ключом — сначала отсортируйте по ключу, иначе получите несколько групп для одного ключа. accumulate производит текущие итоги/произведения. islice, takewhile и dropwhile — ленивые альтернативы срезам и фильтрации, работающие с любым итератором, включая бесконечные.

python
from itertools import groupby, accumulate, starmap, islice, takewhile, dropwhile

# groupby: group consecutive items by a key (sort first!)
data = [("A", 1), ("A", 2), ("B", 3), ("B", 4), ("A", 5)]
data.sort(key=lambda x: x[0])  # MUST sort by key first
for key, group in groupby(data, key=lambda x: x[0]):
    print(key, list(group))
# A [('A',1),('A',2),('A',5)]
# B [('B',3),('B',4)]

# accumulate: running aggregate (sum by default)
list(accumulate([1, 2, 3, 4]))  # [1, 3, 6, 10]
import operator
list(accumulate([1, 2, 3, 4], operator.mul))  # [1, 2, 6, 24]

# starmap: unpack args from tuples before calling
list(starmap(pow, [(2, 3), (3, 2), (10, 3)]))  # [8, 9, 1000]

# islice: slice an iterator (doesn't support negative indices)
list(islice(range(100), 5, 10))  # [5, 6, 7, 8, 9]

# takewhile / dropwhile: filter by predicate
list(takewhile(lambda x: x < 5, [1, 4, 6, 3, 8]))  # [1, 4]
list(dropwhile(lambda x: x < 5, [1, 4, 6, 3, 8]))  # [6, 3, 8]

functools: lru_cache, partial, reduce

lru_cache мемоизирует результаты — значительное ускорение для рекурсивных или дорогих чистых функций; cache_info() показывает статистику попаданий/промахов. partial предзаполняет аргументы для создания специализированных вызываемых объектов. reduce применяет функцию кумулятивно (хотя sum(), any(), all() часто заменяют его). cached_property вычисляет один раз и кэширует на экземпляре.

python
from functools import lru_cache, partial, reduce
import operator

# lru_cache: memoize function results (Least Recently Used)
@lru_cache(maxsize=128)
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(100))  # instant (without cache: impossibly slow)
print(fib.cache_info())  # CacheInfo(hits=98, misses=101, ...)

# partial: fix some arguments, create a new callable
def power(base, exponent):
    return base ** exponent

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

# reduce: cumulatively apply a function, reducing to one value
product = reduce(operator.mul, [1, 2, 3, 4])  # 24
# Equivalent: ((1*2)*3)*4

# Python 3.8+: cached_property for lazy computed attributes
from functools import cached_property
class Data:
    @cached_property
    def expensive(self):
        print("computing...")
        return [i ** 2 for i in range(1000000)]
21

Обработка JSON и CSV

json.dumps и json.loads

json.dumps() (dump string) сериализует объект Python в JSON-строку; json.loads() (load string) парсит JSON обратно. Используйте indent для читаемости, ensure_ascii=False для сохранения Unicode читаемым и sort_keys для детерминированного вывода. Ключи JSON должны быть строками — int-ключи становятся строками.

python
import json

# Serialize Python object to JSON string
data = {"name": "Alice", "age": 30, "scores": [95, 88, 92]}
json_str = json.dumps(data)
print(json_str)  # {"name": "Alice", "age": 30, "scores": [95, 88, 92]}

# Pretty-print with indent
print(json.dumps(data, indent=2))
# {
#   "name": "Alice",
#   "age": 30,
#   "scores": [95, 88, 92]
# }

# Parse JSON string to Python object
parsed = json.loads(json_str)
print(parsed["name"])  # Alice
print(type(parsed["scores"]))  # <class 'list'>

# Sort keys, handle non-ASCII
print(json.dumps({"name": "Zoë"}, ensure_ascii=False, sort_keys=True))

Чтение и запись JSON-файлов

json.dump() записывает напрямую в файловый объект; json.load() читает из него. Всегда указывайте encoding='utf-8' для переносимости. Запомните отображение типов: JSON-объекты становятся dict, массивы — list, числа — int или float. Datetime, set и пользовательские объекты НЕ JSON-сериализуемы по умолчанию.

python
import json

data = {"users": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]}

# Write to file
with open("data.json", "w", encoding="utf-8") as f:
    json.dump(data, f, indent=2, ensure_ascii=False)

# Read from file
with open("data.json", "r", encoding="utf-8") as f:
    loaded = json.load(f)

print(loaded["users"][0]["name"])  # Alice

# Type conversions to remember:
# JSON object  <-> Python dict
# JSON array   <-> Python list
# JSON string  <-> Python str
# JSON number  <-> Python int/float
# JSON true/false <-> Python True/False
# JSON null    <-> Python None

Пользовательское JSON-кодирование (datetime, пользовательские объекты)

Модуль json не может сериализовать datetime, set или пользовательские классы по умолчанию. Предоставьте функцию default (вызывается для несериализуемых объектов) или подкласс JSONEncoder. Для round-trip соедините пользовательский кодер с object_hook в loads() для восстановления исходных типов. Так ORM сериализуют объекты моделей.

python
import json
from datetime import datetime

# Default behavior: TypeError on non-serializable types
# json.dumps({"now": datetime.now()})  # TypeError!

# Solution 1: default function for unknown types
def default_encoder(obj):
    if isinstance(obj, datetime):
        return obj.isoformat()
    if isinstance(obj, set):
        return sorted(obj)
    raise TypeError(f"Cannot serialize {type(obj)}")

data = {"now": datetime.now(), "tags": {"a", "b"}}
print(json.dumps(data, default=default_encoder))

# Solution 2: custom JSONEncoder subclass
class MyEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return {"__datetime__": obj.isoformat()}
        return super().default(obj)

print(json.dumps(data, cls=MyEncoder))

# Decoding with object_hook
def decoder(dct):
    if "__datetime__" in dct:
        return datetime.fromisoformat(dct["__datetime__"])
    return dct
json.loads(json.dumps(data, cls=MyEncoder), object_hook=decoder)

Чтение CSV-файлов

Всегда открывайте CSV-файлы с newline='' во избежание проблем с пустыми строками на Windows. csv.reader возвращает списки; csv.DictReader возвращает dict с ключами из строки заголовка. Модуль csv корректно обрабатывает кавычки, встроенные запятые и переносы — никогда не разделяйте CSV-строки вручную через line.split(','). Используйте Sniffer для автоопределения разделителей.

python
import csv

# Basic reader: each row is a list of strings
with open("data.csv", newline="", encoding="utf-8") as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)  # ['name', 'age', 'city']

# DictReader: each row is a dict keyed by header
with open("data.csv", newline="") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row["name"], row["age"])  # access by column name

# Handle different delimiters and quoting
with open("data.tsv", newline="") as f:
    reader = csv.reader(f, delimiter="\t", quotechar='"')
    for row in reader:
        print(row)

# Sniffer to auto-detect format
with open("unknown.csv", newline="") as f:
    sample = f.read(1024)
    dialect = csv.Sniffer().sniff(sample)
    f.seek(0)
    reader = csv.reader(f, dialect)

Запись CSV-файлов

csv.writer пишет списки; csv.DictWriter пишет dict с фиксированным набором fieldnames. Всегда используйте newline='' при открытии файла. Параметр quoting контролирует, когда поля кавычатся — QUOTE_MINIMAL (по умолчанию) только при необходимости, QUOTE_ALL кавычит всё, полезно для строгих парсеров.

python
import csv

rows = [
    ["name", "age", "city"],
    ["Alice", 30, "NYC"],
    ["Bob", 25, "LA"],
]

# Basic writer
with open("out.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerows(rows)  # write multiple rows

# DictWriter: write from dicts
with open("out.csv", "w", newline="") as f:
    fieldnames = ["name", "age", "city"]
    writer = csv.DictWriter(f, fieldnames=fieldnames)
    writer.writeheader()
    writer.writerow({"name": "Alice", "age": 30, "city": "NYC"})
    writer.writerow({"name": "Bob", "age": 25, "city": "LA"})

# Control quoting: QUOTE_MINIMAL (default), QUOTE_ALL, QUOTE_NONNUMERIC
writer = csv.writer(f, quoting=csv.QUOTE_ALL)
# QUOTE_ALL wraps every field in quotes: "Alice","30","NYC"

JSON Lines (NDJSON) и потоковая обработка

JSON Lines (NDJSON) помещает один JSON-объект на строку — идеально для логов, потоков событий и append-only файлов, потому что можно обрабатывать каждую строку независимо. Для огромных одиночных JSON-документов используйте библиотеку ijson для потокового парсинга без загрузки всего файла в память. NDJSON — стандарт для многих data-пайплайнов.

python
import json

# JSON Lines: one JSON object per line (great for logs, big data)
records = [{"id": 1, "msg": "first"}, {"id": 2, "msg": "second"}]

# Write NDJSON
with open("logs.jsonl", "w") as f:
    for rec in records:
        f.write(json.dumps(rec) + "\n")

# Read NDJSON line by line (memory-efficient for huge files)
with open("logs.jsonl", "r") as f:
    for line in f:
        rec = json.loads(line)
        print(rec["id"], rec["msg"])

# Stream large JSON arrays without loading everything into memory
# Use ijson library for streaming parsing of huge JSON files:
# import ijson
# with open("huge.json", "rb") as f:
#     for item in ijson.items(f, "items.item"):
#         process(item)  # one item at a time
22

Логирование и тестирование

Основы logging

Модуль logging — стандартный способ вывода диагностической информации — гораздо лучше print(), потому что вы контролируете уровни, форматы и направления. Используйте logging.getLogger(__name__) для каждого модуля, чтобы настраивать детализацию по модулю. logging.exception() автоматически включает traceback. Настройте basicConfig один раз при запуске.

python
import logging

# Basic configuration (call once at program start)
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)

# Log levels (severity ascending)
logging.debug("Detailed debug info")     # DEBUG (10)
logging.info("General information")       # INFO (20)
logging.warning("Something unexpected")   # WARNING (30)
logging.error("A real error occurred")    # ERROR (40)
logging.critical("System is down")        # CRITICAL (50)

# Logging exceptions with traceback
try:
    1 / 0
except ZeroDivisionError:
    logging.exception("Division failed")  # includes full traceback

# Get a named logger (best practice per module)
logger = logging.getLogger(__name__)
logger.info("Module-specific log")

Логирование в файл и несколько обработчиков

Обработчики направляют логи в места назначения — консоль, файлы, сеть, email. RotatingFileHandler ограничивает размер файла и хранит резервные копии, предотвращая неограниченный рост логов. Каждый обработчик может иметь свой уровень и формат (например, детальные логи в файл, краткие в консоль). TimedRotatingFileHandler ротирует по времени, а не по размеру.

python
import logging
from logging.handlers import RotatingFileHandler

logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)

# Console handler (INFO and above)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))

# Rotating file handler (DEBUG and above, max 5MB x 3 backups)
file_handler = RotatingFileHandler(
    "app.log", maxBytes=5_000_000, backupCount=3, encoding="utf-8"
)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(
    logging.Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s")
)

logger.addHandler(console)
logger.addHandler(file_handler)

logger.debug("debug to file only")
logger.info("info to both console and file")
logger.error("error everywhere")

Основы unittest

unittest — встроенный тестовый фреймворк Python (стиль xUnit). Тесты живут в классах, наследующих TestCase. setUp/tearDown выполняются до/после каждого теста для изоляции. Общие утверждения: assertEqual, assertTrue, assertRaises, assertIn. Запускайте через python -m unittest для автообнаружения файлов test_*.py.

python
import unittest

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

def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

class TestMath(unittest.TestCase):
    def setUp(self):
        # runs before each test method
        self.data = [1, 2, 3]

    def tearDown(self):
        # runs after each test method
        pass

    def test_add(self):
        self.assertEqual(add(1, 2), 3)
        self.assertEqual(add(-1, 1), 0)

    def test_add_types(self):
        self.assertEqual(add("a", "b"), "ab")

    def test_divide_by_zero(self):
        with self.assertRaises(ValueError):
            divide(1, 0)

    def test_membership(self):
        self.assertIn(2, self.data)
        self.assertTrue(3 in self.data)

if __name__ == "__main__":
    unittest.main()
# Run: python -m unittest test_file.py -v

Основы pytest

pytest — самый популярный инструмент тестирования Python — обычные операторы assert дают богатые отчёты об ошибках, без шаблонных классов. pytest.raises проверяет исключения с опциональным regex-сопоставлением. pytest.approx обрабатывает неточность сравнения float. Установите через pip install pytest и запускайте через pytest -v для подробного вывода.

python
# test_math.py — pytest is simpler and more powerful than unittest
# Install: pip install pytest
# Run: pytest -v

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

def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

# Plain functions, no classes required
def test_add():
    assert add(1, 2) == 3
    assert add(-1, 1) == 0

def test_add_strings():
    assert add("hello", " world") == "hello world"

# Testing exceptions with pytest.raises
import pytest

def test_divide_by_zero():
    with pytest.raises(ValueError, match="Cannot divide by zero"):
        divide(1, 0)

# Approximate float comparison
def test_float():
    assert 0.1 + 0.2 == pytest.approx(0.3)

Фикстуры pytest

Фикстуры — внедрение зависимостей pytest — они предоставляют тестовые данные, mock-объекты или ресурсы тестам через имена параметров. Фикстуры на yield обрабатывают и настройку (до yield), и разбор (после yield). Scope контролируют переиспользование: 'session' создаёт фикстуру один раз для всего запуска, 'module' один раз на файл, 'function' (по умолчанию) один раз на тест.

python
import pytest

# A fixture provides setup data/resources to tests
@pytest.fixture
def sample_list():
    return [1, 2, 3, 4, 5]

# Use fixtures by passing their name as a parameter
def test_length(sample_list):
    assert len(sample_list) == 5

def test_sum(sample_list):
    assert sum(sample_list) == 15

# Fixture with setup AND teardown (yield)
@pytest.fixture
def db_connection():
    print("\n[setup] connecting to DB")
    conn = {"connected": True}
    yield conn  # test runs here; value passed to test
    print("\n[teardown] closing DB")
    conn["connected"] = False

def test_db(db_connection):
    assert db_connection["connected"] is True

# Fixture scopes: function (default), class, module, session
@pytest.fixture(scope="session")
def expensive_resource():
    return load_large_dataset()  # created once per test session

pytest parametrize и mocking

parametrize запускает одну тестовую функцию на нескольких наборах входных данных — устраняет копирование-вставку тестового кода и даёт чёткий вывод по каждому случаю. unittest.mock.patch заменяет функции/объекты на mock для изолированного тестирования. assert_called_once_with проверяет, что mock использовался корректно. @pytest.mark.skip и xfail элегантно обрабатывают незавершённые тесты.

python
import pytest
from unittest.mock import patch, MagicMock

# parametrize: run one test with multiple inputs
@pytest.mark.parametrize("a, b, expected", [
    (1, 2, 3),
    (-1, 1, 0),
    (0, 0, 0),
    (100, 200, 300),
])
def test_add_many(a, b, expected):
    assert add(a, b) == expected

# parametrize with IDs for readable output
@pytest.mark.parametrize("x", [1, 2, 3], ids=["one", "two", "three"])
def test_ids(x):
    assert x > 0

# Mocking: replace external dependencies
def fetch_user(uid):
    # imagine this calls a real API
    return {"id": uid, "name": "real_user"}

@patch("__main__.fetch_user")
def test_with_mock(mock_fetch):
    mock_fetch.return_value = {"id": 1, "name": "mocked"}
    result = fetch_user(1)
    assert result["name"] == "mocked"
    mock_fetch.assert_called_once_with(1)

# Skip and expected failure
@pytest.mark.skip(reason="not implemented yet")
def test_future():
    pass

@pytest.mark.xfail(reason="known bug #42")
def test_known_bug():
    assert 1 == 2
23

Сетевое программирование

TCP-сервер

Создаёт TCP-сервер с использованием модуля socket. bind связывает сокет с адресом, listen устанавливает очередь backlog, accept блокирует до подключения клиента. Всегда закрывайте подключения для освобождения файловых дескрипторов.

python
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 8080))
server.listen(5)
conn, addr = server.accept()
data = conn.recv(1024)
conn.sendall(b'Hello')
conn.close()

TCP-клиент

Создаёт TCP-клиент, подключающийся к серверу. connect устанавливает соединение, sendall отправляет все байты, recv читает до указанного количества байт. Используйте encode/decode для преобразования строка-байты.

python
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('localhost', 8080))
client.sendall(b'Hello Server')
response = client.recv(1024)
print(response.decode())
client.close()

UDP-сокет

UDP без соединения: нет handshake, нет гарантированной доставки. recvfrom возвращает и данные, и адрес отправителя. Используйте SOCK_DGRAM для UDP. Идеально для DNS, игр и потокового вещания в реальном времени.

python
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('localhost', 9090))
data, addr = sock.recvfrom(1024)
print(f"From {addr}: {data.decode()}")
sock.sendto(b'Reply', addr)

HTTP-сервер

Модуль http.server предоставляет простой HTTP-сервер. Наследуйте BaseHTTPRequestHandler и переопределите do_GET, do_POST. Используйте только для разработки; для production используйте gunicorn.

python
from http.server import HTTPServer, BaseHTTPRequestHandler
class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-Type', 'text/html')
        self.end_headers()
        self.wfile.write(b'<h1>Hello</h1>')
HTTPServer(('localhost', 8000), Handler).serve_forever()

Таймаут сокета

settimeout устанавливает таймаут для всех операций сокета. Если операция превышает таймаут, возбуждается исключение socket.timeout. Используйте try/finally для гарантии очистки.

python
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5.0)
try:
    sock.connect(('example.com', 80))
    data = sock.recv(1024)
except socket.timeout:
    print('Connection timed out')
finally:
    sock.close()
24

База данных (SQLite)

Создание таблицы

sqlite3 встроен в Python. connect создаёт или открывает файл базы данных. CREATE TABLE IF NOT EXISTS предотвращает ошибки, если таблица существует. Всегда вызывайте commit для сохранения изменений.

python
import sqlite3
conn = sqlite3.connect('example.db')
cursor = conn.cursor()
cursor.execute('''CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL, email TEXT UNIQUE, age INTEGER)''')
conn.commit()

Вставка данных

Всегда используйте параметризованные запросы (? плейсхолдеры) для предотвращения SQL-инъекций. lastrowid возвращает автоинкрементный ID. Никогда не используйте строковое форматирование для значений SQL.

python
cursor.execute(
    'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
    ('Alice', '[email protected]', 30))
conn.commit()
print(f"ID: {cursor.lastrowid}")

Запрос данных

fetchall возвращает все совпадающие строки как список кортежей. fetchone возвращает одну строку или None. Для больших результатов итерируйте по курсору напрямую.

python
cursor.execute('SELECT * FROM users WHERE age > ?', (25,))
rows = cursor.fetchall()
for row in rows:
    print(row)
cursor.execute('SELECT * FROM users WHERE id = ?', (1,))
user = cursor.fetchone()

Обновление и удаление

UPDATE модифицирует существующие строки, DELETE удаляет их. rowcount указывает затронутые строки. Всегда используйте WHERE с DELETE. commit сохраняет изменения.

python
cursor.execute('UPDATE users SET age = ? WHERE name = ?', (31, 'Alice'))
cursor.execute('DELETE FROM users WHERE age < ?', (18,))
conn.commit()
print(f"Affected: {cursor.rowcount} rows")

Фабрика строк

Использование conn как менеджера контекста автоматически коммитит при успехе и откатывает при исключении. row_factory = sqlite3.Row позволяет обращаться к столбцам по имени.

python
conn = sqlite3.connect('example.db')
conn.row_factory = sqlite3.Row
with conn:
    conn.execute('INSERT INTO users (name, email) VALUES (?, ?)',
                 ('Bob', '[email protected]'))
for row in conn.execute('SELECT * FROM users'):
    print(row['name'], row['email'])
25

Веб-скрапинг

Основы BeautifulSoup

requests получает HTML-содержимое, BeautifulSoup парсит его. html.parser встроенный; lxml быстрее. Всегда проверяйте response.status_code перед парсингом.

python
import requests
from bs4 import BeautifulSoup
resp = requests.get('https://example.com')
soup = BeautifulSoup(resp.text, 'html.parser')
print(soup.title.string)
print(soup.find('h1').text)

Поиск элементов

find_all возвращает все совпадающие элементы, find возвращает первый. Используйте class_ (с подчёркиванием). select использует CSS-селекторы для сложных запросов.

python
links = soup.find_all('a')
for link in links:
    print(link.get('href'), link.text)
article = soup.find('div', class_='article')
items = soup.select('ul.list > li.item')

Извлечение таблиц

Таблицы структурированы как tr (строки), содержащие td (данные) или th (заголовок) ячейки. strip удаляет пробелы. find_all принимает список имён тегов.

python
table = soup.find('table')
for row in table.find_all('tr'):
    cols = row.find_all(['td', 'th'])
    data = [col.text.strip() for col in cols]
    print(data)

Обработка пагинации

Пагинация обрабатывается переходом по ссылкам на следующую страницу. select_one возвращает первое совпадение или None. Добавляйте time.sleep между запросами.

python
all_items = []
url = 'https://example.com/page/1'
while url:
    resp = requests.get(url)
    soup = BeautifulSoup(resp.text, 'html.parser')
    all_items.extend([i.text for i in soup.select('.item')])
    next_link = soup.select_one('a.next')
    url = next_link.get('href') if next_link else None

Сохранение в CSV

csv.DictWriter пишет словари в CSV. newline предотвращает лишние пустые строки на Windows. encoding=utf-8 обрабатывает специальные символы.

python
import csv
with open('data.csv', 'w', newline='', encoding='utf-8') as f:
    writer = csv.DictWriter(f, fieldnames=['name', 'price'])
    writer.writeheader()
    for item in scraped_data:
        writer.writerow(item)
26

Асинхронный веб (aiohttp)

HTTP-клиент

aiohttp предоставляет асинхронный HTTP. ClientSession управляет пулом подключений. async with гарантирует очистку. asyncio.run выполняет корутину.

python
import aiohttp, asyncio
async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.text()
data = asyncio.run(fetch('https://api.example.com'))

Конкурентные запросы

asyncio.gather запускает корутины конкурентно, снижая общее время. Все запросы разделяют одну сессию. Используйте semaphore для ограничения конкурентности.

python
async def fetch_all(urls):
    async with aiohttp.ClientSession() as session:
        tasks = [session.get(url) for url in urls]
        responses = await asyncio.gather(*tasks)
        return [await r.text() for r in responses]

Веб-сервер

aiohttp.web создаёт асинхронные веб-серверы. Маршруты определяются HTTP-методом и шаблоном пути. match_info извлекает параметры пути.

python
from aiohttp import web
async def handle(request):
    name = request.match_info.get('name', 'World')
    return web.json_response({'message': f'Hello, {name}!'})
app = web.Application()
app.add_routes([web.get('/', handle), web.get('/{name}', handle)])
web.run_app(app, port=8080)

WebSocket-сервер

WebSockets обеспечивают двунаправленную связь в реальном времени. WebSocketResponse обрабатывает handshake обновления. async for итерирует по сообщениям.

python
async def ws_handler(request):
    ws = web.WebSocketResponse()
    await ws.prepare(request)
    async for msg in ws:
        if msg.type == aiohttp.WSMsgType.TEXT:
            await ws.send_str(f'Echo: {msg.data}')
    return ws

Сессия с cookies

ClientSession автоматически сохраняет cookies между запросами. Необходимо для аутентифицированного скрапинга. Используйте одну сессию для всех запросов.

python
async def login_and_fetch():
    async with aiohttp.ClientSession() as session:
        await session.post('https://example.com/login',
                          data={'user': 'admin', 'pass': '123'})
        resp = await session.get('https://example.com/dashboard')
        return await resp.text()
27

Многопроцессорность (углублённо)

Пул процессов

Pool управляет рабочими процессами. map распределяет работу параллельно. apply_async запускает одну функцию асинхронно. Всегда используйте защиту if __name__ == main на Windows.

python
from multiprocessing import Pool
def square(x): return x * x
if __name__ == '__main__':
    with Pool(4) as pool:
        results = pool.map(square, range(10))
        result = pool.apply_async(square, (100,))
        print(result.get(timeout=5))

Разделяемая память

Value и Array создают разделяемую память между процессами. Используйте get_lock для синхронизации доступа и предотвращения гонок.

python
from multiprocessing import Value, Array
counter = Value('i', 0)
arr = Array('d', [0.0, 1.0, 2.0])
with counter.get_lock():
    counter.value += 1

Связь через очередь

Queue обеспечивает безопасную связь между процессами. put добавляет элементы, get извлекает их. Queue безопасна для процессов, обрабатывая блокировку внутренне.

python
from multiprocessing import Process, Queue
def worker(q):
    q.put('Data from worker')
if __name__ == '__main__':
    q = Queue()
    p = Process(target=worker, args=(q,))
    p.start()
    print(q.get())
    p.join()

Pipe

Pipe создаёт двунаправленный канал связи. send и recv передают объекты Python через pickling. Pipe быстрее Queue для point-to-point связи.

python
from multiprocessing import Process, Pipe
def worker(conn):
    conn.send(['hello', 'world'])
    msg = conn.recv()
    conn.close()
if __name__ == '__main__':
    parent, child = Pipe()
    p = Process(target=worker, args=(child,))
    p.start()
    print(parent.recv())
    parent.send('acknowledged')
    p.join()

Синхронизация

Lock гарантирует, что только один процесс обращается к общему ресурсу за раз. with lock автоматически захватывает и освобождает. Другие примитивы: RLock, Semaphore, Event.

python
from multiprocessing import Process, Lock
def safe_print(lock, msg):
    with lock:
        print(msg)
if __name__ == '__main__':
    lock = Lock()
    procs = [Process(target=safe_print, args=(lock, f'Task {i}'))
             for i in range(5)]
    for p in procs: p.start()
    for p in procs: p.join()
28

Виртуальные окружения (углублённо)

Модуль venv

venv создаёт изолированные окружения Python с собственными каталогами пакетов. Активация модифицирует PATH. Всегда активируйте перед установкой зависимостей.

python
# Create
python -m venv myenv
# Activate (Linux/Mac)
source myenv/bin/activate
# Activate (Windows)
myenv\Scripts\activate
# Deactivate
deactivate

requirements.txt

requirements.txt перечисляет зависимости проекта. == закрепляет точные версии, >= разрешает обновления в диапазоне. Всегда коммитьте в систему контроля версий.

python
# Generate
pip freeze > requirements.txt
# Install
pip install -r requirements.txt
# Pin versions
flask==2.3.3
requests>=2.28.0,<3.0.0

Poetry

Poetry — современный менеджер зависимостей. pyproject.toml заменяет requirements.txt. Виртуальные окружения управляются автоматически.

python
# Initialize
poetry init
# Add dependency
poetry add flask
poetry add pytest --group dev
# Install all
poetry install
# Run command
poetry run python app.py

pipenv

pipenv объединяет pip и virtualenv. Pipfile объявляет зависимости, Pipfile.lock закрепляет точные версии. --dev разделяет development-зависимости.

python
# Create environment
pipenv install
# Add package
pipenv install requests
pipenv install pytest --dev
# Activate shell
pipenv shell
# Run command
pipenv run python app.py

Окружения Conda

Conda управляет как Python, так и не-Python зависимостями. environment.yml фиксирует полное окружение. Идеально для data science с бинарными зависимостями.

python
# Create
conda create -n myenv python=3.11
# Activate
conda activate myenv
# Export
conda env export > environment.yml
# Recreate
conda env create -f environment.yml
29

Продвинутое использование pip

Установка из Git

Устанавливайте пакеты напрямую из Git-репозиториев. Полезно для невыпущенных версий, форков или приватных пакетов. @branch или @commit закрепляет конкретную версию.

python
# From GitHub
pip install git+https://github.com/user/repo.git
# Specific branch
pip install git+https://github.com/user/repo.git@branch-name
# Specific commit
pip install git+https://github.com/user/repo.git@abc123

Редактируемая установка

Редактируемая установка (-e) связывает пакет вместо копирования. Изменения сразу доступны без переустановки. Необходимо для разработки пакетов.

python
# Install in development mode
pip install -e .
# From a specific path
pip install -e /path/to/package
# With extras
pip install -e ".[dev,test]"

Ограничения и хеши

Ограничения ограничивают, какие версии можно установить. Проверка хешей верифицирует целостность пакетов, предотвращая атаки на цепочку поставок.

python
# constraints.txt
flask==2.3.3
pip install -c constraints.txt flask
# Hash checking
pip install --require-hashes -r requirements.txt

Управление кэшем

pip кэширует загруженные wheels. --no-cache-dir форсирует свежие загрузки. Purge освобождает место на диске, когда кэш слишком разрастается.

python
# Show cache info
pip cache info
# List cached packages
pip cache list
# Purge entire cache
pip cache purge
# Install with no cache
pip install --no-cache-dir flask

Пользовательский индекс

--index-url указывает пользовательский репозиторий пакетов. --extra-index-url добавляет fallback. --trusted-host обходит SSL для внутренних реестров.

python
# Use custom index
pip install --index-url https://pypi.custom.com/simple/ flask
# Extra index (fallback)
pip install --extra-index-url https://pypi.custom.com/simple/ flask
# Trusted host (no SSL)
pip install --trusted-host pypi.custom.com flask
30

Проверка типов (mypy)

Базовые аннотации типов

Аннотации типов аннотируют параметры функций и типы возврата. Python 3.9+ разрешает встроенные типы напрямую. Аннотации включают статический анализ с mypy.

python
def greet(name: str, times: int = 1) -> str:
    return (f"Hello, {name}! " * times).strip()
def process(data: list[int]) -> dict[str, int]:
    return {str(x): x for x in data}

Optional и Union

Optional[X] эквивалентно X | None (Python 3.10+). Union-типы допускают несколько возможных типов. mypy проверяет, что все пути кода обрабатывают все типы.

python
from typing import Optional
def find(items: list[int], target: int) -> int | None:
    for i, v in enumerate(items):
        if v == target: return i
    return None

Generic-типы

Дженерики создают переиспользуемые type-safe контейнеры. TypeVar определяет переменную типа, Generic делает класс generic. mypy обеспечивает согласованность типов.

python
from typing import TypeVar, Generic
T = TypeVar('T')
class Stack(Generic[T]):
    def __init__(self) -> None:
        self._items: list[T] = []
    def push(self, item: T) -> None:
        self._items.append(item)
    def pop(self) -> T:
        return self._items.pop()

Protocol

Protocol определяет структурное субтипирование (duck typing с проверкой типов). Любой класс с требуемыми методами удовлетворяет протоколу, наследование не нужно.

python
from typing import Protocol
class Closeable(Protocol):
    def close(self) -> None: ...
def cleanup(resource: Closeable) -> None:
    resource.close()
class File:
    def close(self) -> None: print("Closed")
cleanup(File())  # OK - File has close()

Конфигурация mypy

mypy.ini настраивает строгость проверки типов. strict включает все проверки. Поключевые переопределения смягчают правила для тестов или legacy-кода.

python
# mypy.ini
[mypy]
python_version = 3.11
strict = True
warn_return_any = True
disallow_untyped_defs = True
[mypy-tests.*]
ignore_errors = True
31

Советы по производительности

Список против генератора

Списки хранят все элементы в памяти; генераторы производят значения по требованию. Используйте генераторы для больших последовательностей, итерируемых один раз.

python
# List: all in memory
squares = [x**2 for x in range(1000000)]
# Generator: lazy evaluation
squares_gen = (x**2 for x in range(1000000))
import sys
print(sys.getsizeof(squares))      # ~8MB
print(sys.getsizeof(squares_gen))  # ~200 bytes

Конкатенация строк

Конкатенация строк через += — O(n^2). join — O(n). f-строки — самый быстрый метод интерполяции.

python
# Slow: creates intermediate strings
result = ""
for s in parts:
    result += s
# Fast: join in one operation
result = "".join(parts)
# Fast: f-strings
msg = f"Hello, {name}!"

Локальные переменные

Поиск локальных переменных быстрее поиска глобальных или атрибутов. Назначение часто используемых функций в локальные ускоряет циклы.

python
import math
# Slow: global lookup
def compute_slow(values):
    return [math.sqrt(v) for v in values]
# Fast: local reference
def compute_fast(values):
    sqrt = math.sqrt
    return [sqrt(v) for v in values]

__slots__

__slots__ предотвращает создание __dict__, экономя 40-50% памяти на экземпляр. Значительно при создании миллионов объектов. Нельзя добавить неуказанные атрибуты.

python
class Point:
    __slots__ = ('x', 'y')
    def __init__(self, x, y):
        self.x = x
        self.y = y
p = Point(1, 2)
# p.z = 3  # AttributeError

timeit и cProfile

timeit измеряет время выполнения маленьких сниппетов. cProfile показывает, куда уходит время. Используйте профилирование перед оптимизацией для поиска реальных узких мест.

python
import timeit
t = timeit.timeit('sum(range(100))', number=10000)
print(f"{t:.4f}s")
import cProfile
cProfile.run('sum(x**2 for x in range(10000))')
32

Распространённые подводные камни

Изменяемые аргументы по умолчанию

Значения аргументов по умолчанию вычисляются один раз при определении. Изменяемые значения по умолчанию разделяются между всеми вызовами. Всегда используйте None как значение по умолчанию.

python
# BUG: default list is shared
def add_item(item, lst=[]):
    lst.append(item)
    return lst
print(add_item(1))  # [1]
print(add_item(2))  # [1, 2]!
# FIX: use None
def add_item(item, lst=None):
    if lst is None: lst = []
    lst.append(item)
    return lst

Позднее связывание замыканий

Замыкания захватывают переменные по ссылке. К моменту вызова лямбд переменная цикла имеет финальное значение. Аргументы по умолчанию захватывают текущее значение.

python
# BUG: all print 2
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])  # [2, 2, 2]
# FIX: default argument
funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs])  # [0, 1, 2]

Кэширование целых чисел

Python кэширует маленькие целые числа. is проверяет тождественность, == проверяет равенство. Никогда не используйте is для сравнения значений; используйте is только для None, True, False.

python
# Small integers cached (-5 to 256)
a = 256; b = 256
print(a is b)  # True (cached)
c = 257; d = 257
print(c is d)  # False (not cached)
print(c == d)  # True

is против ==

is проверяет, указывают ли две ссылки на один объект. == проверяет, имеют ли два объекта одинаковое значение. Используйте is только для None, True, False.

python
a = [1, 2, 3]; b = [1, 2, 3]
print(a == b)  # True (same values)
print(a is b)  # False (different objects)
# Correct usage of is
if x is None: ...
if x is not None: ...

GIL

GIL позволяет только одному потоку выполнять Python-байткод за раз. Threading эффективен для I/O-bound задач. Используйте multiprocessing для CPU-bound параллелизма.

python
# GIL prevents true parallelism for CPU-bound tasks
import threading
def cpu_work():
    total = sum(i**2 for i in range(10**7))
# Use multiprocessing for CPU work
from multiprocessing import Pool
with Pool(4) as p:
    p.map(cpu_work, range(4))

Связанные сниппеты Python

Copy-paste ready code for common tasks.

Сортировка словаря по значению

Сортировка словаря Python по значениям в порядке убывания.

Списковое включение

Быстрая генерация списков с помощью списковых включений.

Слияние словарей

Несколько способов слияния словарей.

Чтение/запись файлов

Различные способы чтения и записи файлов.

Обработка CSV

Чтение и запись CSV-файлов с помощью модуля csv.

Обработка JSON

Сериализация и десериализация JSON.

Сопоставление регулярных выражений

Выполнение сопоставления регулярных выражений с помощью модуля re.

Обработка дат

Обработка дат и времени с помощью datetime.

Декораторы

Определение и использование декораторов.

Генераторы

Экономия памяти с помощью генераторов.

Менеджер контекста

Пользовательские менеджеры контекста.

Обработка исключений

Полный механизм обработки исключений.

Наследование классов

Наследование классов и переопределение методов.

Многопоточность

Реализация многопоточности с помощью модуля threading.

Многопроцессность

Достижение истинного параллелизма с помощью multiprocessing.

Асинхронное программирование asyncio

Реализация асинхронной конкурентности с помощью asyncio.

Сокетное программирование

TCP-сокет-сервер и клиент.

HTTP-запросы

Отправка HTTP-запросов с помощью библиотеки requests.

Операции с базой данных

Операции с базами данных с помощью sqlite3.

Виртуальное окружение

Создание и управление виртуальными окружениями Python.

Установка pip

Распространённые команды управления пакетами pip.

Переменные окружения

Чтение и установка переменных окружения.

Логирование

Настройка и использование модуля logging.

Модульное тестирование

Написание модульных тестов с помощью unittest.

Аннотации типов

Повышение читаемости кода с помощью аннотаций типов.

Dataclass

Упрощение определений классов с помощью dataclass.

Enum

Определение типов enum с помощью Enum.

Декоратор property

Управление доступом к атрибутам с помощью property.

Магические методы

Распространённые примеры магических методов.

Итератор

Реализация пользовательского итератора.

Корутина

Базовое использование корутин.

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.