ar
Feedback
Python для начинающих

Python для начинающих

الذهاب إلى القناة على Telegram

Python для начинающих

إظهار المزيد
1 240
المشتركون
لا توجد بيانات24 ساعات
+17 أيام
+130 أيام
أرشيف المشاركات
Python для начинающих: работаем с конфигурационными файлами через configparser Хардкодить настройки в код — как хранить пароль от квартиры на стикере у двери. Удобно ровно до первого взлома. В Python для таких вещей есть модуль configparser, который позволяет хранить настройки в отдельных .ini‑файлах: удобно, читаемо и без переписывания кода при каждом изменении. --- ### Простой пример: читаем настройки из .ini Пусть у нас есть файл settings.ini:
[database]
host = localhost
port = 5432
user = admin

[app]
debug = true
log_level = INFO
Читаем его в Python:
import configparser

config = configparser.ConfigParser()
config.read("settings.ini")

db_host = config["database"]["host"]
db_port = config.getint("database", "port")
debug_mode = config.getboolean("app", "debug")

print(db_host, db_port, debug_mode)
Заметь: - config["section"]["key"] — обычное строковое значение; - getint, getboolean, getfloat — сразу приводят к нужному типу. --- ### Значения по умолчанию Если ключа нет — можно задать дефолты, чтобы код не падал:
log_level = config.get("app", "log_level", fallback="WARNING")
fallback вернёт значение по умолчанию, если ключ отсутствует. --- ### Создаём и сохраняем конфиг из кода configparser позволяет не только читать, но и создавать .ini‑файлы:
import configparser

config = configparser.ConfigParser()

config["database"] = {
    "host": "localhost",
    "port": "3306",
    "user": "root"
}

config["app"] = {}
config["app"]["debug"] = "false"
config["app"]["log_level"] = "ERROR"

with open("generated_settings.ini", "w") as configfile:
    config.write(configfile)
Теперь у вас есть автогенерируемый файл настроек — удобно, если приложение запускается впервые и должно создать "стартовый" конфиг. --- ### Переменные и интерполяция Фишка configparser — возможность ссылаться на переменные:
[paths]
base_dir = /usr/local/app
logs_dir = %(base_dir)s/logs
import configparser

config = configparser.ConfigParser()
config.read("paths.ini")

logs_dir = config["paths"]["logs_dir"]
print(logs_dir)  # /usr/local/app/logs
--- ### Когда configparser — хороший выбор - Небольшие приложения и скрипты с простыми настройками. - Конфиги, которые редактируют люди (админы, DevOps, вы сами через год). - Сценарии, где важна читаемость и простота, а не сложные структуры. Для вложенных структур удобнее JSON или YAML, но для классических "секций и ключей" configparser — лёгкий, встроенный и очень практичный инструмент.

Работа с конфигурационными файлами с использованием configparser
Работа с конфигурационными файлами с использованием configparser

Изучение io.StringIO и BytesIO: виртуальные файлы в Python Иногда файл нужен… но создавать его на диске совсем не хочется. Лишние записи на диск, временные файлы, уборка за собой — все это раздражает. Именно тут в игру вступают io.StringIO и io.BytesIO — виртуальные файлы в памяти. ### Когда это полезно - тестировать код, который работает с файлами, без реальных файлов; - временно преобразовывать данные (текст или байты); - имитировать чтение/запись файлов в библиотеках; - экономить диск и ускорять операции, когда данные краткоживущие. --- ## StringIO: файл из строки StringIO работает с текстом (str) как обычный файловый объект.
from io import StringIO

def process_stream(stream):
    result = []
    for line in stream:
        line = line.strip()
        if line:
            result.append(line.upper())
    return result

data = "hello\n\nworld\npython\n"
fake_file = StringIO(data)

processed = process_stream(fake_file)
print(processed)  # ['HELLO', 'WORLD', 'PYTHON']
Мы передали в функцию объект, который выглядит как файл: у него есть методы read(), write(), он итерируем по строкам. Но на диске ничего не лежит — все в памяти. StringIO можно использовать и для записи:
from io import StringIO

buffer = StringIO()
buffer.write("line 1\n")
buffer.write("line 2\n")

content = buffer.getvalue()
print(content)
getvalue() — ключевой метод: он возвращает все накопленные данные. --- ## BytesIO: файл из байтов BytesIO делает то же самое, но для байтов (bytes). Это удобно, когда вы работаете с двоичными форматами, изображениями или сетевыми данными.
from io import BytesIO

raw_data = b"\x50\x59\x54\x48\x4f\x4e"  # 'PYTHON' в ASCII
byte_stream = BytesIO(raw_data)

chunk = byte_stream.read(3)
print(chunk)         # b'PYT'
print(byte_stream.read())  # b'HON'
Можно симулировать, например, загрузку файла:
from io import BytesIO

def fake_download():
    return BytesIO(b"PNGDATA...")

stream = fake_download()
data = stream.read()
print(len(data))
--- ## Полезные мелочи - У обоих есть .seek() и .tell() — для перемещения по «файлу». - Работают с with как обычные файлы. - Отлично подходят для unit-тестов: вместо реального файла подсовываете StringIO/BytesIO.
from io import StringIO

def count_lines(file_obj):
    return sum(1 for _ in file_obj)

fake = StringIO("a\nb\nc\n")
print(count_lines(fake))  # 3
StringIO и BytesIO — это быстрый способ получить все преимущества файлового интерфейса без лишнего I/O. Виртуальный файл в несколько строк кода.

Изучение io.StringIO и BytesIO: виртуальные файлы в Python
Изучение io.StringIO и BytesIO: виртуальные файлы в Python

Работа с фоновыми задачами в Python с помощью threading Иногда программе нужно делать сразу несколько вещей: скачивать данные, обрабатывать файлы, показывать прогресс пользователю. Делать это строго по очереди — значит тормозить. Здесь на сцену выходит модуль threading, позволяющий запускать задачи в отдельных потоках. ### Простейший поток Запустим функцию в фоне, пока основная программа живет своей жизнью:
import threading
import time

def background_task(name, delay):
    for i in range(3):
        time.sleep(delay)
        print(f"[{name}] step {i}")

t = threading.Thread(target=background_task, args=("worker-1", 1))
t.start()

print("Main thread continues...")
t.join()  # ждем завершения потока
print("Done")
Thread получает функцию target и аргументы args. start() запускает поток, join() — блокирует основной поток до его завершения. ### Несколько фоновых задач Часто нужно запустить не одну, а сразу пачку задач:
def download_file(file_id):
    print(f"Start downloading {file_id}")
    time.sleep(2)
    print(f"Finished {file_id}")

threads = []
for i in range(5):
    t = threading.Thread(target=download_file, args=(f"file_{i}",))
    t.start()
    threads.append(t)

for t in threads:
    t.join()

print("All downloads finished")
Файлы «скачиваются» параллельно: пока один ждет сеть, другой может работать. ### Демон-потоки: тихие работяги Иногда нужно, чтобы поток не блокировал завершение программы (логирование, метрики):
def logger():
    while True:
        time.sleep(1)
        print("logging...")

log_thread = threading.Thread(target=logger, daemon=True)
log_thread.start()

time.sleep(3)
print("Main thread exits")  # демон-поток автоматически завершится
Если daemon=True, поток живет, пока жив основной процесс. ### Безопасный доступ к общим данным Главная ловушка многопоточности — гонки данных. Два потока меняют одну переменную — результат непредсказуем. Используем Lock:
counter = 0
lock = threading.Lock()

def increment(n):
    global counter
    for _ in range(n):
        with lock:
            counter += 1

threads = []
for _ in range(5):
    t = threading.Thread(target=increment, args=(100000,))
    t.start()
    threads.append(t)

for t in threads:
    t.join()

print("Counter:", counter)
with lock: гарантирует, что только один поток за раз меняет counter. --- threading полезен, когда задачи много ждут (сеть, диск, ввод-вывод). Для тяжелых вычислений лучше посмотреть в сторону multiprocessing, но для фоновых задач, таймеров, сервисных процессов threading — быстрый и удобный инструмент, который стоит освоить одним из первых.

Работа с фоновыми задачами с помощью модуля threading
Работа с фоновыми задачами с помощью модуля threading

Как использовать dataclass и не утонуть в конструкторе Если вы когда‑нибудь писали класс с десятком полей и бесконечным __init__, то модуль dataclasses — это то, что вы искали. Он появился в Python 3.7 и превращает «болванки данных» в аккуратные, удобные структуры без тонны шаблонного кода. --- ## Что такое dataclass? dataclass — это декоратор, который автоматически генерирует: - __init__ - __repr__ - __eq__ (сравнение объектов) - и другие полезные методы Вместо громоздкого класса:
class User:
    def __init__(self, name: str, age: int, active: bool = True):
        self.name = name
        self.age = age
        self.active = active

    def __repr__(self):
        return f"User(name={self.name!r}, age={self.age!r}, active={self.active!r})"
Можно написать:
from dataclasses import dataclass

@dataclass
class User:
    name: str
    age: int
    active: bool = True

user = User("Alice", 25)
print(user)
Dataclass сгенерирует __init__ и красивый __repr__ сам. --- ## Поля по умолчанию и field() Нужно задать сложное значение по умолчанию (например, список)? Делать friends = [] в определении класса опасно — список будет общим для всех экземпляров. В dataclasses это решается через field:
from dataclasses import dataclass, field
from typing import List

@dataclass
class ChatRoom:
    title: str
    members: List[str] = field(default_factory=list)

room = ChatRoom("Python Room")
room.members.append("Alice")
room2 = ChatRoom("Another Room")
print(room.members)   # ['Alice']
print(room2.members)  # []
default_factory создает новое значение для каждого объекта. --- ## Замороженные dataclass’ы (иммутабельные объекты) Хотите, чтобы объект нельзя было менять после создания? Используйте frozen=True:
from dataclasses import dataclass

@dataclass(frozen=True)
class Point:
    x: float
    y: float

p = Point(1, 2)
# p.x = 10  # вызовет ошибку FrozenInstanceError
Это удобно для «чистых» структур данных: координат, конфигураций, ключей в словарях. --- ## Сравнение объектов Обычные классы сравниваются по идентичности (это один и тот же объект или нет). Dataclass по умолчанию сравнивает значения полей:
from dataclasses import dataclass

@dataclass
class Product:
    name: str
    price: float

p1 = Product("Book", 10.0)
p2 = Product("Book", 10.0)
print(p1 == p2)  # True
--- ## Кратко о плюсах dataclass - Меньше шаблонного кода (__init__, __repr__, __eq__ и т.д.) - Читаемые и предсказуемые классы данных - Простая работа со значениями по умолчанию - Поддержка типов (аннотации обязательны и делают код понятнее) - Возможность делать объекты иммутабельными dataclass — идеальный инструмент, когда вам нужен «умный словарь» с атрибутами и типами, но без лишней головной боли.

Как использовать датаклассы и в чем их преимущества
Как использовать датаклассы и в чем их преимущества

Создание интерактивных терминальных интерфейсов с Prompt Toolkit Большинство начинающих знакомится с Python через input() и print(). Но как только хочется чего-то «живого» в терминале — история команд, подсветка, автодополнение — стандартных средств уже мало. Здесь на сцену выходит библиотека prompt_toolkit. Это мощный конструктор для создания интерактивных CLI-приложений: от умных консолей до мини‑IDE прямо в терминале. --- ### Установка
pip install prompt_toolkit
--- ### Пример 1: улучшенный input() с историей и автодополнением
from prompt_toolkit import prompt
from prompt_toolkit.completion import WordCompleter

commands = ["start", "stop", "status", "restart"]
command_completer = WordCompleter(commands, ignore_case=True)

while True:
    user_input = prompt("cmd> ", completer=command_completer)
    if user_input == "exit":
        break
    print(f"You entered: {user_input}")
Что здесь полезного: - автодополнение по Tab из списка commands, - история введённых команд, - редактирование строки как в нормальных шеллах (стрелки, Home/End и т.д.) — «из коробки». --- ### Пример 2: подсветка синтаксиса Можно превратить ввод в мини‑редактор с подсветкой кода:
from prompt_toolkit import prompt
from prompt_toolkit.lexers import PygmentsLexer
from pygments.lexers import PythonLexer

code = prompt(
    "Enter Python code:\n",
    lexer=PygmentsLexer(PythonLexer),
    multiline=True
)

print("You wrote:")
print(code)
Особенности: - multiline=True позволяет писать блок кода (несколько строк). - PygmentsLexer + PythonLexer дают подсветку синтаксиса прямо при вводе. --- ### Пример 3: валидация ввода на лету
from prompt_toolkit import prompt
from prompt_toolkit.validation import Validator, ValidationError

class IntValidator(Validator):
    def validate(self, document):
        text = document.text
        if not text.isdigit():
            raise ValidationError(
                message="Please enter an integer",
                cursor_position=len(text)
            )

age = prompt("Enter your age: ", validator=IntValidator(), validate_while_typing=True)
print(f"Age: {age}")
Плюсы: - пользователь сразу видит ошибку (сообщение + позиция курсора), - валидатор легко заменить любым своим (email, путь к файлу, диапазоны и т.д.). --- ### Зачем это всё новичку? Prompt Toolkit даёт быстрый путь от «скучного консольного ввода» к удобным инструментам: - свои REPL‑консоли, - интерактивные помощники и установщики, - учебные мини‑шеллы для отладки идей. Один модуль — и ваш терминал перестаёт быть просто чёрным окном, а превращается в полноценный интерфейс для пользователя.

Создание интерактивных терминальных интерфейсов с библиотекой Prompt Toolkit
Создание интерактивных терминальных интерфейсов с библиотекой Prompt Toolkit

Введение в работу с protobuf: быстрая сериализация данных 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-разработчика.

Введение в работу с protobuf: быстрая сериализация данных
Введение в работу с protobuf: быстрая сериализация данных

Как протестировать производительность кода с 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 не даст вам абсолютного «идеального» времени, но отлично показывает, какой из двух вариантов кода быстрее и во сколько раз. Это уже достаточно, чтобы принимать осознанные решения об оптимизации.

Как протестировать производительность кода c timeit
Как протестировать производительность кода c timeit

Создание API‑клиентов: практика на примере GitHub API Когда вы научились писать функции и циклы, следующим логичным шагом становится общение с внешним миром: сервисами, сайтами, ботами. Для этого нужны 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. Явная обработка ошибок и лимитов. Попробуйте расширить клиент: добавить получение коммитов, фильтрацию публичных/приватных репозиториев (для этого понадобится токен), поиск репозиториев по языку. Так вы почувствуете, как из нескольких функций рождается удобный инструмент для автоматизации работы с реальными сервисами.

Создание API-клиентов: практика на примере GitHub API
Создание API-клиентов: практика на примере GitHub API

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 — отличный инструмент, когда нужно не просто «случайно», а «случайно с контролем»: симуляции, простые игровые механики, генерация тестовых данных — везде, где вероятность чего-то должна быть не только ощущаемой, но и задаваемой.

Работа с псевдослучайным выбором с помощью random.choices
Работа с псевдослучайным выбором с помощью random.choices

Как исключить дубли в данных с помощью множеств Дубли в данных — классическая боль новичка: выгрузили список 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, многие задачи очистки и анализа данных становятся в разы проще.

Как исключить дубли в данных с помощью множеств
Как исключить дубли в данных с помощью множеств