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

Если вам нужны стек или очередь в Python, не спешите писать свои классы. В стандартной библиотеке уже есть мощный инструмент — collections.deque. Это двусторонняя очередь, но из неё легко сделать и стек, и обычную очередь.

---

## Быстрый старт

from collections import deque

dq = deque() # пустая deque
dq = deque([1, 2, 3]) # инициализация с данными


deque оптимизирован под операции добавления/удаления с обоих концов — в отличие от обычного списка, где pop(0) и insert(0, x) работают медленно.

---

## deque как стек (LIFO)

Стек — это принцип “последним пришёл — первым вышел” (LIFO). Для стека нам нужны две операции: положить и достать с одного и того же конца.

from collections import deque

stack = deque()

# кладём в стек
stack.append("page_1")
stack.append("page_2")
stack.append("page_3")

# достаём из стека
last_page = stack.pop() # "page_3"
prev_page = stack.pop() # "page_2"


Здесь append() и pop() работают с “хвостом” очереди. Именно так строится история переходов в браузере, отмена действий в редакторе и т.п.

---

## deque как очередь (FIFO)

Очередь — “первым пришёл — первым вышел” (FIFO). Добавляем в конец, забираем из начала.

from collections import deque

queue = deque()

# приходят задачи
queue.append("task_1")
queue.append("task_2")
queue.append("task_3")

# обрабатываем в порядке поступления
first = queue.popleft() # "task_1"
second = queue.popleft() # "task_2"


Ключевой метод — popleft(): быстро забирает элемент с начала. Для списка аналог pop(0) был бы заметно медленнее.

---

## Двусторонняя очередь

Иногда удобно, что deque — двусторонняя:

from collections import deque

dq = deque([2, 3])

dq.appendleft(1) # слева: [1, 2, 3]
dq.append(4) # справа: [1, 2, 3, 4]

left = dq.popleft() # 1
right = dq.pop() # 4


Так можно, например, реализовать “слайдящуюся” историю последних N элементов.

---

## Ограниченный размер: maxlen

deque умеет автоматически забывать старые элементы:

from collections import deque

history = deque(maxlen=3)

for i in range(5):
history.append(i)

print(history) # deque([2, 3, 4], maxlen=3)


Полезно для логов, последних измерений, кэшей.

---

Главное: запомните четыре метода — append, appendleft, pop, popleft. С их помощью вы можете построить стек, очередь и даже более сложные структуры, не выходя за рамки стандартной библиотеки Python.
👍2
Создание CLI-интерфейсов с библиотекой argparse
Python для начинающих: создаём удобные CLI-интерфейсы с argparse

Большинство утилит в терминале — это просто программы с аргументами командной строки. python script.py --input data.txt --verbose выглядит профессионально и удобно. За этим стоит стандартная библиотека argparse, которую многие новички игнорируют, а зря.

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

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

import argparse

def main():
parser = argparse.ArgumentParser(
description="Simple greeting script"
)
parser.add_argument(
"name",
help="User name to greet"
)
parser.add_argument(
"-t", "--times",
type=int,
default=1,
help="How many times to print greeting"
)

args = parser.parse_args()

for _ in range(args.times):
print(f"Hello, {args.name}!")

if __name__ == "__main__":
main()


Теперь в терминале:

python greet.py Alice
python greet.py Alice --times 3


argparse сам:
- парсит аргументы,
- проверяет типы (type=int),
- подставляет значения по умолчанию (default=1),
- показывает аккуратную справку: python greet.py -h.

### Флаги и режимы работы

Добавим флаг, который ничего не принимает, но меняет поведение:

import argparse

def main():
parser = argparse.ArgumentParser(
description="File size checker"
)
parser.add_argument("path", help="Path to file")
parser.add_argument(
"-q", "--quiet",
action="store_true",
help="Print only raw size in bytes"
)

args = parser.parse_args()

import os
size = os.path.getsize(args.path)

if args.quiet:
print(size)
else:
print(f"File: {args.path}")
print(f"Size: {size} bytes")

if __name__ == "__main__":
main()


Ключевой момент — action="store_true": если флаг указан, в args.quiet будет True, иначе False.

### Подкоманды: как git clone, git status

Аргументы можно группировать в подкоманды:

import argparse

def cmd_add(args):
print(args.x + args.y)

def cmd_mul(args):
print(args.x * args.y)

def main():
parser = argparse.ArgumentParser(
description="Simple calculator"
)
subparsers = parser.add_subparsers(
dest="command",
required=True
)

parser_add = subparsers.add_parser("add", help="Add numbers")
parser_add.add_argument("x", type=int)
parser_add.add_argument("y", type=int)
parser_add.set_defaults(func=cmd_add)

parser_mul = subparsers.add_parser("mul", help="Multiply numbers")
parser_mul.add_argument("x", type=int)
parser_mul.add_argument("y", type=int)
parser_mul.set_defaults(func=cmd_mul)

args = parser.parse_args()
args.func(args)

if __name__ == "__main__":
main()


Теперь:

python calc.py add 2 3   # 5
python calc.py mul 2 3 # 6


Так строятся целые консольные инструменты: один файл — множество команд.

### Почему стоит привыкнуть к argparse

- Встроен в стандартную библиотеку, без лишних зависимостей.
- Автоматически генерирует --help.
- Делает интерфейс предсказуемым и профессиональным.
- Легко расширяется: типы, значения по умолчанию, подкоманды, обязательные/необязательные аргументы.

Один раз освоив argparse, вы перестаёте писать input() в каждом втором скрипте и начинаете создавать настоящие консольные утилиты. Это маленький шаг в коде, но большой шаг к тому, чтобы ваши программы выглядели как «настоящие инструменты», а не учебные примеры.
🔥31
Преобразование строк и чисел с помощью форматирования f-строк
Преобразование строк и чисел с помощью форматирования f-строк

Если вы все еще конкатенируете строки через +, самое время остановиться. В Python есть более мощный и удобный инструмент — f-строки (formatted string literals). Они не только делают код читаемее, но и позволяют красиво преобразовывать числа и строки буквально “на лету”.

---

### Базовый синтаксис

F-строка — это строка с буквой f перед кавычками. Внутри фигурных скобок {} можно писать выражения:

name = "Alice"
age = 25
text = f"Name: {name}, age: {age}"
print(text) # Name: Alice, age: 25


Уже лучше, чем "Name: " + name + ", age: " + str(age).

---

### Форматирование чисел

Главная магия начинается, когда нужно красиво отобразить числа.

Количество знаков после запятой:

price = 12.34567
print(f"{price:.2f}") # 12.35
print(f"{price:.0f}") # 12


.2f — два знака после запятой, .0f — без дробной части.

Разделение разрядов:

big_num = 1234567890
print(f"{big_num:,}") # 1,234,567,890
print(f"{big_num:_}") # 1_234_567_890


Запятая или нижнее подчеркивание помогают “читать” большие числа.

Проценты:

ratio = 0.0789
print(f"{ratio:.2%}") # 7.89%


---

### Выравнивание и ширина

Можно управлять тем, как текст и числа “лежат” в строке — удобно для табличного вывода.

items = [("Apple", 3.5), ("Banana", 12.0), ("Kiwi", 0.95)]

for name, price in items:
print(f"{name:<10} | {price:>7.2f}")


- <10 — выравнивание по левому краю, ширина 10 символов
- >7.2f — по правому краю, ширина 7, два знака после запятой

Вывод будет аккуратно выровнен по столбцам.

---

### Форматирование чисел в разных системах счисления

num = 42
print(f"bin: {num:b}") # bin: 101010
print(f"hex: {num:x}") # hex: 2a
print(f"HEX: {num:#X}") # HEX: 0X2A


# добавляет префикс 0b, 0x, 0o.

---

### Вложенные выражения и функции

В f-строках можно вызывать функции и писать выражения:

import math

x = 2
print(f"sqrt({x}) = {math.sqrt(x):.3f}")
print(f"{x}! = {math.factorial(x)}")


---

### Быстрая диагностика: =

С Python 3.8 появилось удобство для отладки: =

a = 7
b = 3
print(f"{a=}, {b=}, sum={a + b}")
# a=7, b=3, sum=10


---

F-строки — это компактный, выразительный и очень мощный способ преобразовывать и форматировать строки и числа. Освоив их, вы заметите, насколько чище и понятнее станет ваш код.
👍6
Создание таплов и однострочных функций с помощью lambda и map
Создание таплов и однострочных функций с помощью lambda и map

Python любит, когда код короткий и понятный. Сегодня разберём связку, которая идеально подходит для “быстрых” преобразований данных: lambda + map + кортежи (tuples).

---

### 1. Что такое lambda и зачем она нужна

lambda — это способ создать функцию “на лету”, прямо внутри выражения, без def и имени.

Обычная функция:

def square(x):
return x ** 2


То же самое с lambda:

square = lambda x: x ** 2


А можно вообще не присваивать её переменной, а использовать сразу в выражении — и вот тут в игру вступает map.

---

### 2. map: применяем функцию ко всем элементам

map(func, iterable) берёт каждый элемент из iterable и прогоняет его через func.
Типичный пример:

nums = [1, 2, 3, 4]
result = map(lambda x: x * 10, nums)
print(list(result)) # [10, 20, 30, 40]


Обрати внимание: map возвращает итератор, поэтому часто его оборачивают в list() или tuple().

---

### 3. Создаём таплы с помощью map и lambda

Допустим, у нас есть список чисел, и мы хотим получить кортеж пар (число, его квадрат):

nums = [1, 2, 3, 4, 5]

pairs = tuple(
map(lambda x: (x, x ** 2), nums)
)

print(pairs)
# ((1, 1), (2, 4), (3, 9), (4, 16), (5, 25))


Здесь важно:

- lambda x: (x, x ** 2) возвращает кортеж из двух элементов.
- map(...) создаёт поток таких кортежей.
- tuple(...) собирает их в один большой кортеж.

---

### 4. Комбинируем несколько итерируемых объектов

map может работать сразу с несколькими последовательностями. Например, создадим кортеж таплов из координат:

xs = [0, 1, 2]
ys = [10, 11, 12]

points = tuple(
map(lambda x, y: (x, y), xs, ys)
)

print(points)
# ((0, 10), (1, 11), (2, 12))


Так можно элегантно “склеивать” данные без явных циклов.

---

### 5. Однострочные конвейеры преобразований

Связка map + lambda хорошо работает как мини-конвейер обработки данных:

raw_data = ["1", "2", "3", "4"]

processed = tuple(
map(lambda x: int(x) * 2, raw_data)
)

print(processed)
# (2, 4, 6, 8)


Считываем строки → сразу превращаем в числа → сразу умножаем.

---

### Когда это оправдано

- Нужно быстро преобразовать последовательность.
- Логика простая и умещается в одну строку.
- Нужен результат именно в виде кортежей (например, для неизменяемых данных или в качестве ключей словаря).

Если выражение становится громоздким и трудно читаемым — лучше вернуться к обычным функциям и циклам. Но для небольших преобразований lambda + map + tuple даёт лаконичный и выразительный код, который отлично вписывается в стиль Python.
👍2🔥1
Объединение и фильтрация данных с filter, map, zip
Объединение и фильтрация данных с filter, map, zip

Представьте, что у вас есть конвейер обработки данных. Вход — «сырые» списки, выход — аккуратный результат. В Python роль такого конвейера идеально играют функции filter, map и zip. Разберём, как их сочетать так, чтобы код был короче, понятнее и «питонистее».

---

### filter: оставляем только нужное

filter(func, iterable) пропускает через себя элементы, оставляя только те, для которых func возвращает True.

numbers = [10, -3, 0, 25, -7, 8]

def is_positive(x):
return x > 0

positive_numbers = list(filter(is_positive, numbers))
print(positive_numbers) # [10, 25, 8]


То же самое через lambda:

positive_numbers = list(filter(lambda x: x > 0, numbers))


---

### map: трансформируем элементы

map(func, iterable) применяет функцию к каждому элементу.

numbers = [1, 2, 3, 4]

squared = list(map(lambda x: x ** 2, numbers))
print(squared) # [1, 4, 9, 16]


Комбинация с filter даёт уже мини-пайплайн:

numbers = [-3, -1, 0, 1, 2, 3]

result = list(
map(
lambda x: x ** 2,
filter(lambda x: x > 0, numbers)
)
)
print(result) # [1, 4, 9]


Сначала отфильтровали положительные, потом возводим их в квадрат.

---

### zip: объединяем несколько источников данных

zip склеивает элементы из нескольких итерируемых объектов по позициям.

names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]

paired = list(zip(names, scores))
print(paired) # [('Alice', 85), ('Bob', 92), ('Charlie', 78)]


На основе zip удобно строить структуры данных:

students = [
{"name": name, "score": score}
for name, score in zip(names, scores)
]
print(students)
# [{'name': 'Alice', 'score': 85}, ...]


---

### Все вместе: мини-аналитика

Допустим, у нас есть имена, оценки и флаг «сдал экзамен» (True/False). Нужно оставить только тех, кто сдал, и взять их имена и удвоенные баллы (например, за бонус).

names = ["Alice", "Bob", "Charlie", "Diana"]
scores = [85, 40, 73, 95]
passed = [True, False, True, True]

data = zip(names, scores, passed)

passed_transformed = list(
map(
lambda item: (item[0], item[1] * 2),
filter(lambda item: item[2], data)
)
)

print(passed_transformed)
# [('Alice', 170), ('Charlie', 146), ('Diana', 190)]


zip объединяет разрозненные списки в единый поток кортежей, filter выбрасывает тех, кто не сдал, map меняет формат результата и пересчитывает баллы.

---

filter, map и zip — это кирпичики для построения аккуратных конвейеров обработки данных. В связке они позволяют писать код, который одновременно лаконичен и легко читается как последовательность шагов над данными.
👍3
Как использовать структуру данных defaultdict для подсчета значений
### Как использовать defaultdict для подсчета значений

Если вы когда‑нибудь считали что‑то с помощью словаря — количество слов, кликов, покупок, — вы наверняка писали что‑то вроде:

counts = {}
for item in items:
if item in counts:
counts[item] += 1
else:
counts[item] = 1


Работает, но выглядит громоздко. В Python есть инструмент, который делает это элегантнее — collections.defaultdict.

---

## Что такое defaultdict

Обычный словарь выбрасывает KeyError, если обратиться к несуществующему ключу.
defaultdict вместо ошибки автоматически создаёт значение по умолчанию.

Импорт:

from collections import defaultdict


Создание:

from collections import defaultdict

counts = defaultdict(int) # int() -> 0


Теперь при первом обращении к counts[key] под капотом создаётся 0, и вы можете сразу увеличивать счётчик:

for item in items:
counts[item] += 1


Никаких if, всё уже есть.

---

## Пример 1: Подсчёт слов в тексте

from collections import defaultdict

text = "banana apple banana orange apple banana"
words = text.split()

word_counts = defaultdict(int)

for word in words:
word_counts[word] += 1

print(dict(word_counts))
# {'banana': 3, 'apple': 2, 'orange': 1}


int как фабрика значений даёт 0 по умолчанию. Увеличиваем — и счётчик растёт.

---

## Пример 2: Группировка значений по ключу

defaultdict полезен не только с числами. Частый сценарий — группировка.

from collections import defaultdict

users = [
("alice", "admin"),
("bob", "user"),
("charlie", "admin"),
("david", "user"),
]

grouped = defaultdict(list)

for name, role in users:
grouped[role].append(name)

print(dict(grouped))
# {'admin': ['alice', 'charlie'], 'user': ['bob', 'david']}


Здесь list() создаёт пустой список при первом доступе, и мы просто делаем .append().

---

## Пример 3: Подсчёт сумм по категориям

from collections import defaultdict

sales = [
("books", 120),
("electronics", 300),
("books", 80),
("games", 150),
]

totals = defaultdict(float)

for category, amount in sales:
totals[category] += amount

print(dict(totals))
# {'books': 200.0, 'electronics': 300.0, 'games': 150.0}


float() даёт 0.0, удобно для денег и чисел с плавающей точкой.

---

## Когда defaultdict особенно полезен

- Подсчёт частот (слов, кликов, событий).
- Группировка данных (пользователи по ролям, товары по категориям).
- Накопление сумм и списков без постоянных проверок if key in dict.

Формула запоминания простая:
“Если я пишу if key in d: ... else: ... — возможно, мне нужен defaultdict.”
👍2
Отладка Python-кода с помощью встроенного модуля pdb
Отладка Python-кода с помощью встроенного модуля pdb

Начинающие часто боятся отладки как чего-то «магического». На самом деле pdb — это всего лишь интерактивная пауза в вашем коде, где вы можете посмотреть, что происходит «под капотом», шаг за шагом.

---

### Зачем нужен pdb?

print-отладка быстро превращает код в кашу. pdb позволяет:

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

---

### Самый простой старт: breakpoint()

С Python 3.7 появился встроенный вызов:

def divide(a, b):
result = a / b
return result

def main():
x = 10
y = 0
breakpoint() # здесь выполнение остановится
print(divide(x, y))

if __name__ == "__main__":
main()


Запускаем скрипт в терминале как обычно:

python script.py


В месте breakpoint() вы попадете в интерактивную консоль pdb с приглашением вида (Pdb).

---

### Базовые команды pdb

Находясь в (Pdb), попробуйте:

- l — показать фрагмент исходника (list).
- n — выполнить следующую строку (next).
- s — шагнуть внутрь функции (step).
- c — продолжить выполнение до следующей точки останова (continue).
- p x — вывести значение переменной x (print).
- q — выйти из отладки (quit).

Например, в нашем примере:

(Pdb) p x
10
(Pdb) p y
0
(Pdb) p divide(x, y)
ZeroDivisionError: division by zero


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

---

### Ручной запуск: python -m pdb

Иногда удобно запустить отладчик с самого старта:

python -m pdb script.py


Вы окажетесь в pdb ещё до выполнения первой строки. Тут можно заранее поставить точки останова:

(Pdb) b main
(Pdb) c


Команда b main ставит брейкпоинт на вход в функцию main.

---

### Отладка циклов и логики

Пример с циклом, где что-то идёт не так:

def sum_positive(numbers):
total = 0
for n in numbers:
if n < 0:
breakpoint()
total += n
return total

print(sum_positive([1, 2, -5, 3]))


Когда цикл дойдёт до -5, pdb остановится. Можно исследовать:

(Pdb) p n
-5
(Pdb) p total
3
(Pdb) p numbers
[1, 2, -5, 3]


Так легко понять, что отрицательные числа вообще не должны участвовать в сумме — и исправить логику.

---

### Несколько практических подсказок

- Не оставляйте breakpoint() в продакшене: вы можете обернуть его в условие по переменной окружения.
- Научитесь минимуму команд: l, n, s, c, p, q — этого достаточно, чтобы комфортно отлаживать большинство багов.
- pdb работает везде, где есть консоль: локально, в Docker-контейнере, на сервере.

pdb — лучший способ превратить «странные баги» в понятные и воспроизводимые ситуации. Чем раньше вы привыкнете к нему, тем быстрее перестанете бояться ошибок и начнёте управлять ими.
👍31
Работа с локализацией и форматами: модуль locale
Работа с локализацией и форматами: модуль locale

Если ваш скрипт должен показывать даты, деньги и числа «по‑местному», голый print() быстро перестаёт хватать. В России — запятая в дробях и пробелы между разрядами, в США — точка и запятая, в Германии — наоборот. Всё это умеет модуль locale.

---

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

import locale

# Устанавливаем локаль по умолчанию системы
locale.setlocale(locale.LC_ALL, '')

current_locale = locale.getlocale()
print("Current locale:", current_locale)


LC_ALL — сразу все категории (числа, даты, деньги и т.д.). Можно настраивать точечно: LC_NUMERIC, LC_TIME, LC_MONETARY и др.

---

### Форматирование чисел

import locale

locale.setlocale(locale.LC_ALL, 'ru_RU.UTF-8') # может отличаться в вашей системе

value = 1234567.89
formatted = locale.format_string('%.2f', value, grouping=True)
print(formatted) # например: 1 234 567,89


Параметр grouping=True включает разделители разрядов. Если сменить локаль:

locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
print(locale.format_string('%.2f', value, grouping=True)) # 1,234,567.89


Одна и та же величина — разное отображение.

---

### Валюта по‑местному

import locale

locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8')

price = 1999.99
formatted_price = locale.currency(price, grouping=True)
print(formatted_price) # например: 1.999,99 €


locale.currency() учитывает символ валюты, позицию знака, пробелы и разделители разрядов.

---

### Парсинг строк в числа

Иногда нужно не только выводить, но и «понимать» локальные числа:

import locale

locale.setlocale(locale.LC_ALL, 'fr_FR.UTF-8')

s = "1 234,56"
x = locale.atof(s) # string -> float с учётом локали
print(x) # 1234.56 (обычный Python float)


atof() и atoi() разбирают строки по локальным правилам — важно при вводе данных от пользователя из разных стран.

---

### Полезные выводы

- locale не меняет сами числа, он управляет представлением (форматом).
- Локаль влияет на весь процесс в пределах программы, так что в больших проектах её обычно настраивают централизованно.
- Для кроссплатформенности проверяйте, что нужная локаль есть в системе, и держите запасной вариант (например, C или en_US.UTF-8).

Модуль locale — это способ сделать ваш код дружелюбным к пользователю вне зависимости от того, где он живёт и какой у него разделитель дробной части.
👍3
Основы проверки кода с использованием pylint и flake8
Основы проверки кода с использованием pylint и flake8

Когда начинаешь писать на Python, код обычно «работает — и ладно». Но довольно быстро становится ясно: читать его через неделю невозможно даже самому автору. Тут на сцену выходят линтеры — инструменты автоматической проверки стиля и потенциальных ошибок. Два самых популярных: pylint и flake8.

---

### Зачем вообще нужны линтеры?

Линтеры помогают:

- находить опечатки и потенциальные баги (неиспользуемые переменные, лишние импорты);
- поддерживать единый стиль кода (отступы, длина строк, имена переменных);
- учиться писать «питоничный» код — по PEP 8 и здравому смыслу.

---

### Установка

pip install pylint flake8


Проверка файла:

pylint my_script.py
flake8 my_script.py


---

### Простой пример «плохого» кода

# file: bad_example.py

import os, sys

def add(a,b):
return a+ b

def main():
result = add(2,2)
print("Result:",result)

main()


Типичные проблемы, которые найдут линтеры:

- несколько импортов в одной строке (import os, sys);
- несогласованные отступы;
- отсутствие пробелов вокруг операторов;
- вызов main() без проверки if __name__ == "__main__":.

---

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

# file: good_example.py

import os
import sys


def add(a: int, b: int) -> int:
return a + b


def main() -> None:
result = add(2, 2)
print("Result:", result)


if __name__ == "__main__":
main()


Теперь flake8 в основном будет молчать, а pylint может ещё подсказать, что os и sys нигде не используются.

---

### В чём разница между pylint и flake8?

- flake8 — лёгкий и быстрый. Фокусируется на стиле и базовых ошибках.
- pylint — гораздо более строгий: анализирует структуру проекта, связи между модулями, даёт «оценку» кода, умеет ловить более сложные проблемы.

Часто их используют вместе: flake8 — для стилистики, pylint — для более глубокой проверки.

---

### Настройка под себя

Оба инструмента можно «приручить» конфигами:

- pylint: файл .pylintrc
- flake8: файл .flake8 или раздел [flake8] в setup.cfg

Например, увеличить допустимую длину строки:

# .flake8
[flake8]
max-line-length = 100


---

Лучший момент начать использовать линтеры — сейчас. Они быстро превращают хаотичный учебный код в аккуратный и читаемый, а заодно приучают к хорошему стилю, почти как строгий, но полезный наставник.
👍2
Как организовать модульную структуру проекта в Python
Как организовать модульную структуру проекта в Python

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

---

### Что такое модуль и пакет?

- Модуль — обычный .py-файл. Например: utils.py.
- Пакет — папка с Python-модулями. Обычно содержит файл __init__.py (в современных версиях Python он не обязателен, но лучше его иметь для явности).

Простейшая структура проекта может выглядеть так:

my_project/
main.py
models.py
utils.py


Но уже на среднем проекте удобнее перейти к пакетам:

my_project/
main.py
app/
__init__.py
models.py
services.py
utils.py


---

### Пример: мини-приложение с модулями

Представим, что пишем упрощенную систему заказов.

app/models.py:

from dataclasses import dataclass

@dataclass
class Order:
id: int
amount: float
is_paid: bool = False


app/services.py:

from .models import Order
from .utils import apply_discount

def process_order(order: Order, discount: float) -> Order:
order.amount = apply_discount(order.amount, discount)
order.is_paid = True
return order


app/utils.py:

def apply_discount(amount: float, discount: float) -> float:
if not 0 <= discount <= 1:
raise ValueError("Invalid discount")
return round(amount * (1 - discount), 2)


main.py:

from app.models import Order
from app.services import process_order

def main():
order = Order(id=1, amount=100.0)
order = process_order(order, discount=0.15)
print(order)

if __name__ == "__main__":
main()


Мы разделили:

- models — только данные.
- services — бизнес-логика.
- utils — вспомогательные функции.

Каждый файл отвечает за свою зону, и код перестает быть «простыней».

---

### Относительные и абсолютные импорты

Внутри пакета удобно использовать относительные импорты:

from .models import Order
from .utils import apply_discount


А снаружи — абсолютные:

from app.services import process_order


Это делает структуру понятной: глядя на импорт, сразу видно, откуда что приходит.

---

### Зачем все это?

- Проще тестировать: можно импортировать один модуль и тестировать его изолированно.
- Легче находить код: логика не размазана по одному файлу.
- Удобно расширять проект: добавили новый модуль — остальное не пострадало.

Модульная структура — это не «красиво», это «жить можно», когда код растет. Начать лучше сразу, даже если проект пока умещается в один файл.
🔥2👍1
Создание собственных исключений для понятной обработки ошибок