Python для начинающих
1.24K subscribers
546 photos
3 videos
232 files
74 links
Python для начинающих
Download Telegram
### Работа с zip-архивами с помощью стандартного zipfile

Zip-архивы — это такой «контейнер» для файлов: удобно отправить, сохранить, логировать бэкапы. В Python для этого ничего устанавливать не нужно — в стандартной библиотеке уже есть модуль zipfile.

---

## Создаем zip-архив

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

import zipfile
from pathlib import Path

def create_archive(archive_name, files):
with zipfile.ZipFile(archive_name, mode="w", compression=zipfile.ZIP_DEFLATED) as zf:
for file_path in files:
file_path = Path(file_path)
zf.write(file_path, arcname=file_path.name)

create_archive("project.zip", ["main.py", "config.json", "README.md"])


Ключевые моменты:

- mode="w" — создать новый архив (старый с таким именем будет перезаписан).
- compression=ZIP_DEFLATED — сжатие, без него файлы просто «складываются» в контейнер.
- arcname — имя файла внутри архива (можно отличать от исходного пути).

---

## Добавляем файлы в существующий архив

Иногда нужно дописать архив, не пересоздавая его:

import zipfile

def append_to_archive(archive_name, file_path):
with zipfile.ZipFile(archive_name, mode="a", compression=zipfile.ZIP_DEFLATED) as zf:
zf.write(file_path)

append_to_archive("project.zip", "log.txt")


Режим a — «append», дозапись.

---

## Просмотр содержимого архива

Посмотрим, что внутри, без распаковки:

import zipfile

def list_archive(archive_name):
with zipfile.ZipFile(archive_name, mode="r") as zf:
for info in zf.infolist():
print(f"{info.filename:20} {info.file_size:8} bytes")

list_archive("project.zip")


Объект ZipInfo содержит размер, дату изменения, атрибуты и т.д.

---

## Распаковка архива

Распакуем либо всё, либо один конкретный файл:

import zipfile

def extract_all(archive_name, target_dir):
with zipfile.ZipFile(archive_name, mode="r") as zf:
zf.extractall(path=target_dir)

def extract_file(archive_name, member, target_dir):
with zipfile.ZipFile(archive_name, mode="r") as zf:
zf.extract(member, path=target_dir)

extract_all("project.zip", "unpacked/")
extract_file("project.zip", "config.json", "configs/")


---

## Чтение файла прямо из архива

Иногда распаковывать на диск не хочется — удобнее прочитать файл «на лету»:

import zipfile

def read_text_from_zip(archive_name, member):
with zipfile.ZipFile(archive_name, mode="r") as zf:
with zf.open(member) as f:
return f.read().decode("utf-8")

content = read_text_from_zip("project.zip", "README.md")
print(content)


Так можно, например, читать настройки, ресурсы или шаблоны прямо из архива.

---

zipfile покрывает 90% бытовых задач с архивами: упаковка, дописывание, просмотр, извлечение и чтение «на лету». Всё это — без сторонних библиотек и с парой строк кода.
👍6
Как использовать модуль subprocess для запуска внешних команд
Как использовать модуль subprocess для запуска внешних команд

Иногда Python‑кода мало. Нужно дернуть системную утилиту: запустить ping, запустить другой скрипт, конвертировать видео через ffmpeg. Для этого есть модуль subprocess — безопасная и гибкая альтернатива старым os.system.

---

### Простой запуск и получение вывода

Самый удобный вход — функция subprocess.run. Она запускает команду, ждет завершения и возвращает объект с результатом:

import subprocess

result = subprocess.run(
["echo", "Hello from subprocess"],
capture_output=True,
text=True
)

print("Return code:", result.returncode)
print("Stdout:", result.stdout)
print("Stderr:", result.stderr)


Ключевые моменты:

- Команда передаётся как список: ["echo", "text"], а не одной строкой. Это безопаснее и избавляет от проблем с кавычками.
- capture_output=True собирает stdout и stderr.
- text=True декодирует байты в строки (по умолчанию UTF-8).

---

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

Если вы хотите, чтобы Python падал при ошибке команды, используйте check=True:

import subprocess

try:
subprocess.run(
["ls", "/definitely_not_exists"],
check=True,
capture_output=True,
text=True
)
except subprocess.CalledProcessError as exc:
print("Command failed!")
print("Return code:", exc.returncode)
print("Stderr:", exc.stderr)


CalledProcessError содержит всю полезную информацию о провалившейся команде.

---

### Передача данных на stdin

Иногда внешней программе нужно что‑то «скормить» через стандартный ввод:

import subprocess

text_to_send = "line 1\nline 2\n"

result = subprocess.run(
["grep", "line"],
input=text_to_send,
text=True,
capture_output=True
)

print(result.stdout)


Параметр input= передает данные в stdin процесса.

---

### Непосредственное взаимодействие: Popen

Если нужно более тонкое управление (долгоживущий процесс, чтение вывода построчно и т.п.), используйте Popen:

import subprocess

proc = subprocess.Popen(
["ping", "-c", "3", "python.org"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)

for line in proc.stdout:
print("PING OUTPUT:", line.strip())

return_code = proc.wait()
print("Return code:", return_code)


Здесь процесс живет «параллельно», а вы можете читать его вывод по мере поступления.

---

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

- Не передавайте в subprocess строки, собранные из непроверенного пользовательского ввода.
- Предпочитайте список аргументов вместо одной строки.
- Не включайте shell=True, если это не абсолютно необходимо. Именно shell=True чаще всего открывает дверь инъекциям команд.

---

subprocess — это мост между вашим Python‑кодом и всем остальным миром командной строки. Освоив его, вы сможете автоматизировать практически любую системную задачу, не покидая Python.
👍5
Работа со структурированными логами: использование логгеров с форматами
Python для начинающих: структурированные логи без боли и магии

Обычный print() в продакшене — как дневник на стикерах: пока записей мало, всё понятно, но стоит коду вырасти — и начинается хаос. Здесь на сцену выходит модуль logging и особенно — форматирование логов.

---

### Простейший логгер с форматом

Модуль logging уже в стандартной библиотеке. Начнем с базовой настройки:

import logging

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

handler = logging.StreamHandler()

formatter = logging.Formatter(
fmt="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)

handler.setFormatter(formatter)
logger.addHandler(handler)

logger.info("Application started")
logger.warning("Low disk space")


Теперь вместо разрозненных сообщений у вас есть чёткая структура: время, уровень, имя логгера, текст.

---

### Логи, которые можно парсить: JSON-формат

Когда логов много, их хочется складывать в системы вроде ELK или Loki. Для этого удобно писать их в JSON.

Минимальный пример без сторонних библиотек:

import logging
import json
import sys
from datetime import datetime

class JsonFormatter(logging.Formatter):
def format(self, record):
log_record = {
"time": datetime.fromtimestamp(record.created).isoformat(),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
}
if record.exc_info:
log_record["exception"] = self.formatException(record.exc_info)
return json.dumps(log_record, ensure_ascii=False)

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

handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(JsonFormatter())
logger.addHandler(handler)

logger.info("User logged in", extra={"user_id": 42})


Обрати внимание: extra по умолчанию не попадёт в log_record. Чтобы добавлять свои поля, можно расширить JsonFormatter и пройтись по record.__dict__, отфильтровав служебные ключи.

---

### Контекст: больше данных, меньше шума

Ещё одна фишка — LoggerAdapter. Он позволяет приклеить к логам контекст (например, request_id):

import logging

base_logger = logging.getLogger("service")
base_logger.setLevel(logging.INFO)
base_handler = logging.StreamHandler()
base_handler.setFormatter(logging.Formatter("%(levelname)s | %(request_id)s | %(message)s"))
base_logger.addHandler(base_handler)

class RequestAdapter(logging.LoggerAdapter):
def process(self, msg, kwargs):
kwargs.setdefault("extra", {})
kwargs["extra"].setdefault("request_id", self.extra.get("request_id", "-"))
return msg, kwargs

logger = RequestAdapter(base_logger, {"request_id": "abc-123"})

logger.info("Processing started")


Теперь все логи в рамках запроса будут помечены одним request_id — дебагить станет гораздо проще.

---

Структурированные логи — это не «красивее вывод», а фундамент для нормальной отладки и мониторинга. Настроив форматы однажды, вы перестанете бояться смотреть на логи крупных проектов.
👍5
Как написать простой чат-сервер с использованием asyncio
Как написать простой чат-сервер с использованием asyncio

Асинхронность в Python звучит пугающе, но именно она позволяет одному скрипту одновременно обслуживать десятки клиентов. Отличный способ это почувствовать — написать свой мини-чат-сервер на asyncio.

### Идея чата

У нас будет:
- сервер, который принимает подключения;
- список активных клиентов;
- функция, рассылающая сообщения всем подключённым.

Используем высокоуровневый API: asyncio.start_server, StreamReader и StreamWriter.

### Базовый сервер

import asyncio

clients = set()

async def handle_client(reader: asyncio.StreamReader,
writer: asyncio.StreamWriter):
addr = writer.get_extra_info("peername")
clients.add(writer)
print(f"Client connected: {addr}")

try:
while data := await reader.readline():
message = data.decode().rstrip()
if not message:
continue
full_msg = f"{addr}: {message}\n"
await broadcast(full_msg, sender=writer)
except asyncio.IncompleteReadError:
pass
finally:
print(f"Client disconnected: {addr}")
clients.remove(writer)
writer.close()
await writer.wait_closed()

async def broadcast(message: str, sender: asyncio.StreamWriter | None = None):
dead = []
for client in clients:
if client is sender:
continue
try:
client.write(message.encode())
await client.drain()
except ConnectionError:
dead.add(client)
for client in dead:
clients.discard(client)

async def main():
server = await asyncio.start_server(handle_client, "127.0.0.1", 8888)
addr = ", ".join(str(sock.getsockname()) for sock in server.sockets)
print(f"Server started at {addr}")
async with server:
await server.serve_forever()

if __name__ == "__main__":
asyncio.run(main())


### Как это запустить и протестировать

1. Запускаете сервер: python chat_server.py.
2. В другом терминале подключаетесь через telnet или nc:
- telnet 127.0.0.1 8888
или
- nc 127.0.0.1 8888
3. Откройте 2–3 таких клиента и начните печатать сообщения. Всё, что отправляет один клиент, увидят остальные.

### Что важно понять

- async def и await позволяют не блокировать сервер во время ожидания ввода.
- start_server создаёт корутину, которая для каждого клиента запускает handle_client.
- broadcast показывает ключевую идею: один сервер — много клиентов, минимум блокировок.

Дальше можно добавить ники, команды (/quit, /users), простую авторизацию — всё это поверх уже готового асинхронного фундамента.
👍5
Изучение шаблонов проектирования на простых примерах: Singleton, 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 — спрятать логику создания объектов за единым интерфейсом. Оба шаблона отлично тренируют умение думать о структуре программы, а не только о том, «как бы это запустить, чтобы не упало».
👍31
Модуль statistics: работа со средним, медианой и дисперсией
Модуль statistics: работа со средним, медианой и дисперсией
Модуль 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 для отслеживания изменений в файлах
Как использовать 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 подходы
Работа с кешированием данных: file-based и memory-based подходы
================================================================

Кеширование — это способ «не думать дважды». Если результат уже посчитан, зачем снова тратить время и ресурсы? В 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 есть более элегантный инструмент — множества (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, многие задачи очистки и анализа данных становятся в разы проще.
👍51
Работа с псевдослучайным выбором с помощью random.choices
Python для начинающих: магия 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