Изучение шаблонов проектирования на простых примерах: Singleton, Factory
В какой‑то момент любой код на Python вырастает до такого размера, что простых функций и классов уже мало. Хочется порядка. Тут в игру выходят шаблоны проектирования. Разберём два базовых — Singleton и Factory — на минимальных, но жизненных примерах.
---
## Singleton: когда нужен только один экземпляр
Singleton гарантирует, что у класса будет всего один объект. Полезно для настроек приложения, логгера, подключения к БД.
### Пример: глобальные настройки приложения
---
## Factory: когда нужно создавать разные объекты одинаковым способом
Factory инкапсулирует логику создания объектов. Внешнему коду не важно, какой именно класс используется — он просто просит «сделай мне подходящий объект».
### Пример: парсер данных в разных форматах
Теперь, если появится
---
Singleton помогает централизовать состояние, а Factory — спрятать логику создания объектов за единым интерфейсом. Оба шаблона отлично тренируют умение думать о структуре программы, а не только о том, «как бы это запустить, чтобы не упало».
В какой‑то момент любой код на Python вырастает до такого размера, что простых функций и классов уже мало. Хочется порядка. Тут в игру выходят шаблоны проектирования. Разберём два базовых — Singleton и Factory — на минимальных, но жизненных примерах.
---
## Singleton: когда нужен только один экземпляр
Singleton гарантирует, что у класса будет всего один объект. Полезно для настроек приложения, логгера, подключения к БД.
### Пример: глобальные настройки приложения
class AppConfig:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self, debug=False):
if self._initialized:
return
self.debug = debug
self._initialized = True
cfg1 = AppConfig(debug=True)
cfg2 = AppConfig(debug=False)
print(cfg1 is cfg2) # True
print(cfg1.debug, cfg2.debug) # True True
__new__ контролирует создание объекта и хранит единственный экземпляр в _instance. __init__ запускается при каждом вызове класса, поэтому мы защищаемся флагом _initialized, чтобы не перезаписывать настройки.---
## Factory: когда нужно создавать разные объекты одинаковым способом
Factory инкапсулирует логику создания объектов. Внешнему коду не важно, какой именно класс используется — он просто просит «сделай мне подходящий объект».
### Пример: парсер данных в разных форматах
class JsonParser:
def parse(self, data: str):
return {"type": "json", "raw": data}
class XmlParser:
def parse(self, data: str):
return {"type": "xml", "raw": data}
class ParserFactory:
@staticmethod
def get_parser(format_name: str):
format_name = format_name.lower()
if format_name == "json":
return JsonParser()
if format_name == "xml":
return XmlParser()
raise ValueError(f"Unknown format: {format_name}")
parser = ParserFactory.get_parser("json")
result = parser.parse('{"id": 1}')
print(result)
Теперь, если появится
YamlParser, мы просто добавим его в ParserFactory, не переписывая весь остальной код.---
Singleton помогает централизовать состояние, а Factory — спрятать логику создания объектов за единым интерфейсом. Оба шаблона отлично тренируют умение думать о структуре программы, а не только о том, «как бы это запустить, чтобы не упало».
👍3❤1
Модуль
Когда данные растут, как снежный ком, возникает вопрос: «И что со всем этим делать?»
В Python для этого есть модуль
---
### Среднее:
Среднее арифметическое — это классическое "в среднем по больнице".
---
### Медиана:
Медиана — "середина" отсортированных данных. Она куда устойчивее к выбросам.
Для четного количества значений можно управлять поведением:
---
### Дисперсия и стандартное отклонение: насколько данные "разбросаны"
Если среднее говорит "где центр", то дисперсия и стандартное отклонение — "насколько всё вокруг центра разлетается".
Чем больше дисперсия и стандартное отклонение, тем сильнее отличаются значения внутри набора.
Важно:
---
### Небольшой практический пример
Оценим стабильность времени отклика сервиса:
Если среднее сильно больше медианы и стандартное отклонение велико — у вас есть редкие, но тяжелые "подвисания", которые нельзя игнорировать.
---
statistics: среднее, медиана и дисперсия без боли в головеКогда данные растут, как снежный ком, возникает вопрос: «И что со всем этим делать?»
В Python для этого есть модуль
statistics — маленький, но очень полезный набор инструментов для анализа чисел.---
### Среднее:
meanСреднее арифметическое — это классическое "в среднем по больнице".
from statistics import mean
temperatures = [18, 20, 21, 19, 22, 20]
avg_temp = mean(temperatures)
print(avg_temp) # 20.0
mean() чувствителен к выбросам. Если добавить одно странное значение, картина исказится:from statistics import mean
salaries = [50_000, 55_000, 52_000, 60_000, 1_000_000]
print(mean(salaries)) # 243000.0 — выглядит уже не так реалистично
---
### Медиана:
median и median_low / median_highМедиана — "середина" отсортированных данных. Она куда устойчивее к выбросам.
from statistics import median
salaries = [50_000, 55_000, 52_000, 60_000, 1_000_000]
print(median(salaries)) # 55_000 — намного ближе к реальности
Для четного количества значений можно управлять поведением:
from statistics import median_low, median_high
values = [1, 2, 100, 101]
print(median_low(values)) # 2
print(median_high(values)) # 100
---
### Дисперсия и стандартное отклонение: насколько данные "разбросаны"
Если среднее говорит "где центр", то дисперсия и стандартное отклонение — "насколько всё вокруг центра разлетается".
from statistics import variance, stdev
scores = [70, 72, 71, 69, 70, 71]
print(variance(scores)) # маленькое значение — все близко к среднему
print(stdev(scores)) # стандартное отклонение
Чем больше дисперсия и стандартное отклонение, тем сильнее отличаются значения внутри набора.
Важно:
variance() и stdev() требуют минимум 2 значения. Для всей генеральной совокупности есть pvariance() и pstdev() — используют немного другие формулы:from statistics import pvariance, pstdev
scores = [70, 72, 71, 69, 70, 71]
print(pvariance(scores))
print(pstdev(scores))
---
### Небольшой практический пример
Оценим стабильность времени отклика сервиса:
from statistics import mean, median, stdev
response_times = [110, 120, 115, 300, 118, 117, 119]
print("mean:", mean(response_times))
print("median:", median(response_times))
print("stdev:", stdev(response_times))
Если среднее сильно больше медианы и стандартное отклонение велико — у вас есть редкие, но тяжелые "подвисания", которые нельзя игнорировать.
---
statistics — идеальный вход в мир анализа данных: минимум кода, максимум информации о ваших числах. Начните с этих функций, и набор цифр перестанет быть просто хаосом.❤3👍2🔥2
Как использовать watchdog для отслеживания изменений в файлах
Иногда хочется, чтобы скрипт сам реагировал на изменения в файлах: перезапускал сборку, обновлял данные, копировал новый файл в бэкап. Переписывать бесконечные циклы
---
### Установка
---
### Простейший пример: реагируем на изменения в папке
Создадим наблюдателя, который будет печатать, когда что-то происходит в директории:
Теперь, если в папке создать/изменить/удалить файл — скрипт отреагирует.
---
### Фильтрация по типу файлов
Часто нужно отслеживать только, скажем,
Так можно запускать, например, линтер или тесты только при изменении исходников.
---
### Автоматическая реакция: мини‑автосборка
Пример: при изменении файла конфигурации перегенерировать выходной файл.
Теперь любое сохранение
---
Иногда хочется, чтобы скрипт сам реагировал на изменения в файлах: перезапускал сборку, обновлял данные, копировал новый файл в бэкап. Переписывать бесконечные циклы
while True с time.sleep — скучно и неэффективно. Для этого есть отличный модуль — watchdog.---
### Установка
pip install watchdog
watchdog работает кроссплатформенно и умеет «слушать» файловую систему почти как системные утилиты.---
### Простейший пример: реагируем на изменения в папке
Создадим наблюдателя, который будет печатать, когда что-то происходит в директории:
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import time
from pathlib import Path
class SimpleHandler(FileSystemEventHandler):
def on_modified(self, event):
print(f"Modified: {event.src_path}")
def on_created(self, event):
print(f"Created: {event.src_path}")
def on_deleted(self, event):
print(f"Deleted: {event.src_path}")
if __name__ == "__main__":
path = Path(".").resolve()
event_handler = SimpleHandler()
observer = Observer()
observer.schedule(event_handler, str(path), recursive=True)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()
Теперь, если в папке создать/изменить/удалить файл — скрипт отреагирует.
---
### Фильтрация по типу файлов
Часто нужно отслеживать только, скажем,
.py или .txt. Добавим простую проверку:class PyFilesHandler(FileSystemEventHandler):
def on_modified(self, event):
if event.is_directory:
return
if not event.src_path.endswith(".py"):
return
print(f"Python file changed: {event.src_path}")
Так можно запускать, например, линтер или тесты только при изменении исходников.
---
### Автоматическая реакция: мини‑автосборка
Пример: при изменении файла конфигурации перегенерировать выходной файл.
import subprocess
class BuildHandler(FileSystemEventHandler):
def on_modified(self, event):
if event.is_directory:
return
if event.src_path.endswith("config.yaml"):
print("Config changed, rebuilding...")
subprocess.run(["python", "build.py"])
Теперь любое сохранение
config.yaml автоматически запускает build.py.---
watchdog — мощный инструмент для автоматизации рутинных задач вокруг файловой системы: автосборка проектов, синхронизация папок, логирование изменений. Главное — один раз написать обработчик событий, а дальше файлы будут «говорить» с вашим кодом сами.🔥5
Работа с кешированием данных: file-based и memory-based подходы
================================================================
Кеширование — это способ «не думать дважды». Если результат уже посчитан, зачем снова тратить время и ресурсы? В Python это особенно актуально при работе с медленными запросами к API, базам данных или тяжёлым вычислениям.
Разберём два простых подхода: кеш в памяти и кеш в файле.
## Memory-based: быстро, но временно
Кеш в памяти живёт только пока работает программа. Зато он очень быстрый.
- автоматически хранит результаты по аргументам;
- ограничивает размер кеша (
- идеально подходит для функций без побочных эффектов (чистые функции).
Минус: как только программа завершилась — кеш исчез.
## File-based: медленнее, но переживает перезапуск
Кеш в файле сохраняет результаты между запусками. Удобно для скриптов, которые периодически запускаются по расписанию.
Самый простой способ — использовать
Плюсы:
- данные сохраняются между запусками;
- можно легко чистить/анализировать кеш-файл.
Минусы:
- медленнее RAM;
- нужно следить за размером файла и «протуханием» данных.
## Что выбрать?
- Нужна скорость внутри одного процесса — берите
- Важно экономить время между запусками — храните кеш в файле.
- В реальных проектах их часто комбинируют: быстрый memory-кеш поверх более «упорного» file-кеша.
Кеширование — один из тех инструментов, которые мгновенно делают ваш код ощущаемо «умнее» и быстрее, если применять его осознанно.
================================================================
Кеширование — это способ «не думать дважды». Если результат уже посчитан, зачем снова тратить время и ресурсы? В Python это особенно актуально при работе с медленными запросами к API, базам данных или тяжёлым вычислениям.
Разберём два простых подхода: кеш в памяти и кеш в файле.
## Memory-based: быстро, но временно
Кеш в памяти живёт только пока работает программа. Зато он очень быстрый.
import time
from functools import lru_cache
@lru_cache(maxsize=128)
def slow_square(n: int) -> int:
time.sleep(1) # имитация долгой операции
return n * n
if __name__ == "__main__":
start = time.time()
print(slow_square(10)) # первый вызов — медленно
print("First call:", time.time() - start)
start = time.time()
print(slow_square(10)) # второй вызов — из кеша
print("Second call:", time.time() - start)
lru_cache:- автоматически хранит результаты по аргументам;
- ограничивает размер кеша (
maxsize);- идеально подходит для функций без побочных эффектов (чистые функции).
Минус: как только программа завершилась — кеш исчез.
## File-based: медленнее, но переживает перезапуск
Кеш в файле сохраняет результаты между запусками. Удобно для скриптов, которые периодически запускаются по расписанию.
Самый простой способ — использовать
pickle или json. Пример с json:import json
import os
import time
from typing import Any, Dict
CACHE_FILE = "cache.json"
def load_cache() -> Dict[str, Any]:
if not os.path.exists(CACHE_FILE):
return {}
with open(CACHE_FILE, "r", encoding="utf-8") as f:
return json.load(f)
def save_cache(cache: Dict[str, Any]) -> None:
with open(CACHE_FILE, "w", encoding="utf-8") as f:
json.dump(cache, f)
def get_data(param: str) -> str:
time.sleep(1) # имитация тяжёлого запроса
return f"result_for_{param}"
def cached_get_data(param: str) -> str:
cache = load_cache()
if param in cache:
return cache[param]
result = get_data(param)
cache[param] = result
save_cache(cache)
return result
if __name__ == "__main__":
print(cached_get_data("foo")) # первый раз — долго
print(cached_get_data("foo")) # второй раз — сразу
Плюсы:
- данные сохраняются между запусками;
- можно легко чистить/анализировать кеш-файл.
Минусы:
- медленнее RAM;
- нужно следить за размером файла и «протуханием» данных.
## Что выбрать?
- Нужна скорость внутри одного процесса — берите
lru_cache.- Важно экономить время между запусками — храните кеш в файле.
- В реальных проектах их часто комбинируют: быстрый memory-кеш поверх более «упорного» file-кеша.
Кеширование — один из тех инструментов, которые мгновенно делают ваш код ощущаемо «умнее» и быстрее, если применять его осознанно.
🔥3👍2
Как исключить дубли в данных с помощью множеств
Дубли в данных — классическая боль новичка: выгрузили список email’ов, пользователей или айдишников, а там половина повторяется. Перебирать руками? Нет. В Python есть более элегантный инструмент — множества (
### Что такое множество?
Бонус: операции над множествами быстрые. Если нужно часто проверять, есть ли элемент в коллекции,
---
### Удаляем дубли, но сохраняем порядок
У
---
### Убираем дубли в списке словарей
Сложнее случай: у нас есть список словарей, и мы хотим убира́ть дубли по какому-то полю, например
Мы не пытаемся сделать множество из словарей (они неизменяемыми быть не могут), мы используем множество только для хранения уже встреченных
---
### Быстрая проверка пересечений
Множества полезны не только для удаления дублей, но и для анализа данных: пересечения, разности, объединения.
---
Множества — это простой способ приручить хаос в данных: удалять дубли, быстро искать элементы и работать с пересечениями. Как только начинаешь активно использовать
Дубли в данных — классическая боль новичка: выгрузили список email’ов, пользователей или айдишников, а там половина повторяется. Перебирать руками? Нет. В Python есть более элегантный инструмент — множества (
set).### Что такое множество?
set — это неупорядоченная коллекция уникальных элементов. Любой дубль при добавлении просто игнорируется.emails = ["a@example.com", "b@example.com", "a@example.com", "c@example.com"]
unique_emails = set(emails)
print(unique_emails)
# {'a@example.com', 'b@example.com', 'c@example.com'}
Бонус: операции над множествами быстрые. Если нужно часто проверять, есть ли элемент в коллекции,
set обычно быстрее списка.---
### Удаляем дубли, но сохраняем порядок
У
set нет порядка, а иногда он важен (например, порядок регистрации пользователей). Тогда можно комбинировать список и множество:users = ["alice", "bob", "alice", "dave", "bob", "eve"]
seen = set()
unique_users = []
for user in users:
if user not in seen:
seen.add(user)
unique_users.append(user)
print(unique_users)
# ['alice', 'bob', 'dave', 'eve']
seen отвечает только за проверку дублей, а итоговый порядок хранится в unique_users.---
### Убираем дубли в списке словарей
Сложнее случай: у нас есть список словарей, и мы хотим убира́ть дубли по какому-то полю, например
id.records = [
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"},
{"id": 1, "name": "Alice Smith"},
{"id": 3, "name": "Eve"},
]
seen_ids = set()
clean_records = []
for record in records:
record_id = record["id"]
if record_id not in seen_ids:
seen_ids.add(record_id)
clean_records.append(record)
print(clean_records)
# [{'id': 1, 'name': 'Alice'},
# {'id': 2, 'name': 'Bob'},
# {'id': 3, 'name': 'Eve'}]
Мы не пытаемся сделать множество из словарей (они неизменяемыми быть не могут), мы используем множество только для хранения уже встреченных
id.---
### Быстрая проверка пересечений
Множества полезны не только для удаления дублей, но и для анализа данных: пересечения, разности, объединения.
old_users = {"alice", "bob", "carol"}
new_users = {"bob", "dave", "eve"}
returned_users = old_users & new_users # пересечение
all_users = old_users | new_users # объединение
lost_users = old_users - new_users # кто не вернулся
print(returned_users) # {'bob'}
print(all_users) # {'alice', 'bob', 'carol', 'dave', 'eve'}
print(lost_users) # {'alice', 'carol'}
---
Множества — это простой способ приручить хаос в данных: удалять дубли, быстро искать элементы и работать с пересечениями. Как только начинаешь активно использовать
set, многие задачи очистки и анализа данных становятся в разы проще.👍5❤1
Python для начинающих: магия
Когда мы слышим «случайность в Python», чаще всего вспоминаем
Но как только появляется слово «вероятность», в игру вступает его более продвинутый родственник —
## Базовый пример: не просто выбрать, а выбрать с шансами
Функция
- выбирать несколько элементов сразу;
- учитывать «вес» каждого элемента (то есть вероятность).
Здесь
## Чем
-
-
По умолчанию
## Веса и кумулятивные веса
## Выбор с возвращением и без
Если нужен выбор без повторов, смотри на:
-
- или на
## Минииcследование: проверяем, что веса работают
Запустив этот код, ты увидишь, что частоты примерно соответствуют пропорциям
random.choices — взвешенный случайный выборКогда мы слышим «случайность в Python», чаще всего вспоминаем
random.choice. Он берет один случайный элемент из последовательности — и на этом всё. Но как только появляется слово «вероятность», в игру вступает его более продвинутый родственник —
random.choices.## Базовый пример: не просто выбрать, а выбрать с шансами
Функция
random.choices умеет:- выбирать несколько элементов сразу;
- учитывать «вес» каждого элемента (то есть вероятность).
import random
fruits = ["apple", "banana", "orange"]
weights = [1, 3, 6] # вероятности: apple < banana < orange
result = random.choices(fruits, weights=weights, k=10)
print(result)
Здесь
k=10 — сколько элементов выбрать. orange будет выпадать чаще всего, потому что его вес больше.## Чем
choice отличается от choices-
random.choice(seq) → один элемент, все равновероятны.-
random.choices(seq, k=n) → список из n элементов, с возможностью задавать веса.items = ["A", "B", "C"]
print(random.choice(items)) # один элемент
print(random.choices(items, k=5)) # список из 5 элементов
По умолчанию
choices тоже считает все элементы равновероятными, пока не указаны weights или cum_weights.## Веса и кумулятивные веса
weights — обычные веса (не обязаны быть в сумме 1):cards = ["common", "rare", "epic", "legendary"]
weights = [70, 20, 9, 1] # проценты можно записать и так, пропорционально
pulled = random.choices(cards, weights=weights, k=20)
print(pulled)
cum_weights — кумулятивные (накопленные) значения. Например, [70, 90, 99, 100] — та же таблица вероятностей, но в формате «до этой границы»:cards = ["common", "rare", "epic", "legendary"]
cum_weights = [70, 90, 99, 100]
pulled = random.choices(cards, cum_weights=cum_weights, k=20)
print(pulled)
weights и cum_weights вместе указывать нельзя — выбери что-то одно.## Выбор с возвращением и без
random.choices всегда выбирает с возвращением — один и тот же элемент может встретиться несколько раз. Это удобно для моделирования повторяющихся событий: бросков, попыток, лут-боксов.Если нужен выбор без повторов, смотри на:
-
random.sample(population, k) — без повторов;- или на
random.shuffle и срезы.import random
numbers = list(range(10))
print(random.sample(numbers, 3)) # 3 уникальных числа
## Минииcследование: проверяем, что веса работают
import random
from collections import Counter
choices = ["A", "B", "C"]
weights = [1, 2, 7]
result = random.choices(choices, weights=weights, k=10000)
counts = Counter(result)
for item in choices:
print(item, counts[item] / 10000)
Запустив этот код, ты увидишь, что частоты примерно соответствуют пропорциям
1:2:7.random.choices — отличный инструмент, когда нужно не просто «случайно», а «случайно с контролем»: симуляции, простые игровые механики, генерация тестовых данных — везде, где вероятность чего-то должна быть не только ощущаемой, но и задаваемой.👍5
Создание API‑клиентов: практика на примере GitHub API
Когда вы научились писать функции и циклы, следующим логичным шагом становится общение с внешним миром: сервисами, сайтами, ботами. Для этого нужны API‑клиенты — небольшие программы, которые отправляют запросы и получают данные. Разберёмся на реальном и популярном примере: GitHub API.
---
### Базовый запрос: получаем информацию о пользователе
Для начала нам нужен модуль
Здесь важные моменты:
-
-
- API GitHub возвращает JSON, поэтому
---
### Оборачиваем в удобный клиент
Чтобы код не расползался по проекту, удобно сделать мини‑клиент:
Что здесь важно для начинающего:
- Используем
- Выделяем приватный метод
- Методы
---
### Обработка ошибок и лимитов
GitHub ограничивает число запросов. Если лимит исчерпан, сервер вернёт код 403/429. Простая обработка:
---
### Итог
API‑клиент — это:
1. Базовый слой работы с HTTP (
2. Чёткие методы под задачи домена (
3. Явная обработка ошибок и лимитов.
Попробуйте расширить клиент: добавить получение коммитов, фильтрацию публичных/приватных репозиториев (для этого понадобится токен), поиск репозиториев по языку. Так вы почувствуете, как из нескольких функций рождается удобный инструмент для автоматизации работы с реальными сервисами.
Когда вы научились писать функции и циклы, следующим логичным шагом становится общение с внешним миром: сервисами, сайтами, ботами. Для этого нужны API‑клиенты — небольшие программы, которые отправляют запросы и получают данные. Разберёмся на реальном и популярном примере: GitHub API.
---
### Базовый запрос: получаем информацию о пользователе
Для начала нам нужен модуль
requests:import requests
def get_user_info(username: str) -> dict:
url = f"https://api.github.com/users/{username}"
response = requests.get(url, timeout=5)
response.raise_for_status() # выбросит исключение при ошибке
return response.json()
if __name__ == "__main__":
user = get_user_info("torvalds")
print(user["login"], user["public_repos"], user["followers"])
Здесь важные моменты:
-
timeout — не даёт программе «висеть» бесконечно.-
raise_for_status() — дисциплинирует: если код ответа не 200, вы узнаете об этом сразу.- API GitHub возвращает JSON, поэтому
response.json() превращает его в dict.---
### Оборачиваем в удобный клиент
Чтобы код не расползался по проекту, удобно сделать мини‑клиент:
import requests
from typing import List, Dict
class GitHubClient:
BASE_URL = "https://api.github.com"
def __init__(self, token: str | None = None) -> None:
self.session = requests.Session()
if token:
self.session.headers.update({"Authorization": f"Bearer {token}"})
def _get(self, path: str, **params) -> dict | List[dict]:
url = f"{self.BASE_URL}{path}"
resp = self.session.get(url, params=params, timeout=5)
resp.raise_for_status()
return resp.json()
def get_user(self, username: str) -> dict:
return self._get(f"/users/{username}")
def get_repos(self, username: str) -> List[Dict]:
return self._get(f"/users/{username}/repos", per_page=100)
if __name__ == "__main__":
client = GitHubClient() # без токена тоже работает, но с лимитами
user = client.get_user("python")
repos = client.get_repos("python")
print(user["login"], "repos:", len(repos))
Что здесь важно для начинающего:
- Используем
Session — это быстрее и позволяет один раз настроить заголовки.- Выделяем приватный метод
_get — вся логика запросов в одном месте.- Методы
get_user, get_repos уже звучат как «команды» к GitHub.---
### Обработка ошибок и лимитов
GitHub ограничивает число запросов. Если лимит исчерпан, сервер вернёт код 403/429. Простая обработка:
def safe_get_user(client: GitHubClient, username: str) -> dict | None:
try:
return client.get_user(username)
except requests.HTTPError as exc:
status = exc.response.status_code
if status == 404:
print("User not found")
elif status in (403, 429):
print("Rate limit exceeded")
else:
print("HTTP error:", status)
return None
---
### Итог
API‑клиент — это:
1. Базовый слой работы с HTTP (
requests, Session, _get).2. Чёткие методы под задачи домена (
get_user, get_repos).3. Явная обработка ошибок и лимитов.
Попробуйте расширить клиент: добавить получение коммитов, фильтрацию публичных/приватных репозиториев (для этого понадобится токен), поиск репозиториев по языку. Так вы почувствуете, как из нескольких функций рождается удобный инструмент для автоматизации работы с реальными сервисами.
👍3🔥2❤1
Как протестировать производительность кода с
Иногда код «на глаз» кажется быстрым, но в реальности один лишний цикл или неудачная структура данных легко замедляют программу в разы. Модуль
### Почему
Простой подход:
Проблема: результаты «шумные». На них влияет всё: другие процессы, кэш CPU, случайные задержки.
- запускает код много раз (по умолчанию 1 000 000 коротких запусков),
- считает среднее время,
- минимизирует влияние внешних факторов.
### Базовое использование в виде модуля
Параметр
### Сравнение двух вариантов решения
Классическая задача: сравнить list comprehension и цикл с
### Использование
Если вы работаете в терминале:
По умолчанию Python сам подбирает количество повторений и выводит минимальное время одного запуска.
### Замер функций из своего кода
Иногда удобнее измерять уже определенную функцию:
Ключевой момент — импортировать функции в
---
timeitИногда код «на глаз» кажется быстрым, но в реальности один лишний цикл или неудачная структура данных легко замедляют программу в разы. Модуль
timeit — встроенный в Python инструмент, который позволяет честно померить время исполнения небольших фрагментов кода.### Почему
timeit, а не time.time()Простой подход:
import time
start = time.time()
# some code
end = time.time()
print(end - start)
Проблема: результаты «шумные». На них влияет всё: другие процессы, кэш CPU, случайные задержки.
timeit решает это:- запускает код много раз (по умолчанию 1 000 000 коротких запусков),
- считает среднее время,
- минимизирует влияние внешних факторов.
### Базовое использование в виде модуля
import timeit
code = """
result = []
for i in range(1000):
result.append(i)
"""
t = timeit.timeit(code, number=1000)
print(t)
Параметр
number — сколько раз выполнить код целиком. Чем меньше код, тем больше number стоит использовать, чтобы получить стабильный результат.### Сравнение двух вариантов решения
Классическая задача: сравнить list comprehension и цикл с
append.import timeit
setup = "n = 1000"
code_listcomp = "[i for i in range(n)]"
code_append = """
result = []
for i in range(n):
result.append(i)
"""
t_listcomp = timeit.timeit(code_listcomp, setup=setup, number=10000)
t_append = timeit.timeit(code_append, setup=setup, number=10000)
print("list comprehension:", t_listcomp)
print("append in loop :", t_append)
setup выполняется один раз до серии замеров (инициализация переменных, импортов и т.п.). Внутри code_* удобно писать только «голое» тело эксперимента.### Использование
timeit из интерактивной консолиЕсли вы работаете в терминале:
python -m timeit "sum(range(1000))"
python -m timeit "total=0\nfor i in range(1000): total+=i"
По умолчанию Python сам подбирает количество повторений и выводит минимальное время одного запуска.
### Замер функций из своего кода
Иногда удобнее измерять уже определенную функцию:
import timeit
def slow_sum(n):
total = 0
for i in range(n):
total += i
return total
def fast_sum(n):
return sum(range(n))
t_slow = timeit.timeit("slow_sum(1000)", setup="from __main__ import slow_sum", number=10000)
t_fast = timeit.timeit("fast_sum(1000)", setup="from __main__ import fast_sum", number=10000)
print("slow_sum:", t_slow)
print("fast_sum:", t_fast)
Ключевой момент — импортировать функции в
setup.---
timeit не даст вам абсолютного «идеального» времени, но отлично показывает, какой из двух вариантов кода быстрее и во сколько раз. Это уже достаточно, чтобы принимать осознанные решения об оптимизации.🔥3👍2
Введение в работу с protobuf: быстрая сериализация данных
JSON удобен, пока данные небольшие и требования к скорости умеренные. Но как только вы начинаете передавать тысячи сообщений в секунду между сервисами, JSON внезапно становится «тормозом». Здесь на сцену выходит Protocol Buffers (protobuf) — бинарный формат от Google, заточенный под скорость и компактность.
---
### Что такое protobuf в двух словах
Protobuf — это:
- бинарный формат сериализации (данные занимают меньше места, чем JSON);
- строгая схема (типизация, обязательные/необязательные поля);
- кросс-языковая поддержка (Python, Go, Java, C++ и т.д.).
Сначала вы описываете структуру данных в
---
### Описание схемы
Создадим файл
Ключевые моменты:
-
-
- Числа
---
### Генерация Python-классов
Устанавливаем пакет:
Компилируем схему (нужен установленный
Появится
---
### Сериализация и десериализация
По сравнению с JSON:
- меньше размер (особенно для больших структур и списков);
- быстрая (де)сериализация;
- гарантируется наличие нужных полей и их типов.
---
### Эволюция схемы без боли
Сильная сторона protobuf — обратная совместимость.
Вы можете:
- добавлять новые поля в конец (
- помечать поля как устаревшие, но пока не удалять их.
Старый клиент просто игнорирует незнакомые теги, а новый — использует дополнительные поля, если они есть.
---
### Когда protobuf действительно нужен
Используйте protobuf, если:
- у вас есть микросервисы, которым нужно быстро обмениваться структурированными данными;
- важна экономия трафика;
- требуется строгая и эволюционирующая схема данных.
Если же вы просто сохраняете настройки в файл или делаете маленький скрипт-утилиту, JSON остаётся проще. Но как только проект растёт — protobuf становится важным инструментом Python-разработчика.
JSON удобен, пока данные небольшие и требования к скорости умеренные. Но как только вы начинаете передавать тысячи сообщений в секунду между сервисами, JSON внезапно становится «тормозом». Здесь на сцену выходит Protocol Buffers (protobuf) — бинарный формат от Google, заточенный под скорость и компактность.
---
### Что такое protobuf в двух словах
Protobuf — это:
- бинарный формат сериализации (данные занимают меньше места, чем JSON);
- строгая схема (типизация, обязательные/необязательные поля);
- кросс-языковая поддержка (Python, Go, Java, C++ и т.д.).
Сначала вы описываете структуру данных в
.proto файле, затем компилируете его, и уже сгенерированный Python-код используете как обычные классы.---
### Описание схемы
Создадим файл
user.proto:syntax = "proto3";
message User {
int32 id = 1;
string name = 2;
string email = 3;
repeated string tags = 4;
}
Ключевые моменты:
-
message — аналог класса/структуры.-
repeated — список значений.- Числа
= 1, = 2 — теги полей. Они нужны для бинарного формата и обратной совместимости.---
### Генерация Python-классов
Устанавливаем пакет:
pip install protobuf
Компилируем схему (нужен установленный
protoc):protoc --python_out=. user.proto
Появится
user_pb2.py — не редактируем его вручную, просто используем.---
### Сериализация и десериализация
from user_pb2 import User
def create_user() -> User:
user = User(
id=1,
name="Alice",
email="alice@example.com",
tags=["admin", "premium"]
)
return user
user = create_user()
# сериализация в бинарный формат
data_bytes = user.SerializeToString()
# восстановление объекта из байт
user_copy = User()
user_copy.ParseFromString(data_bytes)
print(len(data_bytes)) # компактный размер
print(user_copy.name) # Alice
print(user_copy.tags) # ['admin', 'premium']
По сравнению с JSON:
- меньше размер (особенно для больших структур и списков);
- быстрая (де)сериализация;
- гарантируется наличие нужных полей и их типов.
---
### Эволюция схемы без боли
Сильная сторона protobuf — обратная совместимость.
Вы можете:
- добавлять новые поля в конец (
= 5, = 6), не ломая старые клиенты;- помечать поля как устаревшие, но пока не удалять их.
Старый клиент просто игнорирует незнакомые теги, а новый — использует дополнительные поля, если они есть.
---
### Когда protobuf действительно нужен
Используйте protobuf, если:
- у вас есть микросервисы, которым нужно быстро обмениваться структурированными данными;
- важна экономия трафика;
- требуется строгая и эволюционирующая схема данных.
Если же вы просто сохраняете настройки в файл или делаете маленький скрипт-утилиту, JSON остаётся проще. Но как только проект растёт — protobuf становится важным инструментом Python-разработчика.
👍3