### Python для начинающих: доступ по паролю с помощью shelve
Представьте: вы пишете небольшой консольный “личный дневник” или мини‑админку для заметок. Хранить данные в чистом виде в файле не хочется, база данных — слишком тяжело. А ещё нужен простой механизм ограничения доступа по логину и паролю.
Для таких задач отлично подходит модуль
---
## Что такое shelve
- ключи — строки;
- значения — любые сериализуемые объекты (списки, словари, классы и т.п.);
- данные автоматически сохраняются на диск.
Минимальный пример:
---
## Простейшая система доступа с сессиями
Сессия — это запись о том, что пользователь успешно вошёл, плюс немного данных о нём.
### 1. Регистрация пользователя
### 2. Логин и создание сессии
---
## Проверка доступа по сессии
Добавим проверку, что у пользователя есть действительная сессия:
Теперь можно ограничивать доступ к действиям:
---
## Когда 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 адреса
Валидация email — классическая задача, с которой рано или поздно сталкивается почти каждый начинающий питонист. Можно проверять строку “на глаз”, разбирать её по
### Почему именно regular expressions?
Регулярки позволяют описать формат email одной строкой-правилом:
- есть одна и только одна
- до
- после
- в конце — доменная зона: минимум 2 буквы (
### Базовый пример
Здесь
### Валидация списка и фильтрация
Представим, что у нас есть “сырые” данные, и нужно оставить только корректные адреса:
Комбинация
### На что обратить внимание
1. Идеальной регулярки для email не существует. Полный стандарт RFC очень сложный; в продакшене нередко используют библиотечные валидаторы или отправку тестового письма.
2. Регулярка — предварительный фильтр. Она отсекает заведомо неверные строки, но не гарантирует, что почтовый ящик существует.
3. Не переусложняйте. Для большинства веб-форм достаточно аккуратного, но не “RFC-идеального” шаблона — вроде того, что выше.
Регулярные выражения в Python — это инструмент, который позволяет превратить разрозненные проверки в одно чёткое правило. Освоив их на простом примере с 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
### Как использовать
В Python многие разработчики знают про исключения, но гораздо реже используют предупреждения. А зря: модуль
Предупреждение — это сигнал: “что‑то не так, но мы еще можем продолжать”.
---
## Базовый пример: ваше первое предупреждение
Здесь вместо аварийного
---
## Свой класс предупреждений
Как и с исключениями, вы можете создавать свои типы:
Наследуемся от
---
## Управление показом предупреждений
Иногда предупреждения слишком шумные.
Теперь все
Теперь любое
---
## Локальное подавление предупреждений
Если “шумит” только конкретный участок кода:
Вне
---
## Когда использовать предупреждения, а не исключения
Предупреждения особенно полезны, когда:
- используется устаревший параметр, но вы пока его поддерживаете;
- подставляется значение по умолчанию вместо отсутствующего;
- результат “подозрительно”, но еще пригоден;
- вы готовите пользователей к будущим изменениям API.
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 с
Когда слышишь слово «API», кажется, что сейчас понадобятся Django, Flask и гора магии. Но под капотом всё начинается с очень простой идеи: принять HTTP‑запрос и вернуть HTTP‑ответ. В стандартной библиотеке Python уже есть всё, чтобы сделать мини‑API буквально в несколько строк.
Ниже — современный вариант на Python 3 с модулем
### Мини‑сервер на
Сделаем API, у которого есть два маршрута:
-
-
Запусти этот скрипт и проверь:
-
-
или из терминала:
### А как в Python 2?
Логика та же, только модуль другой:
и без
### Что полезно заметить
-
- Метод
-
Такой «ручной» подход не заменяет Flask или FastAPI, но отлично прокачивает понимание основ и помогает быстро накидать простое внутреннее API или заглушку для тестов.
BaseHTTPServer / http.serverКогда слышишь слово «API», кажется, что сейчас понадобятся Django, Flask и гора магии. Но под капотом всё начинается с очень простой идеи: принять HTTP‑запрос и вернуть HTTP‑ответ. В стандартной библиотеке Python уже есть всё, чтобы сделать мини‑API буквально в несколько строк.
Ниже — современный вариант на Python 3 с модулем
http.server. Для Python 2 можно использовать те же идеи, просто заменить импорты.### Мини‑сервер на
http.serverСделаем API, у которого есть два маршрута:
-
GET / — вернёт простое сообщение-
GET /api/time — вернёт текущее время в JSONfrom 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 или заглушку для тестов.
🔥3❤1👍1
Python для начинающих: приручаем ошибки ввода с помощью
Любая программа, в которую что‑то вводит человек, рано или поздно ломается. Пользователь вместо числа вводит
В 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 и finallyelse выполняется, если в 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
Сериализация с
Иногда хочется просто “заморозить” объект Python: сложный словарь, результат обучения модели, кэш вычислений — а потом в другом месте и в другое время “разморозить” его и продолжить работу. Для этого есть модуль
Сериализация — это превращение объекта в последовательность байт. Десериализация — обратный процесс: из байтов снова получаем объект.
- файлы непонятны другим языкам;
- нельзя бездумно грузить непроверенный pickle-файл — это небезопасно.
---
### Базовый пример: сохранить и загрузить объект
---
### Работа в памяти:
Если нужно получить байты, например, чтобы отправить по сети или положить в базу:
---
### Сериализация собственных классов
Важно: при загрузке класс
---
### Вопрос безопасности
Никогда не загружай pickle-файлы из непроверенных источников.
Для обмена данными между разными системами чаще выбирают JSON. Но когда нужно быстро и удобно сохранять сложные Python-объекты “для себя” —
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
Работа с логами: запись и фильтрация с модулем
В любой программе рано или поздно наступает момент, когда
### Базовая настройка логов
Начнем с простого: запишем логи в файл и выведем их в консоль.
Ключевые идеи:
-
-
-
### Логи по модулям
Для реальных приложений полезно иметь отдельный логгер на модуль:
Теперь ты можешь тонко управлять шумом:
Весь проект логируется детально, но
### Фильтрация логов
Иногда нужен не только уровень, но и умный фильтр. Например, логировать только запросы к критичным endpoint’ам.
Фильтры позволяют:
- разделять логи по файлам по любому условию;
- временно “выключать” шумный функционал;
- собирать отдельные журналы для аудита.
### Логирование исключений
Еще один must-have — логировать трассировку стека:
---
Модуль
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() и хаоса в консоли.👍2❤1🔥1
Введение в модуль
Когда вы работаете с отсортированным списком, вставка новых элементов может испортить порядок — приходится либо искать позицию вручную, либо каждый раз вызывать
---
## Основные функции
Импортируем модуль:
Четыре ключевые функции:
-
-
-
-
---
## Поиск позиции без ручных циклов
Так вы мгновенно узнаете, куда встанет элемент, не перебирая список вручную.
---
## Вставка в отсортированный список
Вставляем и сразу поддерживаем порядок:
---
## Реальный пример: поиск "позиции в рейтинге"
Допустим, у вас есть отсортированный список результатов, и вы хотите узнать, каким по счету будет новый результат:
---
Модуль
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
Создание простого таймера обратного отсчёта на Python
Таймер обратного отсчёта — отличный мини‑проект для новичка: он знакомит с циклами, модулями стандартной библиотеки и работой со временем. А ещё его реально можно использовать: для помодоро-сессий, тренировок или напоминаний.
---
### Вариант 1: Самый простой консольный таймер
Используем модуль
Что здесь важно:
-
-
- Формат
Попробуйте поменять
---
### Вариант 2: Таймер с вводом от пользователя
Добавим интерактивность:
Здесь мы получаем число секунд от пользователя и передаём его в функцию. Если хотите, можете добавить проверку ввода, чтобы обрабатывать нечисловые значения и отрицательные числа.
---
### Вариант 3: Таймер до конкретного момента времени
Теперь немного «прокачаемся» и используем модуль
Здесь:
-
-
-
---
На таком простом таймере вы затрагиваете сразу несколько фундаментальных вещей: циклы, функции, работу с модулями
Таймер обратного отсчёта — отличный мини‑проект для новичка: он знакомит с циклами, модулями стандартной библиотеки и работой со временем. А ещё его реально можно использовать: для помодоро-сессий, тренировок или напоминаний.
---
### Вариант 1: Самый простой консольный таймер
Используем модуль
time и цикл while:import time
def countdown(seconds):
while seconds > 0:
mins = seconds // 60
secs = seconds % 60
time_format = f"{mins:02d}:{secs:02d}"
print(time_format, end="\r")
time.sleep(1)
seconds -= 1
print("00:00")
print("Time is up!")
countdown(10)
Что здесь важно:
-
time.sleep(1) — «усыпляет» программу на 1 секунду.-
end="\r" — перезаписывает строку в консоли, вместо того чтобы печатать новую.- Формат
f"{mins:02d}" добавляет ведущий ноль (например, 03:07).Попробуйте поменять
countdown(10) на любое количество секунд.---
### Вариант 2: Таймер с вводом от пользователя
Добавим интерактивность:
import time
def countdown(seconds):
while seconds > 0:
mins = seconds // 60
secs = seconds % 60
print(f"{mins:02d}:{secs:02d}", end="\r")
time.sleep(1)
seconds -= 1
print("00:00")
print("Time is up!")
user_input = int(input("Enter time in seconds: "))
countdown(user_input)
Здесь мы получаем число секунд от пользователя и передаём его в функцию. Если хотите, можете добавить проверку ввода, чтобы обрабатывать нечисловые значения и отрицательные числа.
---
### Вариант 3: Таймер до конкретного момента времени
Теперь немного «прокачаемся» и используем модуль
datetime: сделаем обратный отсчёт до заданного времени в будущем.import time
from datetime import datetime, timedelta
def countdown_to(target_time):
while True:
now = datetime.now()
delta = target_time - now
if delta.total_seconds() <= 0:
print("00:00:00")
print("Time is up!")
break
total_seconds = int(delta.total_seconds())
hours = total_seconds // 3600
mins = (total_seconds % 3600) // 60
secs = total_seconds % 60
print(f"{hours:02d}:{mins:02d}:{secs:02d}", end="\r")
time.sleep(1)
target = datetime.now() + timedelta(minutes=1)
countdown_to(target)
Здесь:
-
datetime.now() — текущее время.-
timedelta(minutes=1) — интервал в 1 минуту.-
delta.total_seconds() — сколько секунд осталось.---
На таком простом таймере вы затрагиваете сразу несколько фундаментальных вещей: циклы, функции, работу с модулями
time и datetime, форматирование строк и простую логику. Это именно тот тип мини‑проекта, который помогает быстро почувствовать живой результат от кода.👍6
Python для начинающих: как приручить модули и относительные импорты
Когда скрипт вырастает до 300+ строк, его уже страшно открывать, не то что править. Решение простое и взрослое — разбивать код на модули и пакеты. Давай разберёмся, как это делать аккуратно и зачем вообще нужны относительные импорты.
---
## Модули и пакеты по-простому
- Модуль — любой
- Пакет — папка с файлом
Пример структуры проекта:
Теперь
---
## Абсолютный импорт
Самый прямолинейный способ:
Плюс: ясно, откуда что приезжает.
Минус: если переименуешь пакет
---
## Зачем нужны относительные импорты
Внутри пакета можно ссылаться на «соседей» относительно текущего модуля, а не от корня проекта.
Используются точки:
-
-
-
### Пример
Это удобно, если структура пакетов логична сама по себе и ты не хочешь, чтобы имя «корневого» пакета было зашито в каждый импорт.
---
## Важный момент: как запускать проект
Относительные импорты корректно работают, если запускать код как модуль, а не «проваливаться» внутрь пакета.
Правильно:
Неправильно:
Когда запускаешь модуль через
---
## Практический мини-шаблон
---
Главная идея: модули — это способ разрезать код на логические куски, а относительные импорты — способ связывать эти куски так, чтобы проект можно было безболезненно переорганизовывать. Как только твой файл перестанет влезать «на один экран», самое время завести свой первый пакет.
Когда скрипт вырастает до 300+ строк, его уже страшно открывать, не то что править. Решение простое и взрослое — разбивать код на модули и пакеты. Давай разберёмся, как это делать аккуратно и зачем вообще нужны относительные импорты.
---
## Модули и пакеты по-простому
- Модуль — любой
.py файл. - Пакет — папка с файлом
__init__.py (может быть пустым).Пример структуры проекта:
my_project/
app.py
core/
__init__.py
models.py
services.py
utils/
__init__.py
validators.py
Теперь
core и core.utils — это пакеты, а остальные .py — модули.---
## Абсолютный импорт
Самый прямолинейный способ:
# app.py
from core.models import User
from core.services import UserService
from core.utils.validators import validate_email
Плюс: ясно, откуда что приезжает.
Минус: если переименуешь пакет
core в backend, придется править полпроекта.---
## Зачем нужны относительные импорты
Внутри пакета можно ссылаться на «соседей» относительно текущего модуля, а не от корня проекта.
Используются точки:
-
. — текущий пакет -
.. — на уровень вверх -
... — на два уровня вверх и т.д.### Пример
# core/services.py
from .models import User # из того же пакета core
from .utils.validators import validate_email # из подпакета utils
# core/utils/validators.py
from ..models import User # поднялись из core/utils в core и взяли models
Это удобно, если структура пакетов логична сама по себе и ты не хочешь, чтобы имя «корневого» пакета было зашито в каждый импорт.
---
## Важный момент: как запускать проект
Относительные импорты корректно работают, если запускать код как модуль, а не «проваливаться» внутрь пакета.
Правильно:
python -m my_project.app
Неправильно:
cd my_project/core
python services.py # относительные импорты могут упасть
Когда запускаешь модуль через
-m, Python понимает структуру пакетов и не путается с путями.---
## Практический мини-шаблон
# core/models.py
class User:
def __init__(self, email: str):
self.email = email
# core/utils/validators.py
def validate_email(email: str) -> bool:
return "@" in email and "." in email
# core/services.py
from .models import User
from .utils.validators import validate_email
def create_user(email: str) -> User:
if not validate_email(email):
raise ValueError("Invalid email")
return User(email=email)
# app.py
from core.services import create_user
if __name__ == "__main__":
user = create_user("test@example.com")
print(user.email)
---
Главная идея: модули — это способ разрезать код на логические куски, а относительные импорты — способ связывать эти куски так, чтобы проект можно было безболезненно переорганизовывать. Как только твой файл перестанет влезать «на один экран», самое время завести свой первый пакет.
👍2🔥1