Python для начинающих
1.24K subscribers
546 photos
3 videos
232 files
74 links
Python для начинающих
Download Telegram
Как задать формат вывода дат с помощью strftime
Как укротить дату в Python: форматирование с strftime

У datetime в Python есть суперспособность — превращать «сырую» дату в аккуратную строку нужного формата. Делает он это с помощью метода strftime. Запомните: string format time.

Начнем с базы:

from datetime import datetime

now = datetime.now()
formatted = now.strftime("%Y-%m-%d %H:%M:%S")
print(formatted) # например: 2026-04-09 14:37:12


strftime принимает строку-шаблон, где специальные %-коды заменяются на кусочки даты и времени.

### Самые полезные коды формата

Вот минимальный «набор выживания»:

- %Y — год полностью, 2026
- %y — год из двух цифр, 26
- %m — месяц, 0112
- %d — день месяца, 0131
- %H — часы (24-часовой формат), 0023
- %M — минуты, 0059
- %S — секунды, 0059

Комбинируя их, можно получить любой стиль:

from datetime import datetime

now = datetime.now()

iso_style = now.strftime("%Y-%m-%d")
europe_style = now.strftime("%d.%m.%Y")
us_style = now.strftime("%m/%d/%Y")
time_only = now.strftime("%H:%M")

print(iso_style)
print(europe_style)
print(us_style)
print(time_only)


### День недели и месяц словами

Если нужно что-то «человечнее», вроде «Thu, April 09, 2026»:

- %a — краткое название дня недели (Mon)
- %A — полное название дня недели (Monday)
- %b — краткое название месяца (Apr)
- %B — полное название месяца (April)

from datetime import datetime

now = datetime.now()

pretty = now.strftime("%A, %d %B %Y, %H:%M")
log_style = now.strftime("[%Y-%m-%d %H:%M:%S]")

print(pretty)
print(log_style)


### Формат для логов и файлов

Частая задача — сделать «безопасное» имя файла с датой внутри (никаких пробелов и двоеточий):

from datetime import datetime

now = datetime.now()

filename = now.strftime("backup_%Y%m%d_%H%M%S.zip")
print(filename) # например: backup_20260409_143712.zip


### Маленький лайфхак

Если увидели где-то странную строку вида 2026-04-09T14:37:12 — это почти ISO 8601. Такое легко получить:

from datetime import datetime

now = datetime.now()
iso_like = now.strftime("%Y-%m-%dT%H:%M:%S")
print(iso_like)


strftime — это ваш личный дизайнер дат. Один и тот же объект datetime, но десятки разных представлений — под логи, интерфейсы, имена файлов и отчеты. Главное — выучить несколько кодов, а остальное легко комбинировать как конструктор.
👍5🔥2
Использование enum для создания понятных значений
Python для начинающих: как Enum делает код понятнее

У каждого начинающего питониста в какой‑то момент в коде появляются «магические» значения:

status = "ok"   # что значит?
role = 1 # а это?


Через пару недель вы сами не вспомните, что такое 1 и чем "ok" отличается от "ready". Для таких случаев в Python есть модуль enum, который превращает разрозненные значения в понятные, самодокументирующиеся константы.

---

## Что такое Enum?

Enum (перечисление) — это набор именованных значений. Вместо голых чисел и строк вы используете осмысленные имена:

from enum import Enum

class OrderStatus(Enum):
CREATED = "created"
PAID = "paid"
SHIPPED = "shipped"
CANCELED = "canceled"


Теперь:

status = OrderStatus.PAID

if status is OrderStatus.PAID:
print("Send receipt")


Сразу видно, какие вообще существуют статусы и какие из них допустимы.

---

## Почему это лучше, чем строки и числа?

1. Меньше опечаток

# Плохо
if status == "padi": # незаметная ошибка
...

# Хорошо
if status is OrderStatus.PAID:
...


При опечатке Python сразу ругнется: OrderStatus.PADI просто не существует.

2. Гарантия допустимых значений

def change_status(status: OrderStatus):
print("New status:", status)

change_status(OrderStatus.SHIPPED) # ок
change_status("lost") # логическая ошибка


С Enum становится очевидно, что "lost" — лишний статус.

3. Самодокументируемость

Вместо status = 3status = OrderStatus.CANCELED. Смысл читается без комментариев.

---

## Числовые Enum: пример с правами доступа

Иногда удобно использовать числа — например, для уровней доступа:

from enum import IntEnum, auto

class AccessLevel(IntEnum):
GUEST = 1
USER = 2
ADMIN = 3

def can_delete_posts(level: AccessLevel) -> bool:
return level >= AccessLevel.ADMIN

print(can_delete_posts(AccessLevel.USER)) # False
print(can_delete_posts(AccessLevel.ADMIN)) # True


IntEnum ведет себя как int, можно сравнивать уровни, сортировать и т.д.

---

## Enum и словари

Частый паттерн — использовать Enum как ключи конфигураций:

from enum import Enum

class Env(Enum):
DEV = "dev"
STAGE = "stage"
PROD = "prod"

config = {
Env.DEV: {"debug": True, "db": "sqlite:///:memory:"},
Env.PROD: {"debug": False, "db": "postgres://prod-db"},
}

current_env = Env.DEV
db_url = config[current_env]["db"]
print(db_url)


Так вы не перепутаете "dev" и "prod" и сразу видите все возможные окружения.

---

## Вывод

Enum — это простой способ навести порядок в коде:

- заменяет «магические» числа и строки;
- делает список допустимых значений явным;
- уменьшает количество скрытых ошибок и опечаток;
- улучшает читаемость без дополнительных комментариев.

Как только у вас появляются повторяющиеся значения с ограниченным набором вариантов — статусы, роли, режимы, окружения — это сигнал: здесь пора использовать Enum.
👍4
Работа с ограничением доступа с сессионной памятью shelve
### Python для начинающих: доступ по паролю с помощью shelve

Представьте: вы пишете небольшой консольный “личный дневник” или мини‑админку для заметок. Хранить данные в чистом виде в файле не хочется, база данных — слишком тяжело. А ещё нужен простой механизм ограничения доступа по логину и паролю.

Для таких задач отлично подходит модуль shelve — встроенное “полу‑хранилище, полу‑словарь” на диске.

---

## Что такое shelve

shelve позволяет хранить произвольные Python‑объекты в файле, обращаясь к ним как к словарю:

- ключи — строки;
- значения — любые сериализуемые объекты (списки, словари, классы и т.п.);
- данные автоматически сохраняются на диск.

Минимальный пример:

import shelve

with shelve.open("session_db") as db:
db["user1"] = {"name": "Alice", "role": "admin"}

with shelve.open("session_db") as db:
print(db["user1"])


---

## Простейшая система доступа с сессиями

Сессия — это запись о том, что пользователь успешно вошёл, плюс немного данных о нём.

### 1. Регистрация пользователя

import shelve
import hashlib

def hash_password(password: str) -> str:
return hashlib.sha256(password.encode("utf-8")).hexdigest()

def register_user(username: str, password: str) -> None:
with shelve.open("users_db") as users:
if username in users:
raise ValueError("User already exists")
users[username] = {
"password_hash": hash_password(password),
"role": "user",
}


### 2. Логин и создание сессии

import os
import time

def create_session(username: str) -> str:
session_id = os.urandom(16).hex()
with shelve.open("sessions_db") as sessions:
sessions[session_id] = {
"username": username,
"created_at": time.time(),
}
return session_id

def login(username: str, password: str) -> str | None:
with shelve.open("users_db") as users:
user = users.get(username)
if not user:
return None
if user["password_hash"] != hash_password(password):
return None
return create_session(username)


session_id можно, например, хранить в файле, окружении, передавать параметром и т.п.

---

## Проверка доступа по сессии

Добавим проверку, что у пользователя есть действительная сессия:

SESSION_TTL = 3600  # 1 hour

def get_current_user(session_id: str) -> dict | None:
with shelve.open("sessions_db", writeback=True) as sessions:
session = sessions.get(session_id)
if not session:
return None
is_expired = time.time() - session["created_at"] > SESSION_TTL
if is_expired:
del sessions[session_id]
return None

with shelve.open("users_db") as users:
return users.get(session["username"])


Теперь можно ограничивать доступ к действиям:

def protected_action(session_id: str) -> None:
user = get_current_user(session_id)
if not user:
print("Access denied")
return
print(f"Welcome, {user['role']}!")


---

## Когда shelve — удачный выбор

- небольшие консольные утилиты;
- учебные проекты;
- прототипы, где не хочется поднимать СУБД.

Важно: shelve не рассчитан на высокую нагрузку и многопоточность, но для “домашних” Python‑скриптов это простой и удобный способ реализовать хранение пользователей и сессий без лишних зависимостей.
🔥3
Применение regular expressions для валидации email адреса
Применение regular expressions для валидации email адреса

Валидация email — классическая задача, с которой рано или поздно сталкивается почти каждый начинающий питонист. Можно проверять строку “на глаз”, разбирать её по @ и точкам, но гораздо мощнее и гибче использовать регулярные выражения.

### Почему именно regular expressions?

Регулярки позволяют описать формат email одной строкой-правилом:

- есть одна и только одна @
- до @ — допустимые символы (буквы, цифры, _ . + -)
- после @ — домен: буквы, цифры, дефис, точки
- в конце — доменная зона: минимум 2 буквы (.com, .ru, .info)

### Базовый пример

import re

EMAIL_PATTERN = re.compile(
r"^[a-zA-Z0-9_.+-]+" # локальная часть
r"@" # символ @
r"[a-zA-Z0-9-]+" # домен
r"(\.[a-zA-Z0-9-]+)*" # поддомены
r"\.[a-zA-Z]{2,}$" # доменная зона
)

def is_valid_email(email: str) -> bool:
return EMAIL_PATTERN.match(email) is not None

test_emails = [
"user@example.com",
"user.name+tag@gmail.com",
"bad@@example.com",
"no-domain@",
"admin@mail-server.co.uk",
]

for addr in test_emails:
print(addr, "=>", is_valid_email(addr))


Здесь EMAIL_PATTERN компилируется один раз, а потом много раз переиспользуется — так быстрее, чем вызывать re.match со строкой-шаблоном каждый раз.

### Валидация списка и фильтрация

Представим, что у нас есть “сырые” данные, и нужно оставить только корректные адреса:

def filter_valid_emails(emails: list[str]) -> list[str]:
return [e for e in emails if is_valid_email(e)]

raw_emails = [
" boss@company.com ",
"invalid@domain",
"user@sub.domain.org",
"no-at-sign.com",
]

clean_emails = [e.strip() for e in raw_emails]
valid_emails = filter_valid_emails(clean_emails)

print(valid_emails)


Комбинация strip() + регулярка даёт простой, но рабочий пайплайн очистки.

### На что обратить внимание

1. Идеальной регулярки для email не существует. Полный стандарт RFC очень сложный; в продакшене нередко используют библиотечные валидаторы или отправку тестового письма.
2. Регулярка — предварительный фильтр. Она отсекает заведомо неверные строки, но не гарантирует, что почтовый ящик существует.
3. Не переусложняйте. Для большинства веб-форм достаточно аккуратного, но не “RFC-идеального” шаблона — вроде того, что выше.

Регулярные выражения в Python — это инструмент, который позволяет превратить разрозненные проверки в одно чёткое правило. Освоив их на простом примере с email, дальше проще разбирать и более сложные шаблоны.
🔥3👍1
Как использовать warnings для генерации пользовательских предупреждений
### Как использовать warnings для генерации пользовательских предупреждений

В Python многие разработчики знают про исключения, но гораздо реже используют предупреждения. А зря: модуль warnings позволяет мягко сообщить пользователю о проблеме, не ломая выполнение программы.

Предупреждение — это сигнал: “что‑то не так, но мы еще можем продолжать”.

---

## Базовый пример: ваше первое предупреждение

import warnings

def divide(a, b):
if b == 0:
warnings.warn("Division by zero replaced with 0", RuntimeWarning)
return 0
return a / b

print(divide(10, 0))


Здесь вместо аварийного ZeroDivisionError мы возвращаем безопасное значение и генерируем предупреждение. Тип RuntimeWarning помогает понять характер проблемы.

---

## Свой класс предупреждений

Как и с исключениями, вы можете создавать свои типы:

import warnings

class ConfigWarning(UserWarning):
pass

def load_config(config):
if "timeout" not in config:
warnings.warn("Missing 'timeout', using default = 30", ConfigWarning)
return {**config, "timeout": 30}
return config

cfg = load_config({"host": "example.com"})


Наследуемся от UserWarning — это стандартный базовый класс для пользовательских предупреждений. Так их легко отфильтровать или обработать отдельно.

---

## Управление показом предупреждений

Иногда предупреждения слишком шумные. warnings позволяет управлять их поведением:

import warnings

warnings.filterwarnings(
"ignore", # действие: "ignore", "default", "error", "once", ...
category=ConfigWarning # фильтрация по типу
)


Теперь все ConfigWarning будут скрыты. А так можно превратить предупреждение в исключение — полезно для тестов:

warnings.filterwarnings("error", category=ConfigWarning)


Теперь любое ConfigWarning рухнет как ошибка.

---

## Локальное подавление предупреждений

Если “шумит” только конкретный участок кода:

import warnings

with warnings.catch_warnings():
warnings.simplefilter("ignore", category=ConfigWarning)
load_config({"host": "example.com"}) # тут предупреждений не будет


Вне with поведение возвращается к обычному.

---

## Когда использовать предупреждения, а не исключения

Предупреждения особенно полезны, когда:

- используется устаревший параметр, но вы пока его поддерживаете;
- подставляется значение по умолчанию вместо отсутствующего;
- результат “подозрительно”, но еще пригоден;
- вы готовите пользователей к будущим изменениям API.

warnings — отличный инструмент “вежливой строгости”: код работает, но пользователь заранее узнает, где его может ждать сюрприз.
👍3
Создание простого API с модулем BaseHTTPServer (Python 2) или http.server (Python 3)
Создание простого API с BaseHTTPServer / http.server

Когда слышишь слово «API», кажется, что сейчас понадобятся Django, Flask и гора магии. Но под капотом всё начинается с очень простой идеи: принять HTTP‑запрос и вернуть HTTP‑ответ. В стандартной библиотеке Python уже есть всё, чтобы сделать мини‑API буквально в несколько строк.

Ниже — современный вариант на Python 3 с модулем http.server. Для Python 2 можно использовать те же идеи, просто заменить импорты.

### Мини‑сервер на http.server

Сделаем API, у которого есть два маршрута:

- GET / — вернёт простое сообщение
- GET /api/time — вернёт текущее время в JSON

from http.server import HTTPServer, BaseHTTPRequestHandler
import json
from datetime import datetime


class SimpleAPIHandler(BaseHTTPRequestHandler):
def _set_headers(self, status=200, content_type="application/json"):
self.send_response(status)
self.send_header("Content-Type", content_type)
self.end_headers()

def do_GET(self):
if self.path == "/":
self._set_headers(content_type="text/plain; charset=utf-8")
self.wfile.write(b"Welcome to Simple API")
elif self.path == "/api/time":
self._set_headers()
data = {"time": datetime.utcnow().isoformat() + "Z"}
self.wfile.write(json.dumps(data).encode("utf-8"))
else:
self._set_headers(status=404)
data = {"error": "Not found"}
self.wfile.write(json.dumps(data).encode("utf-8"))


def run_server(host="127.0.0.1", port=8000):
server_address = (host, port)
httpd = HTTPServer(server_address, SimpleAPIHandler)
print(f"Serving on http://{host}:{port}")
httpd.serve_forever()


if __name__ == "__main__":
run_server()


Запусти этот скрипт и проверь:

- http://127.0.0.1:8000/
- http://127.0.0.1:8000/api/time

или из терминала:

curl http://127.0.0.1:8000/api/time


### А как в Python 2?

Логика та же, только модуль другой:

from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler


и без encode("utf-8"), там всё байтовое по умолчанию. Но лучше всё‑таки ориентироваться на Python 3 — Python 2 уже давно снят с поддержки.

### Что полезно заметить

- BaseHTTPRequestHandler даёт тебе полный контроль над протоколом — идеально, чтобы понять, как на самом деле работает HTTP.
- Метод do_GET вызывается для запросов GET. Аналогично можно реализовать do_POST, do_PUT, do_DELETE.
- self.path — это путь из URL, на нём легко построить свою мини‑маршрутизацию без фреймворков.

Такой «ручной» подход не заменяет Flask или FastAPI, но отлично прокачивает понимание основ и помогает быстро накидать простое внутреннее API или заглушку для тестов.
🔥31👍1
Обработка ошибок ввода пользователя с try / except
Python для начинающих: приручаем ошибки ввода с помощью try / except

Любая программа, в которую что‑то вводит человек, рано или поздно ломается. Пользователь вместо числа вводит "пять", вместо пути к файлу — пустую строку, а вместо выбора меню — пробел. Если не обработать такие ситуации, программа падает с некрасивым трейсбеком.

В Python для этого есть связка try / except. Идея проста:
«Попробуй выполнить этот код. Если случится ошибка — поймай её и сделай что‑нибудь разумное».

---

### Базовый пример: просим число у пользователя

while True:
user_input = input("Enter an integer: ")
try:
value = int(user_input)
break
except ValueError:
print("This is not a valid integer, try again.")

print("You entered:", value)


Что здесь происходит:

- try: пытаемся преобразовать строку в int.
- except ValueError: ловим конкретную ошибку, когда строка не похожа на число.
- Цикл повторяется, пока пользователь не введет корректное значение.

Такой подход спасает от падения программы и делает поведение предсказуемым.

---

### Несколько исключений и «план Б» по умолчанию

Иногда полезно различать разные типы ошибок:

def safe_division(a, b):
try:
return a / b
except ZeroDivisionError:
print("Division by zero is not allowed.")
return None
except TypeError:
print("Both arguments must be numbers.")
return None

print(safe_division(10, 0))
print(safe_division(10, "x"))


- Мы обрабатываем две разные ошибки по‑разному.
- Вместо аварийного завершения возвращаем None и осмысленное сообщение.

---

### Добавляем else и finally

else выполняется, если в try не было ошибок.
finally выполняется всегда — даже при ошибке или return.

def read_int(prompt):
while True:
user_input = input(prompt)
try:
value = int(user_input)
except ValueError:
print("Invalid number, try again.")
else:
print("Thanks, got a valid number.")
return value
finally:
# Можно использовать для логирования, очистки ресурсов и т.п.
pass


---

### Практический мини‑паттерн: «надёжный ввод с попытками»

def ask_int_limited(prompt, attempts=3):
for _ in range(attempts):
try:
return int(input(prompt))
except ValueError:
print("Please enter a valid integer.")
raise ValueError("Too many invalid attempts.")

age = ask_int_limited("Enter your age: ")
print("Age is:", age)


Здесь мы:

- ограничиваем количество попыток,
- при превышении — явно выбрасываем исключение с понятным текстом.

---

Обработка ошибок с try / except — это не «костыль», а нормальная архитектура. Она превращает хрупкие скрипты в программы, с которыми можно работать без страха, что любой неправильный ввод всё сломает.
👍2
Сериализация и десериализация данных с pickle
Сериализация с pickle: сохраняем объекты Python “в банку”

Иногда хочется просто “заморозить” объект Python: сложный словарь, результат обучения модели, кэш вычислений — а потом в другом месте и в другое время “разморозить” его и продолжить работу. Для этого есть модуль pickle.

Сериализация — это превращение объекта в последовательность байт. Десериализация — обратный процесс: из байтов снова получаем объект.

pickle умеет сохранять почти всё: списки, словари, классы, функции (с нюансами), вложенные структуры. Минус — формат бинарный и специфичный для Python, то есть:
- файлы непонятны другим языкам;
- нельзя бездумно грузить непроверенный pickle-файл — это небезопасно.

---

### Базовый пример: сохранить и загрузить объект

import pickle

data = {
"users": ["alice", "bob"],
"settings": {"theme": "dark", "volume": 80},
"active": True
}

# сериализация в файл
with open("data.pkl", "wb") as f:
pickle.dump(data, f)

# десериализация из файла
with open("data.pkl", "rb") as f:
loaded_data = pickle.load(f)

print(loaded_data)


dump/load работают с файлами. Обрати внимание на режим: "wb" и "rb" — бинарный.

---

### Работа в памяти: dumps и loads

Если нужно получить байты, например, чтобы отправить по сети или положить в базу:

import pickle

config = {"timeout": 10, "retries": 3}

serialized = pickle.dumps(config) # bytes
print(type(serialized), len(serialized))

restored = pickle.loads(serialized)
print(restored)


---

### Сериализация собственных классов

pickle умеет сохранять экземпляры пользовательских классов (если они определены на верхнем уровне модуля):

import pickle

class Player:
def __init__(self, name, score):
self.name = name
self.score = score

player = Player("Alice", 42)

with open("player.pkl", "wb") as f:
pickle.dump(player, f)

with open("player.pkl", "rb") as f:
loaded_player = pickle.load(f)

print(loaded_player.name, loaded_player.score)


Важно: при загрузке класс Player должен быть доступен (импортирован) с тем же именем и в том же модуле.

---

### Вопрос безопасности

pickle.load может выполнить произвольный код, если файл злонамеренно подделан. Поэтому правило простое и жесткое:

Никогда не загружай pickle-файлы из непроверенных источников.

Для обмена данными между разными системами чаще выбирают JSON. Но когда нужно быстро и удобно сохранять сложные Python-объекты “для себя” — pickle это мощный и гибкий инструмент.
👍3
Работа с логами: запись и фильтрация с модулем logging
Работа с логами: запись и фильтрация с модулем logging

В любой программе рано или поздно наступает момент, когда print() перестает помогать. Нужно понимать, что происходит “под капотом”, но не засорять консоль сотней строк. Здесь на сцену выходит модуль logging.

### Базовая настройка логов

Начнем с простого: запишем логи в файл и выведем их в консоль.

import logging

logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
handlers=[
logging.FileHandler("app.log", encoding="utf-8"),
logging.StreamHandler()
]
)

logger = logging.getLogger("app")

logger.debug("Debug message") # не увидим, уровень ниже INFO
logger.info("App started")
logger.warning("Low disk space")
logger.error("Unexpected error")


Ключевые идеи:
- level — минимальный уровень сообщений (DEBUG, INFO, WARNING, ERROR, CRITICAL).
- handlers — куда отправлять логи (файл, консоль, сеть и т.п.).
- format — шаблон строки лога: время, уровень, имя логгера, сообщение.

### Логи по модулям

Для реальных приложений полезно иметь отдельный логгер на модуль:

# module_a.py
import logging
logger = logging.getLogger("module_a")

def run_task():
logger.info("Task started")
logger.debug("Internal details...")


# main.py
import logging
import module_a

logging.basicConfig(level=logging.DEBUG)
module_a.run_task()


Теперь ты можешь тонко управлять шумом:

logging.getLogger("module_a").setLevel(logging.WARNING)


Весь проект логируется детально, но module_a пишет только предупреждения и ошибки.

### Фильтрация логов

Иногда нужен не только уровень, но и умный фильтр. Например, логировать только запросы к критичным endpoint’ам.

import logging

class EndpointFilter(logging.Filter):
def filter(self, record):
return "/payments" in record.getMessage()

logger = logging.getLogger("api")
logger.setLevel(logging.INFO)

handler = logging.FileHandler("payments.log", encoding="utf-8")
handler.addFilter(EndpointFilter())
logger.addHandler(handler)

logger.info("GET /status 200")
logger.info("POST /payments 500") # только это уйдет в payments.log


Фильтры позволяют:
- разделять логи по файлам по любому условию;
- временно “выключать” шумный функционал;
- собирать отдельные журналы для аудита.

### Логирование исключений

Еще один must-have — логировать трассировку стека:

import logging

logging.basicConfig(level=logging.ERROR)
logger = logging.getLogger("errors")

try:
1 / 0
except ZeroDivisionError:
logger.exception("Calculation failed")


logger.exception автоматически добавит traceback к сообщению — незаменимо при отладке.

---

Модуль logging — это ваш “черный ящик” приложения. Настроив уровни, обработчики и фильтры, вы получаете полную картину происходящего без тонны print() и хаоса в консоли.
👍21🔥1
Введение в модуль bisect для работы с отсортированными списками
Введение в модуль bisect: быстрые операции с отсортированными списками

Когда вы работаете с отсортированным списком, вставка новых элементов может испортить порядок — приходится либо искать позицию вручную, либо каждый раз вызывать sort(). Модуль bisect решает эту проблему: он помогает искать позицию для элемента в отсортированном списке и вставлять его так, чтобы порядок сохранился.

---

## Основные функции bisect

Импортируем модуль:

import bisect


Четыре ключевые функции:

- bisect_left(a, x) — найти позицию для вставки x слева (перед равными элементами)
- bisect_right(a, x) или просто bisect.bisect(a, x) — позиция для вставки справа (после равных)
- insort_left(a, x) — вставить x слева и сохранить сортировку
- insort_right(a, x) или insort(a, x) — вставить x справа

---

## Поиск позиции без ручных циклов

import bisect

scores = [10, 20, 20, 30, 40]

pos_left = bisect.bisect_left(scores, 20)
pos_right = bisect.bisect_right(scores, 20)

print(pos_left) # 1 -> перед первой 20
print(pos_right) # 3 -> после последней 20


Так вы мгновенно узнаете, куда встанет элемент, не перебирая список вручную.

---

## Вставка в отсортированный список

Вставляем и сразу поддерживаем порядок:

import bisect

scores = [10, 20, 30, 40]

bisect.insort(scores, 25)
bisect.insort(scores, 30)

print(scores) # [10, 20, 25, 30, 30, 40]


insort делает insert + бинарный поиск под капотом. Для вас это одна строка — и список по-прежнему отсортирован.

---

## Реальный пример: поиск "позиции в рейтинге"

Допустим, у вас есть отсортированный список результатов, и вы хотите узнать, каким по счету будет новый результат:

import bisect

high_scores = [100, 90, 80, 70, 60]
high_scores.sort(reverse=True) # гарантируем порядок по убыванию

# Для работы с bisect развернем по возрастанию
asc_scores = sorted(high_scores)

new_score = 85
pos = len(asc_scores) - bisect.bisect_left(asc_scores, new_score)

print(f"New score rank: {pos}") # позиция в рейтинге по убыванию


---

Модуль bisect — это маленький, но очень полезный инструмент, когда вам нужно много раз искать и вставлять элементы в отсортированный список без лишних затрат и переписывания алгоритмов бинарного поиска вручную.
🔥2