Начало работы
Hello World и комментарии
Python использует # для комментариев и тройные кавычки для докстрок. Функция print() поддерживает параметры sep и end для настройки форматирования вывода. Докстроки служат документацией, доступной через help() и __doc__.
# 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 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+) — предпочтительный способ форматирования строк.
# 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). Длинные строки можно продолжать обратной косой чертой или автоматически внутри (), [], {}. Предпочитайте неявное продолжение для читаемости.
# 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.
# 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Переменные и типы данных
Переменные и динамическая типизация
Python использует динамическую типизацию — переменные могут менять тип во время выполнения. Используйте type() для проверки, isinstance() для верификации. Python 3.6+ поддерживает аннотации типов (name: str = 'Alice') для поддержки IDE без принуждения во время выполнения.
# 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.
# 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() использует банковское округление.
# 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). Используйте подчёркивания в числовых литералах для читаемости.
# 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 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__Строки
Строковые методы
Строки неизменяемы — методы возвращают новые строки. find() возвращает -1, если не найдено, тогда как index() вызывает ValueError. Используйте isalpha()/isdigit()/isalnum() для валидации. Класс str имеет более 40 методов — изучите через dir(str).
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, :, для разделителя тысяч. Избегайте %-форматирования в новом коде.
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] — идиоматичный способ перевернуть строку. Срезы безопасны: индексы вне диапазона возвращают пустые строки вместо ошибок.
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 части (до, разделитель, после).
# 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 заполняют до указанной ширины с опциональным символом заполнения.
# 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. Строки в тройных кавычках сохраняют переносы строк. Строки поддерживают оператор * для повторения и + для объединения.
# 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Числа и математика
Арифметические операторы
В Python 7 арифметических операторов. / всегда возвращает float, // — целочисленное деление (округляет в сторону минус бесконечности). ** — возведение в степень (не ^ — это XOR). Результат оператора % принимает знак делителя, в отличие от C/Java.
# 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.
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 для воспроизводимых результатов в тестировании.
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 sequenceDecimal и Fractions
Используйте Decimal для финансовых вычислений, где ошибки точности float недопустимы (например, деньги). Используйте Fraction для точной рациональной арифметики. Оба медленнее float, но избегают ошибок округления. Всегда создавайте Decimal из строк, а не из float.
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.
# 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)Структуры данных
Списки
Списки — самая универсальная структура данных Python — упорядоченные, изменяемые и гетерогенные. append() — O(1), insert(0, x) — O(n). Используйте collections.deque для быстрых операций на обоих концах. sort() на месте, sorted() возвращает новый список.
# 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Кортежи
Кортежи неизменяемы и быстрее списков. Используйте их для фиксированных коллекций, множественных возвращаемых значений и ключей словаря (списки не могут быть ключами). Именованные кортежи предоставляют имена полей для читаемости. Одноэлементные кортежи требуют завершающей запятой.
# 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 элегантно создаёт словари.
# 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 неизменяем и хешируем. Порядок не гарантируется.
# 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() для читаемости.
# 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) для списков). Они необходимы для чистого, эффективного кода.
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)])Управление потоком
If / Elif / Else
Python использует if/elif/else — обратите внимание 'elif', а не 'elseif'. Отступы определяют блоки. Тернарный оператор 'x if cond else y' — это выражение. Python рассматривает пустые коллекции, 0, None и False как ложные — полезно для лаконичных условий.
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() для итерации пар ключ-значение словаря.
# 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 заглушка для пустых блоков.
# 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' добавляют условия.
# 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()). После исчерпания они завершены. Используйте генераторы для больших/бесконечных последовательностей, конвейеров и потоковых данных.
# 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))Функции
Определение и вызов функций
Функции определяются с помощью def. Аргументы по умолчанию используют =. Python поддерживает именованные аргументы для ясности. Функции могут возвращать несколько значений (как кортеж). Докстроки (в тройных кавычках) документируют функции и доступны через help().
# 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.
# *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 kwargsLambda и функции высшего порядка
Lambda ограничены одним выражением — используйте def для сложной логики. Они блистают как аргументы функций высшего порядка вроде sorted(), map(), filter(). Однако генераторные списки часто более читаемы, чем map/filter. reduce() находится в functools.
# 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 для сохранения метаданных исходной функции (имя, докстрока).
# 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) для изменения переменной в объемлющей области видимости. Замыкания запоминают свою объемлющую область видимости.
# 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ООП и классы
Классы и объекты
Классы объединяют данные (атрибуты) и поведение (методы). __init__ — конструктор. self ссылается на экземпляр (как 'this' в других языках). Переменные класса общие; переменные экземпляра — для каждого объекта. __str__ для пользователей, __repr__ для разработчиков.
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().
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 превращает методы в атрибуты с геттерами/сеттерами, обеспечивая валидацию и вычисляемые свойства.
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 для утилит.
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 естественно.
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__Обработка ошибок
Try / Except / Finally
try/except/else/finally: try выполняет рискованный код, except ловит ошибки, else выполняется при отсутствии исключения, finally всегда выполняется (очистка). Ловите конкретные исключения, а не пустой 'except:'. Блок else полезен, когда очистка должна выполняться только при успехе. Exception — базовый для большинства ловимых ошибок.
# 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' связывает исключения, сохраняя исходную причину. Всегда возбуждайте конкретные типы исключений. Избегайте использования исключений для обычного потока управления.
# 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).
# 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.
# 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-коде — он поддерживает уровни, форматирование и направления вывода.
# 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Файловый ввод-вывод
Чтение файлов
Всегда используйте 'with' для открытия файлов — он автоматически закрывает их даже при ошибке. Указывайте encoding='utf-8' для избежания проблем с кодировкой, зависящей от платформы. Для больших файлов итерируйте построчно вместо read() для экономии памяти. readlines() загружает весь файл в память.
# 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() возвращает его позицию. Всегда указывайте кодировку для текстовых файлов.
# 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() рекурсивно ищет.
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 и других сложных объектов.
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 NoneCSV и другие форматы
Модуль csv обрабатывает CSV с правильным экранированием. Используйте newline='' при открытии CSV-файлов на Windows. DictReader/DictWriter работают с именами столбцов. pickle может сериализовать любой объект 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!Модули и пакеты
Импорт модулей
Импорты подключают модули. 'import X' сохраняет пространство имён чистым. 'from X import Y' удобно, но может вызвать коллизии имён. Псевдонимы (import X as Y) распространены для библиотек с соглашениями (np, pd). Избегайте 'from X import *' — это загрязняет пространство имён.
# 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.
# 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__'; при импорте — имя модуля. Этот паттерн необходим для создания переиспользуемых модулей, которые также могут выполняться автономно.
# 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/.
# 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.
# 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Дата и время
Модуль datetime
Модуль datetime предоставляет классы date, time, datetime и timedelta. datetime.now() возвращает локальное время; используйте datetime.now(timezone.utc) для UTC. weekday() возвращает 0-6 (Пн-Вс). Всегда используйте timezone-aware datetime в production для избежания неоднозначности.
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.
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() возвращает всю длительность в секундах.
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 автоматически.
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"))Регулярные выражения
Основы модуля re
re.search() находит первое совпадение где угодно; re.match() только в начале; re.fullmatch() требует совпадения всей строки. Используйте сырые строки (r'...') для шаблонов во избежание проблем с экранированием обратной косой черты. Объекты совпадения предоставляют group(), start(), end() и span().
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'...'), чтобы обратные косые черты были буквальными. Жадные кванторы совпадают максимально; добавьте ? для ленивых (например, *?).
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() для получения (результат, количество).
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 (?<=) совпадают без поглощения.
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)Асинхронность и конкурентность
Основы asyncio
asyncio — асинхронный I/O фреймворк Python. 'async def' определяет корутину; 'await' приостанавливает до готовности результата. asyncio.run() запускает цикл событий. asyncio.gather() выполняет корутины конкурентно. Корутины обеспечивают высококонкурентный I/O без потоков.
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 работы.
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. Потоки разделяют память; процессы — нет.
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.
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]Декораторы
Базовый декоратор
Декораторы обёртывают функцию для расширения или модификации поведения без изменения исходного кода. Синтаксис @ — синтаксический сахар для присваивания результата вызова декоратора обратно имени функции. Используйте *args, **kwargs в обёртке для работы с любой сигнатурой.
# 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) внутри декораторов для сохранения метаданных. Это почти универсальная лучшая практика.
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), который возвращает декоратор, который затем применяется к функции.
# 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__.
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 ООП.
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. Результат вложен как слои луковицы. Порядок важен — обращение меняет вложенность вывода.
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Генераторы и итераторы
Функции-генераторы (yield)
Генераторы лениво производят значения с помощью yield — они приостанавливают выполнение после каждого yield и возобновляются при вызове next(). Это делает их эффективными по памяти для больших или бесконечных последовательностей, поскольку только одно значение существует в памяти за раз. После исчерпания генератор нельзя использовать повторно.
# 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() или подачи в другие итераторы. Предпочитайте их генераторным спискам, когда не нужен случайный доступ.
# 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). Итерируемые объекты можно обходить в цикле; итераторы производят значения по одному. Для переиспользуемых итерируемых объектов разделяйте итерируемый (возвращает новый итератор) и итератор (хранит состояние).
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-фреймворков.
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 работают концептуально так же.
# 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' построен на той же концепции.
# 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.Менеджеры контекста
Основы оператора with
Оператор with гарантирует освобождение ресурсов (закрытие файлов, снятие блокировок, возврат подключений) даже при исключениях. Он вызывает __enter__ в начале и __exit__ в конце. Всегда предпочитайте 'with' ручному try/finally для управления ресурсами — это безопаснее и читабельнее.
# '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 подавляет его. Этот паттерн идеален для сложной настройки/очистки, например транзакций БД.
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.1234scontextlib.contextmanager
contextlib.contextmanager превращает функцию-генератор в менеджер контекста — код до yield это __enter__, код после yield (в finally) это __exit__. Э то лаконичнее класса для простых случаев. Yield значение для передачи в переменную 'as'. Используйте try/finally для гарантии очистки.
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 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 перехватывают вывод, который иначе шёл бы в консоль, полезно для тестирования или логирования. Эти утилиты избегают шаблонности и делают намерение явным.
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 или исключении внутри блока.
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...Аннотации типов
Базовые аннотации переменных и функций
Аннотации типов документируют ожидаемые типы, но НЕ принуждаются во время выполнения — Python остаётся динамически типизированным. Используйте статический чекер вроде mypy или pyright для поиска ошибок типов до выполнения. Встроенные дженерики (list[str], dict[str, int]) требуют Python 3.9+; старые версии нуждаются в typing.List, typing.Dict.
# 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.
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 для сужения типов и поиска недопустимых аргументов при проверке.
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] как базу для параметризованных контейнеров.
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 errorCallable, псевдонимы типов и Protocol
Callable[[int, str], bool] описывает функцию, принимающую int и str, возвращающую bool. Псевдонимы типов дают описательные имена сложным типам. Protocol обеспечивает структурную (duck) типизацию — любой объект с нужными методами удовлетворяет протоколу, без наследования. Это ответ 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 для новых проектов для всеобъемлющих аннотаций.
# 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Классы данных
Базовый @dataclass
@dataclass автоматически генерирует __init__, __repr__ и __eq__ на основе аннотированных полей — устраняет шаблонность для классов, хранящих данные. Идеально для value-объектов, конфигов, DTO и записей. Доступно с Python 3.7. Поля должны иметь аннотации типов; аннотация определяет поле.
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) можно назначать напрямую.
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-типов вроде координат, цветов или версий.
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 и проверок равенства.
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+) предотвращает добавление произвольных атрибутов и значительно снижает память на экземпляр — идеально для миллионов маленьких объектов.
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 instancesCollections и itertools
namedtuple
namedtuple создаёт подклассы кортежа с именованными полями — такие же эффективные по памяти, как кортежи, но гораздо читабельнее. Они неизменяемы, используйте _replace() для создания изменённых копий. Предпочитайте typing.NamedTuple для нового кода, так как поддерживает аннотации типов и значения по умолчанию. Отлично для возврата нескольких значений из функций.
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.0Counter
Counter — подкласс dict для подсчёта хешируемых объектов — идеально для частотного анализа, гистограмм и голосования. most_common(n) возвращает топ-n элементов. Отсутствующие ключи возвращают 0 вместо KeyError. Counter поддерживает +, -, &, | для set-подобной арифметики счётчиков.
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'. Фабрика вызывается только при отсутствии ключа, а не при каждом доступе.
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-кэшей.
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() для просмотра.
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 — ленивые альтернативы срезам и фильтрации, работающие с любым итератором, включая бесконечные.
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 вычисляет один раз и кэширует на экземпляре.
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)]Обработка 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-ключи становятся строками.
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-сериализуемы по умолчанию.
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 сериализуют объекты моделей.
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 для автоопределения разделителей.
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 кавычит всё, полезно для строгих парсеров.
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-пайплайнов.
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Логирование и тестирование
Основы logging
Модуль logging — стандартный способ вывода диагностической информации — гораздо лучше print(), потому что вы контролируете уровни, форматы и направления. Используйте logging.getLogger(__name__) для каждого модуля, чтобы настраивать детализацию по модулю. logging.exception() автоматически включает traceback. Настройте basicConfig один раз при запуске.
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 ротирует по времени, а не по размеру.
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.
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 для подробного вывода.
# 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' (по умолчанию) один раз на тест.
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 sessionpytest parametrize и mocking
parametrize запускает одну тестовую функцию на нескольких наборах входных данных — устраняет копирование-вставку тестового кода и даёт чёткий вывод по каждому случаю. unittest.mock.patch заменяет функции/объекты на mock для изолированного тестирования. assert_called_once_with проверяет, что mock использовался корректно. @pytest.mark.skip и xfail элегантно обрабатывают незавершённые тесты.
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Сетевое программирование
TCP-сервер
Создаёт TCP-сервер с использованием модуля socket. bind связывает сокет с адресом, listen устанавливает очередь backlog, accept блокирует до подключения клиента. Всегда закрывайте подключения для освобождения файловых дескрипторов.
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 для преобразования строка-байты.
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, игр и потокового вещания в реальном времени.
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.
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 для гарантии очистки.
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()База данных (SQLite)
Создание таблицы
sqlite3 встроен в Python. connect создаёт или открывает файл базы данных. CREATE TABLE IF NOT EXISTS предотвращает ошибки, если таблица существует. Всегда вызывайте commit для сохранения изменений.
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.
cursor.execute(
'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
('Alice', '[email protected]', 30))
conn.commit()
print(f"ID: {cursor.lastrowid}")Запрос данных
fetchall возвращает все совпадающие строки как список кортежей. fetchone возвращает одну строку или None. Для больших результатов итерируйте по курсору напрямую.
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 сохраняет изменения.
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 позволяет обращаться к столбцам по имени.
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'])Веб-скрапинг
Основы BeautifulSoup
requests получает HTML-содержимое, BeautifulSoup парсит его. html.parser встроенный; lxml быстрее. Всегда проверяйте response.status_code перед парсингом.
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-селекторы для сложных запросов.
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 принимает список имён тегов.
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 между запросами.
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 обрабатывает специальные символы.
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)Аси нхронный веб (aiohttp)
HTTP-клиент
aiohttp предоставляет асинхронный HTTP. ClientSession управляет пулом подключений. async with гарантирует очистку. asyncio.run выполняет корутину.
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 для ограничения конкурентности.
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 извлекает параметры пути.
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 итерирует по сообщениям.
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 между запросами. Необходимо для аутентифицированного скрапинга. Используйте одну сессию для всех запросов.
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()Многопроцессорность (углублённо)
Пул процессов
Pool управляет рабочими процессами. map распределяет работу параллельно. apply_async запускает одну функцию асинхронно. Всегда используйте защиту if __name__ == main на Windows.
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 для синхронизации доступа и предотвращения гонок.
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 безопасна для процессов, обрабатывая блокировку внутренне.
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 связи.
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.
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()Виртуальные окружения (углублённо)
Модуль venv
venv создаёт изолированные окружения Python с собственными каталогами пакетов. Активация модифицирует PATH. Всегда активируйте перед установкой зависимостей.
# Create
python -m venv myenv
# Activate (Linux/Mac)
source myenv/bin/activate
# Activate (Windows)
myenv\Scripts\activate
# Deactivate
deactivaterequirements.txt
requirements.txt перечисляет зависимости проекта. == закрепляет точные версии, >= разрешает обновления в диапаз оне. Всегда коммитьте в систему контроля версий.
# Generate
pip freeze > requirements.txt
# Install
pip install -r requirements.txt
# Pin versions
flask==2.3.3
requests>=2.28.0,<3.0.0Poetry
Poetry — современный менеджер зависимостей. pyproject.toml заменяет requirements.txt. Виртуальные окружения управляются автоматически.
# Initialize
poetry init
# Add dependency
poetry add flask
poetry add pytest --group dev
# Install all
poetry install
# Run command
poetry run python app.pypipenv
pipenv объединяет pip и virtualenv. Pipfile объявляет зависимости, Pipfile.lock закрепляет точные версии. --dev разделяет development-зависимости.
# 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 с бинарными зависимостями.
# 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Продвинутое использование pip
Установка из Git
Устанавливайте пакеты напрямую из Git-репозиториев. Полезно для невыпущенных версий, форков или приватных пакетов. @branch или @commit закрепляет конкретную версию.
# 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) связывает пакет вместо копирования. Изменения сразу доступны без переустановки. Необходимо для разработки пакетов.
# Install in development mode
pip install -e .
# From a specific path
pip install -e /path/to/package
# With extras
pip install -e ".[dev,test]"Ограничения и хеши
Ограничения ограничивают, какие версии можно установить. Проверка хешей верифицирует целостность пакетов, предотвращая атаки на цепочку поставок.
# 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 освобождает место на диске, когда кэш слишком разрастается.
# 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 для внутренних реестров.
# 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Проверка типов (mypy)
Базовые аннотации типов
Аннотации типов аннотируют параметры функций и типы возврата. Python 3.9+ разрешает встроенные типы напрямую. Аннотации включают статический анализ с mypy.
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 проверяет, что все пути кода обрабатывают все типы.
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 NoneGeneric-типы
Дженерики создают переиспользуемые type-safe контейнеры. TypeVar определяет переменную типа, Generic делает класс generic. mypy обеспечивает согласованность типов.
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 с проверкой типов). Любой класс с требуемыми методами удовлетворяет протоколу, наследование не нужно.
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-кода.
# mypy.ini
[mypy]
python_version = 3.11
strict = True
warn_return_any = True
disallow_untyped_defs = True
[mypy-tests.*]
ignore_errors = TrueСоветы по производительности
Список против генератора
Списки хранят все элементы в памяти; генераторы производят значения по требованию. Используйте генераторы для больших последовательностей, итерируемых один раз.
# 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-строки — самый быстрый метод интерполяции.
# 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}!"Локальные переменные
Поиск локальных переменных быстрее поиска глобальных или атрибутов. Назначение часто используемых функций в локальные ускоряет циклы.
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% памяти на экземпляр. Значительно при создании миллионов объектов. Нельзя добавить неуказанные атрибуты.
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
# p.z = 3 # AttributeErrortimeit и cProfile
timeit измеряет время выполнения маленьких сниппетов. cProfile показывает, куда уходит время. Используйте профилирование перед оптимизацией для поиска реальных узких мест.
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))')Распространённые подводные камни
Изменяемые аргументы по умолчанию
Значения аргументов по умолчанию вычисляютс я один раз при определении. Изменяемые значения по умолчанию разделяются между всеми вызовами. Всегда используйте None как значение по умолчанию.
# 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Позднее связывание замыканий
Замыкания захватывают переменные по ссылке. К моменту вызова лямбд переменная цикла имеет финальное значение. Аргументы по умолчанию захватывают текущее значение.
# 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.
# 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) # Trueis против ==
is проверяет, указывают ли две ссылки на один объект. == проверяет, имеют ли два объекта одинаковое значение. Используйте is только для None, True, False.
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 параллелизма.
# 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.
Магические методы
Распространённые примеры магических методов.
Итератор
Реализация пользовательского итератора.
Корутина
Базовое использование корутин.
Было ли это полезно?