feat: implement legacy shop management system and add task documentation files
CI Check / lint-and-check (push) Failing after 9s

This commit is contained in:
Dmitry Belan
2026-07-18 12:25:32 +03:00
parent 7e8afd2a7e
commit ee235ece10
14 changed files with 953 additions and 1 deletions
+18
View File
@@ -0,0 +1,18 @@
[flake8]
max-line-length = 80
import-order-style = google
application-import-names = shop
exclude =
.git,
__pycache__,
build,
dist,
.venv,
venv,
.mypy_cache,
.pytest_cache
ignore =
# N802: function name should be lowercase (we want the student to fix this, but standard pep8 has this. Let's not ignore it so they have to fix it!)
# T201, T001: print found (they will need prints for CLI, so we can ignore print check T201 if they use print check plugins, but default flake8 doesn't check prints unless we use flake8-print)
# We will keep it minimal so flake8 checks standard styling, naming, and import order.
E121, E123, E126, E226, E24, E704, W503, W504
+22
View File
@@ -0,0 +1,22 @@
### Задача 1. Калькулятор статистики (Функции, Списки, Циклы)
Напишите программу из 4 функций для анализа числовых данных:
1. `filter_numbers(data)` — принимает список `data`, который может содержать числа, строки, `None`. Возвращает новый список только с числами (`int` или `float`).
2. `calculate_average(numbers)` — принимает список чисел и возвращает их среднее арифметическое. Если список пуст, возвращает `0`.
3. `find_extremes(numbers)` — принимает список чисел и возвращает кортеж `(минимум, максимум)`. Если список пуст, возвращает `(None, None)`.
4. `analyze_data(data)` — принимает список `data`, фильтрует числа через `filter_numbers`, затем вызывает `calculate_average` и `find_extremes`. Выводит результаты в формате:
```
Среднее: 42.5
Минимум: 10
Максимум: 100
```
**Тесты:**
1. Создайте список с числами, строками и `None`: `[10, "test", 50, None, 100, "data", 25]`
2. Вызовите `analyze_data` и проверьте результат
3. Проверьте работу с пустым списком
+25
View File
@@ -0,0 +1,25 @@
### Задача 10. Многопоточность и очереди (Threading, Queue, Синхронизация)
Напишите программу для обработки задач в нескольких потоках:
1. **Класс `Worker`:**
- В `__init__(self, name, queue)` сохраните имя рабочего и очередь задач
- Метод `run(self)` — бесконечный цикл: берет задачу из очереди (`queue.get()`), обрабатывает её (просто выводит `"Worker {name} обрабатывает: {task}"`), делает `time.sleep(1)` для имитации работы, затем вызывает `queue.task_done()`
2. `create_workers(count, queue)` — создает `count` объектов `Worker`, запускает каждого в отдельном потоке (`threading.Thread`) с `daemon=True`. Возвращает список потоков.
3. `process_tasks(tasks, worker_count=3)` — принимает список задач:
- Создает очередь `queue.Queue()`
- Добавляет все задачи в очередь
- Создает рабочих через `create_workers`
- Ждет завершения всех задач через `queue.join()`
- Выводит *"Все задачи обработаны"*
**Тесты:**
1. Создайте список из 10 задач: `["Задача 1", "Задача 2", ..., "Задача 10"]`
2. Запустите обработку с 3 рабочими потоками
3. Убедитесь, что задачи распределяются между потоками
4. Замерьте время выполнения (должно быть ~4 секунды для 10 задач с 3 потоками)
**Подсказка:** Импортируйте `import threading`, `import queue`, `import time`.
+78
View File
@@ -0,0 +1,78 @@
### Задача 11. Рефакторинг системы управления интернет-магазином
**Контекст**
Вам достался legacy-код системы управления интернет-магазином, расположенный в файле `legacy_shop.py`.
Код является абсолютно **монолитным**: вся логика (регистрация пользователей, авторизация, корзина, оформление заказов, расчёт доставки для разных городов, применение промокодов и админ-отчёты) написана сплошным линейным потоком внутри интерактивного CLI-интерфейса. В программе **нет ни одной функции и ни одного класса**. При этом код содержит огромное количество дублирования логики (copy-paste), нарушает стандарты PEP 8, не имеет обработки ошибок и сложен в поддержке.
---
**Ваша задача:**
Провести полный рефакторинг этого кода, разделив его на модули, применив объектно-ориентированное программирование (ООП) и исправив стиль кодирования.
#### Шаг 1. Анализ и исправление стиля (PEP 8)
- Приведите весь код в соответствие со стандартами **PEP 8**:
- Устраните отсутствие пробелов вокруг операторов (например, `x=y+z`).
- Исправьте именование переменных на `snake_case`.
- Уберите «волшебные числа» и жестко захардкоженные пароли (например, пароль админа), вынеся их в константы или конфигурацию.
#### Шаг 2. Декомпозиция логики в функции
- Найдите все повторяющиеся и логически обособленные куски кода и вынесите их в отдельные переиспользуемые функции с понятными названиями.
- **Особое внимание уделите:**
- Валидации полей при регистрации пользователя (сейчас проверки скопированы для каждого поля отдельно).
- Расчету стоимости доставки (уберите дублирование формул для 10+ городов, обобщите логику).
- Применению промокодов (уберите длинный `if-elif` блок, заменив его на структуру данных/словарь соответствий или единую формулу).
- Валидации чисел при вводе количества товаров и цен.
#### Шаг 3. Проектирование ООП-архитектуры
#### Шаг 3. Проектирование ООП-архитектуры
- Вместо хранения сырых словарей (`dict`) и глобальных переменных спроектируйте систему классов. **Создавайте классы там, где они действительно выгодны** (например, там, где необходимо инкапсулировать состояние, защитить данные от некорректного изменения или связать данные с поведением). Подумайте, для каких сущностей достаточно простых структур данных/функций, а для каких необходимы полноценные классы:
- `Product` — модель товара (id, название, категория, цена, остаток на складе, sku, производитель).
- `User` — модель пользователя. Подумайте над наследованием: например, выделите базовый класс `User` и наследников `Customer` (клиент) и `Admin` (администратор) с разным уровнем прав, если это упростит разграничение доступа.
- `Cart` — корзина покупателя, управляющая добавлением товаров, изменением количества и подсчетом промежуточного итога (здесь класс выгоден для управления состоянием).
- `Order` — заказ пользователя (детали доставки, примененная скидка, стоимость доставки, итоговая сумма, статус).
- `Database` — сервис для работы с файлами базы данных (`users.json`, `products.json`, `orders.json`). Должен инкапсулировать логику чтения и записи.
- `Shop` — центральный сервис-фасад, координирующий бизнес-логику магазина.
#### Шаг 4. Создание модульной файловой структуры
Разделите проект на логические части и файлы. Итоговая структура должна выглядеть следующим образом:
```
shop/
├── __init__.py
├── models/
│ ├── __init__.py
│ ├── product.py
│ ├── user.py
│ ├── order.py
│ └── cart.py
├── services/
│ ├── __init__.py
│ ├── shop.py
│ └── database.py
├── utils/
│ ├── __init__.py
│ └── validators.py
└── main.py
```
- В `main.py` должен остаться только запуск приложения и минималистичный интерактивный CLI-интерфейс, вызывающий методы класса `Shop`.
#### Шаг 5. Качество кода и надежность
- Добавьте осмысленные docstring'и ко всем классам, методам и модулям.
- Используйте аннотации типов (type hints) для аргументов функций и возвращаемых значений.
- Добавьте обработку исключений (`try-except`) в места взаимодействия с файловой системой (загрузка/сохранение БД) и при вводе некорректных данных пользователем.
- **Проверка стиля кода**: Для проверки соответствия стандартам стиля запустите линтер `flake8` в корне проекта:
```bash
flake8 shop/
```
---
**Критерии оценки:**
1. **Работоспособность**: после рефакторинга весь функционал приложения (от регистрации до админских отчетов) должен работать точно так же, как в исходном файле.
2. **Чистота кода**: отсутствие дублирования логики (DRY), понятные имена переменных и методов. **Команда `flake8 shop/` не должна выводить никаких предупреждений или ошибок.**
3. **Качество ООП**: грамотное распределение ответственности между классами, использование инкапсуляции (скрытие внутренних данных), осмысленное применение ООП.
4. **Модульность**: логичное разделение кода по файлам в папке `shop/`.
+583
View File
@@ -0,0 +1,583 @@
# -*- coding: utf-8 -*-
import os
import sys
import json
import datetime
USERS_DB_FILE = 'users.json'
PRODUCTS_DB_FILE = 'products.json'
ORDERS_DB_FILE = 'orders.json'
users = []
products = {}
orders = []
current_user = None
cart = []
# LOAD DATABASE DIRECTLY ON STARTUP
if os.path.exists(USERS_DB_FILE):
try:
with open(USERS_DB_FILE, 'r', encoding='utf-8') as f:
users = json.loads(f.read())
except Exception as e:
print('Error loading users:', e)
if os.path.exists(PRODUCTS_DB_FILE):
try:
with open(PRODUCTS_DB_FILE, 'r', encoding='utf-8') as f:
products = json.loads(f.read())
except Exception as e:
print('Error loading products:', e)
if os.path.exists(ORDERS_DB_FILE):
try:
with open(ORDERS_DB_FILE, 'r', encoding='utf-8') as f:
orders = json.loads(f.read())
except Exception as e:
print('Error loading orders:', e)
if not products:
products['1'] = {'id': '1', 'name': 'Laptop Pro', 'category': 'Electronics', 'price': 75000.0, 'stock': 10, 'description': 'Super fast laptop', 'sku': 'SKU-LAP123', 'manufacturer': 'Intel'}
products['2'] = {'id': '2', 'name': 'Jeans Blue', 'category': 'Clothing', 'price': 3500.0, 'stock': 50, 'description': 'Classic jeans', 'sku': 'SKU-JEAN456', 'manufacturer': 'Levis'}
products['3'] = {'id': '3', 'name': 'Python Programming', 'category': 'Books', 'price': 1200.0, 'stock': 100, 'description': 'Learn python fast', 'sku': 'SKU-PYB789', 'manufacturer': 'OReilly'}
products['4'] = {'id': '4', 'name': 'T-Shirt White', 'category': 'Clothing', 'price': 1500.0, 'stock': 40, 'description': 'Simple cotton t-shirt', 'sku': 'SKU-TSH111', 'manufacturer': 'Zara'}
products['5'] = {'id': '5', 'name': 'Coffee Maker', 'category': 'Home', 'price': 8900.0, 'stock': 15, 'description': 'Espresso coffee maker', 'sku': 'SKU-COF333', 'manufacturer': 'DeLonghi'}
while True:
print('========================================')
print(' LEGACY ONLINE SHOP MANAGER ')
print('========================================')
if current_user:
print('Logged in as:', current_user)
else:
print('Not logged in')
print('1. Register User')
print('2. Login User')
print('3. View Products Catalog')
print('4. Add Product to Shopping Cart')
print('5. View Current Cart')
print('6. Checkout (Calculate Shipping, Coupons, Place Order)')
print('7. View My Order History')
print('8. Logout')
print('9. Exit')
print('10. ADMIN: Add New Product to Catalog')
print('11. ADMIN: View All System Orders')
print('12. ADMIN: System Stats & Reports')
print('========================================')
choice = input('Enter your choice (1-12): ').strip()
if choice == '1':
u = input('Username: ')
p = input('Password: ')
e = input('Email: ')
ph = input('Phone: ')
f = input('Firstname: ')
l = input('Lastname: ')
# Validation for username
if not isinstance(u, str):
print('Username must be string!')
continue
if len(u) < 3 or len(u) > 20:
print('Username length invalid!')
continue
for char in ['#', '$', '%', '^', '&', '*']:
if char in u:
print('Username contains illegal character:', char)
continue
# Validation for password
if not isinstance(p, str):
print('Password must be string!')
continue
if len(p) < 6 or len(p) > 30:
print('Password length invalid!')
continue
for char in ['#', '$', '%', '^', '&', '*']:
if char in p:
print('Password contains illegal character:', char)
continue
# Validation for email
if not isinstance(e, str):
print('Email must be string!')
continue
if len(e) < 5 or len(e) > 50:
print('Email length invalid!')
continue
if '@' not in e or '.' not in e:
print('Email format is invalid!')
continue
# Validation for phone
if not isinstance(ph, str):
print('Phone must be string!')
continue
if len(ph) < 7 or len(ph) > 15:
print('Phone length invalid!')
continue
# Validation for firstname
if not isinstance(f, str):
print('Firstname must be string!')
continue
if len(f) < 2 or len(f) > 30:
print('Firstname length invalid!')
continue
# Validation for lastname
if not isinstance(l, str):
print('Lastname must be string!')
continue
if len(l) < 2 or len(l) > 30:
print('Lastname length invalid!')
continue
user_exists = False
for user_dict in users:
if user_dict['username'] == u:
user_exists = True
if user_exists:
print('Error: User already exists!')
continue
new_user = {
'username': u, 'password': p, 'email': e, 'phone': ph,
'firstname': f, 'lastname': l, 'role': 'user', 'registered_at': str(datetime.datetime.now())
}
users.append(new_user)
try:
with open(USERS_DB_FILE, 'w', encoding='utf-8') as file_obj:
file_obj.write(json.dumps(users, indent=4))
print('Registration successful!')
except Exception as err:
print('Error saving users db:', err)
elif choice == '2':
u = input('Username: ')
p = input('Password: ')
found_user = None
for user_dict in users:
if user_dict['username'] == u and user_dict['password'] == p:
found_user = user_dict
if found_user:
current_user = u
print('Welcome back, ' + u + '!')
else:
print('Login failed! Invalid credentials.')
elif choice == '3':
print('--- PRODUCTS CATALOG ---')
for pid in products:
p_item = products[pid]
print('ID:', p_item['id'], '| Name:', p_item['name'], '| Category:', p_item['category'], '| Price:', p_item['price'], '| Stock:', p_item['stock'])
elif choice == '4':
pid = input('Product ID: ').strip()
qty_str = input('Quantity: ').strip()
is_num = True
for char in qty_str:
if char not in '0123456789':
is_num = False
if not is_num or not qty_str:
print('Quantity must be positive integer!')
continue
qty = int(qty_str)
if pid not in products:
print('Product not found!')
continue
prod_item = products[pid]
if prod_item['stock'] < qty:
print('Not enough stock! Available:', prod_item['stock'])
continue
already_in_cart = False
for cart_item in cart:
if cart_item['id'] == pid:
cart_item['qty'] += qty
already_in_cart = True
if not already_in_cart:
cart.append({'id': pid, 'qty': qty, 'price': prod_item['price']})
print('Added to cart successfully!')
elif choice == '5':
print('--- YOUR CART ---')
if not cart:
print('Your cart is empty!')
else:
cart_total = 0.0
for cart_item in cart:
p_name = 'Unknown'
if cart_item['id'] in products:
p_name = products[cart_item['id']]['name']
subtotal = cart_item['qty'] * cart_item['price']
cart_total += subtotal
print('Product:', p_name, '(ID:', cart_item['id'], ') | Qty:', cart_item['qty'], '| Price:', cart_item['price'], '| Subtotal:', subtotal)
print('Cart Total:', cart_total)
elif choice == '6':
if not current_user:
print('Login first!')
continue
if not cart:
print('Cart is empty!')
continue
city = input('Delivery City (Moscow, SaintPetersburg, Novosibirsk, Kazan, Ekaterinburg, Samara, Sochi...): ').strip()
coupon = input('Promo/Coupon code (optional): ').strip()
total_price = 0.0
total_weight = 0.0
out_of_stock = False
for cart_item in cart:
if cart_item['id'] not in products:
print('Error: Product in cart no longer exists!')
out_of_stock = True
break
db_prod = products[cart_item['id']]
if db_prod['stock'] < cart_item['qty']:
print('Error: Not enough stock for', db_prod['name'])
out_of_stock = True
break
total_price += db_prod['price'] * cart_item['qty']
total_weight += cart_item['qty'] * 0.75
if out_of_stock:
continue
shipping_cost = 250.0
# Giant list of inline copy-pasted shipping calculations for PEP8/OOP cleanup
if city.lower() == 'moscow':
shipping_cost = 200.0
if total_weight > 5.0:
shipping_cost += (total_weight - 5.0) * 10
if total_price > 5000.0:
shipping_cost = shipping_cost * 0.5
if total_price > 10000.0:
shipping_cost = 0.0
elif city.lower() == 'saintpetersburg':
shipping_cost = 300.0
if total_weight > 5.0:
shipping_cost += (total_weight - 5.0) * 12
if total_price > 5000.0:
shipping_cost = shipping_cost * 0.5
if total_price > 10000.0:
shipping_cost = 0.0
elif city.lower() == 'novosibirsk':
shipping_cost = 500.0
if total_weight > 5.0:
shipping_cost += (total_weight - 5.0) * 20
if total_price > 5000.0:
shipping_cost = shipping_cost * 0.5
if total_price > 10000.0:
shipping_cost = 0.0
elif city.lower() == 'ekaterinburg':
shipping_cost = 450.0
if total_weight > 5.0:
shipping_cost += (total_weight - 5.0) * 18
if total_price > 5000.0:
shipping_cost = shipping_cost * 0.5
if total_price > 10000.0:
shipping_cost = 0.0
elif city.lower() == 'kazan':
shipping_cost = 380.0
if total_weight > 5.0:
shipping_cost += (total_weight - 5.0) * 15
if total_price > 5000.0:
shipping_cost = shipping_cost * 0.5
if total_price > 10000.0:
shipping_cost = 0.0
elif city.lower() == 'samara':
shipping_cost = 390.0
if total_weight > 5.0:
shipping_cost += (total_weight - 5.0) * 15
if total_price > 5000.0:
shipping_cost = shipping_cost * 0.5
if total_price > 10000.0:
shipping_cost = 0.0
elif city.lower() == 'sochi':
shipping_cost = 400.0
if total_weight > 5.0:
shipping_cost += (total_weight - 5.0) * 15
if total_price > 5000.0:
shipping_cost = shipping_cost * 0.5
if total_price > 10000.0:
shipping_cost = 0.0
elif city.lower() == 'vladivostok':
shipping_cost = 680.0
if total_weight > 5.0:
shipping_cost += (total_weight - 5.0) * 28
if total_price > 5000.0:
shipping_cost = shipping_cost * 0.5
if total_price > 10000.0:
shipping_cost = 0.0
elif city.lower() == 'krasnodar':
shipping_cost = 350.0
if total_weight > 5.0:
shipping_cost += (total_weight - 5.0) * 12
if total_price > 5000.0:
shipping_cost = shipping_cost * 0.5
if total_price > 10000.0:
shipping_cost = 0.0
elif city.lower() == 'ufa':
shipping_cost = 410.0
if total_weight > 5.0:
shipping_cost += (total_weight - 5.0) * 16
if total_price > 5000.0:
shipping_cost = shipping_cost * 0.5
if total_price > 10000.0:
shipping_cost = 0.0
elif city.lower() == 'perm':
shipping_cost = 430.0
if total_weight > 5.0:
shipping_cost += (total_weight - 5.0) * 16
if total_price > 5000.0:
shipping_cost = shipping_cost * 0.5
if total_price > 10000.0:
shipping_cost = 0.0
elif city.lower() == 'tula':
shipping_cost = 300.0
if total_weight > 5.0:
shipping_cost += (total_weight - 5.0) * 11
if total_price > 5000.0:
shipping_cost = shipping_cost * 0.5
if total_price > 10000.0:
shipping_cost = 0.0
elif city.lower() == 'kaliningrad':
shipping_cost = 480.0
if total_weight > 5.0:
shipping_cost += (total_weight - 5.0) * 19
if total_price > 5000.0:
shipping_cost = shipping_cost * 0.5
if total_price > 10000.0:
shipping_cost = 0.0
else:
# default calculation for other cities
if total_weight > 5.0:
shipping_cost += (total_weight - 5.0) * 25
if total_price > 5000.0:
shipping_cost = shipping_cost * 0.8
if total_price > 12000.0:
shipping_cost = 0.0
discount = 0.0
# Inline coupon calculations
if coupon == 'SALE10':
discount = total_price * 0.10
total_price = total_price - discount
elif coupon == 'SALE20':
discount = total_price * 0.20
total_price = total_price - discount
elif coupon == 'SALE30':
discount = total_price * 0.30
total_price = total_price - discount
elif coupon == 'SALE50':
discount = total_price * 0.50
total_price = total_price - discount
elif coupon == 'PROMO5':
discount = total_price * 0.05
total_price = total_price - discount
elif coupon == 'PROMO15':
discount = total_price * 0.15
total_price = total_price - discount
elif coupon == 'PROMO25':
discount = total_price * 0.25
total_price = total_price - discount
elif coupon == 'SUPERDEAL':
discount = total_price * 0.40
total_price = total_price - discount
if total_price < 0:
total_price = 0.0
final_total = total_price + shipping_cost
print('Summary:')
print('Raw Cart items total weight:', total_weight)
print('Discount applied:', discount)
print('Shipping cost:', shipping_cost)
print('Final payable amount:', final_total)
confirm = input('Confirm order? (yes/no): ').strip().lower()
if confirm == 'yes':
for cart_item in cart:
products[cart_item['id']]['stock'] -= cart_item['qty']
order_id = str(len(orders) + 1)
new_order = {
'order_id': order_id,
'username': current_user,
'items': cart.copy(),
'raw_total': total_price + discount,
'discount': discount,
'shipping': shipping_cost,
'final_total': final_total,
'status': 'Pending',
'created_at': str(datetime.datetime.now())
}
orders.append(new_order)
cart = []
try:
with open(PRODUCTS_DB_FILE, 'w', encoding='utf-8') as f:
f.write(json.dumps(products, indent=4))
with open(ORDERS_DB_FILE, 'w', encoding='utf-8') as f:
f.write(json.dumps(orders, indent=4))
print('Order placed successfully! Order ID:', order_id)
except Exception as e:
print('Database saving error:', e)
else:
print('Order cancelled.')
elif choice == '7':
if not current_user:
print('Login first!')
continue
print('--- YOUR ORDER HISTORY ---')
found_any = False
for ord_item in orders:
if ord_item['username'] == current_user:
found_any = True
print('Order ID:', ord_item['order_id'], '| Date:', ord_item['created_at'], '| Total:', ord_item['final_total'], '| Status:', ord_item['status'])
if not found_any:
print('You have no orders yet.')
elif choice == '8':
current_user = None
cart = []
print('Logged out successfully!')
elif choice == '9':
sys.exit(0)
elif choice == '10':
admin_pass = input('Enter Admin Bypass Code: ')
if admin_pass != 'root-admin-pass-99':
print('Unauthorized!')
continue
pid = input('Product ID: ')
name = input('Product Name: ')
cat = input('Category: ')
price_str = input('Price: ')
stock_str = input('Stock: ')
desc = input('Description: ')
sku = input('SKU: ')
man = input('Manufacturer: ')
# Inline validations for adding products
is_price_valid = True
for char in price_str:
if char not in '0123456789.':
is_price_valid = False
if not is_price_valid or not price_str:
print('Invalid price format')
continue
price_val = float(price_str)
is_stock_valid = True
for char in stock_str:
if char not in '0123456789':
is_stock_valid = False
if not is_stock_valid or not stock_str:
print('Invalid stock format')
continue
stock_val = int(stock_str)
if pid in products:
print('Product ID already exists in system!')
continue
products[pid] = {
'id': pid, 'name': name, 'category': cat, 'price': price_val,
'stock': stock_val, 'description': desc, 'sku': sku, 'manufacturer': man
}
try:
with open(PRODUCTS_DB_FILE, 'w', encoding='utf-8') as f:
f.write(json.dumps(products, indent=4))
print('Product cataloged!')
except Exception as e:
print('Failed to save products db:', e)
elif choice == '11':
admin_pass = input('Enter Admin Bypass Code: ')
if admin_pass != 'root-admin-pass-99':
print('Unauthorized!')
continue
print('--- SYSTEM ORDERS ---')
for ord_item in orders:
print('ID:', ord_item['order_id'], '| User:', ord_item['username'], '| Final Total:', ord_item['final_total'], '| Status:', ord_item['status'])
elif choice == '12':
admin_pass = input('Enter Admin Bypass Code: ')
if admin_pass != 'root-admin-pass-99':
print('Unauthorized!')
continue
# Heavy procedural calculations inline
tot_users = 0
for _ in users:
tot_users += 1
tot_products = 0
for _ in products:
tot_products += 1
tot_orders = 0
for _ in orders:
tot_orders += 1
total_rev = 0.0
for o_itm in orders:
total_rev += o_itm['final_total']
total_discount_given = 0.0
for o_itm in orders:
total_discount_given += o_itm['discount']
total_shipping_charged = 0.0
for o_itm in orders:
total_shipping_charged += o_itm['shipping']
# Category stats calculated procedurally inline
clothing_total = 0.0
electronics_total = 0.0
books_total = 0.0
other_total = 0.0
for o_itm in orders:
for item in o_itm['items']:
p_id = item['id']
qty = item['qty']
price = item['price']
cat_name = 'Unknown'
if p_id in products:
cat_name = products[p_id]['category']
if cat_name.lower() == 'clothing':
clothing_total += qty * price
elif cat_name.lower() == 'electronics':
electronics_total += qty * price
elif cat_name.lower() == 'books':
books_total += qty * price
else:
other_total += qty * price
print('--- REPORTS SUMMARY ---')
print('Users count:', tot_users)
print('Products catalog count:', tot_products)
print('Orders count:', tot_orders)
print('Total Revenue generated:', total_rev)
print('Total Discount Given:', total_discount_given)
print('Total Shipping Charged:', total_shipping_charged)
print('--- Category Sales Revenue ---')
print('Clothing:', clothing_total)
print('Electronics:', electronics_total)
print('Books:', books_total)
print('Other:', other_total)
else:
print('Invalid Option! Please try again.')
+20
View File
@@ -0,0 +1,20 @@
### Задача 2. Генератор паролей (Строки, Циклы, Условия)
Напишите программу из 3 функций для генерации безопасных паролей:
1. `validate_length(length)` — принимает число `length`. Если оно меньше 8, возвращает `False` и выводит *"Пароль должен быть не менее 8 символов"*. Иначе возвращает `True`.
2. `generate_password(length, use_digits=True, use_special=True)` — генерирует случайный пароль длиной `length`:
- Всегда использует буквы (a-z, A-Z)
- Если `use_digits=True`, добавляет цифры (0-9)
- Если `use_special=True`, добавляет спецсимволы (!@#$%^&*)
- Используйте модуль `random` и `string`
- Возвращает строку-пароль
3. `create_passwords(count, length)` — создает список из `count` паролей, каждый длиной `length`. Перед генерацией проверяет длину через `validate_length`. Возвращает список паролей.
**Тесты:**
1. Создайте 5 паролей длиной 12 символов
2. Попробуйте создать пароль длиной 4 символа (должна сработать валидация)
3. Создайте пароль без цифр и спецсимволов
+24
View File
@@ -0,0 +1,24 @@
### Задача 3. Анализатор URL (Строки, Словари, Функции)
Напишите программу из 3 функций для парсинга URL:
1. `parse_url(url)` — принимает строку URL вида `"https://example.com/path?key1=value1&key2=value2"`. Разбирает URL на части и возвращает словарь:
```python
{
"protocol": "https",
"domain": "example.com",
"path": "/path",
"params": {"key1": "value1", "key2": "value2"}
}
```
Если параметров нет, `params` должен быть пустым словарем.
2. `get_param(parsed_url, key, default=None)` — принимает результат `parse_url` и возвращает значение параметра по ключу `key`. Если параметра нет, возвращает `default`.
3. `format_url(parsed_url)` — принимает словарь из `parse_url` и собирает обратно строку URL.
**Тесты:**
1. Распарсите URL: `"https://api.github.com/search?q=python&sort=stars"`
2. Получите значение параметра `q`
3. Соберите URL обратно и сравните с оригиналом
+32
View File
@@ -0,0 +1,32 @@
### Задача 4. Работа с JSON и файлами (ООП, JSON, Исключения)
**Постановка**
* **Конфигурация:** Умеет читать и писать настройки в JSON-файл.
* **Приложение:** Использует конфигурацию для хранения состояния.
**Задание**
**Класс `Config`:**
1. Создайте класс `Config`.
2. В `__init__(self, filename)` сохраните имя файла.
3. Метод `load(self)` — читает JSON-файл и возвращает словарь. Если файла нет, перехватывает `FileNotFoundError` и возвращает пустой словарь `{}`.
4. Метод `save(self, data)` — записывает словарь `data` в JSON-файл с отступами (`indent=4`) и кодировкой `utf-8`.
5. Метод `get(self, key, default=None)` — загружает данные и возвращает значение по ключу или `default`.
6. Метод `set(self, key, value)` — загружает данные, устанавливает новое значение, сохраняет обратно.
**Класс `Application`:**
1. Создайте класс `Application`.
2. В `__init__(self, config)` сохраните объект `Config`.
3. Метод `run(self)` — выводит приветствие с именем пользователя из конфига (ключ `"username"`), по умолчанию `"Гость"`.
4. Метод `set_username(self, name)` — сохраняет имя в конфиг через `config.set`.
**Тесты:**
1. Создайте `Config("settings.json")` и `Application(config)`
2. Запустите приложение (должно вывести "Гость")
3. Установите имя через `set_username("Иван")`
4. Запустите снова (должно вывести "Иван")
5. Проверьте, что файл `settings.json` создался и содержит правильные данные
+42
View File
@@ -0,0 +1,42 @@
### Задача 5. Система логирования (ООП, Наследование, Работа с файлами)
**Постановка**
* **Logger (базовый класс):** Умеет логировать сообщения.
* **FileLogger:** Наследник, который пишет в файл.
* **ConsoleLogger:** Наследник, который пишет в консоль.
* **MultiLogger:** Комбинированный логгер, который пишет везде.
**Задание**
**Класс `Logger`:**
1. Создайте класс `Logger`.
2. Метод `__init__(self, name)` — сохраняет имя логгера.
3. Метод `format_message(self, level, message)` — возвращает строку формата: `"[2026-07-18 10:30:45] [INFO] [LoggerName] Message"`. Используйте `datetime.now()`.
4. Метод `log(self, level, message)` — абстрактный метод, просто пишет `pass`. Переопределяется в наследниках.
**Класс `FileLogger(Logger)`:**
1. Создайте класс `FileLogger`, наследующий `Logger`.
2. В `__init__(self, name, filename)` вызовите `super().__init__(name)` и сохраните имя файла.
3. Переопределите `log(self, level, message)` — форматирует через `format_message` и записывает в файл (режим `'a'`, append).
**Класс `ConsoleLogger(Logger)`:**
1. Создайте класс `ConsoleLogger`, наследующий `Logger`.
2. Переопределите `log(self, level, message)` — форматирует и выводит в консоль через `print`.
**Класс `MultiLogger`:**
1. Создайте класс `MultiLogger`.
2. В `__init__(self, *loggers)` сохраните список логгеров.
3. Метод `log(self, level, message)` — вызывает `log` у каждого логгера.
**Тесты:**
1. Создайте `FileLogger("file_log", "app.log")`
2. Создайте `ConsoleLogger("console_log")`
3. Создайте `MultiLogger(file_logger, console_logger)`
4. Залогируйте несколько сообщений разных уровней: `"INFO"`, `"WARNING"`, `"ERROR"`
5. Проверьте, что сообщения появились и в файле, и в консоли
+17
View File
@@ -0,0 +1,17 @@
### Задача 6. Декораторы и время выполнения (Декораторы, Функции высшего порядка)
Напишите программу с декораторами для измерения производительности:
1. `timer(func)` — декоратор, который измеряет время выполнения функции. Выводит: *"Функция {название} выполнилась за {время} секунд"*. Используйте `time.time()`.
2. `repeat(n)` — декоратор с параметром, который запускает функцию `n` раз подряд и возвращает список результатов.
3. `cache_result(func)` — декоратор, который кэширует результат функции. Если функция вызывается с теми же аргументами, возвращает закэшированный результат, не выполняя функцию повторно. Используйте словарь для хранения кэша.
**Тесты:**
1. Создайте функцию `slow_calculation(x)`, которая делает `time.sleep(1)` и возвращает `x * 2`
2. Примените к ней декоратор `@timer`
3. Создайте функцию `fibonacci(n)` (рекурсивная) и примените `@cache_result`
4. Вызовите `fibonacci(35)` дважды и убедитесь, что второй раз выполняется мгновенно
5. Создайте функцию с декоратором `@repeat(3)` и проверьте, что она выполнилась 3 раза
+39
View File
@@ -0,0 +1,39 @@
### Задача 7. Система управления задачами (ООП, Композиция, Enum)
**Постановка**
* **Приоритет:** Enum с уровнями LOW, MEDIUM, HIGH.
* **Задача:** Имеет название, описание, приоритет и статус (выполнена/не выполнена).
* **Менеджер задач:** Управляет списком задач, может фильтровать по приоритету.
**Задание**
**Enum `Priority`:**
1. Импортируйте `Enum` из модуля `enum`.
2. Создайте класс `Priority(Enum)` с тремя значениями: `LOW = 1`, `MEDIUM = 2`, `HIGH = 3`.
**Класс `Task`:**
1. Создайте класс `Task`.
2. В `__init__(self, title, description, priority)` сохраните параметры и установите `completed = False`.
3. Метод `mark_completed(self)` — устанавливает `completed = True`.
4. Метод `__str__(self)` — возвращает строку: `"[HIGH] Задача: Исправить баг (Не выполнена)"`.
**Класс `TaskManager`:**
1. Создайте класс `TaskManager`.
2. В `__init__(self)` создайте пустой список `tasks`.
3. Метод `add_task(self, task)` — добавляет задачу в список.
4. Метод `get_by_priority(self, priority)` — возвращает список задач с указанным приоритетом.
5. Метод `get_incomplete(self)` — возвращает список невыполненных задач.
6. Метод `show_all(self)` — выводит все задачи циклом через `print`.
**Тесты:**
1. Создайте менеджер задач
2. Добавьте 5 задач с разными приоритетами
3. Отметьте 2 задачи как выполненные
4. Получите все задачи с приоритетом HIGH
5. Получите все невыполненные задачи
6. Выведите все задачи
+27
View File
@@ -0,0 +1,27 @@
### Задача 8. Работа с CSV (Работа с файлами, CSV, Функции)
Напишите программу из 4 функций для работы с CSV-файлами:
1. `create_csv(filename, headers, data)` — создает CSV-файл с заголовками `headers` (список строк) и данными `data` (список списков). Используйте модуль `csv` и метод `csv.writer`.
2. `read_csv(filename)` — читает CSV-файл и возвращает список словарей, где ключи — это заголовки. Используйте `csv.DictReader`.
3. `filter_csv(data, column, value)` — принимает список словарей из `read_csv`, название колонки и значение. Возвращает отфильтрованный список, где значение в колонке совпадает.
4. `calculate_column_sum(data, column)` — принимает список словарей и название колонки с числами. Возвращает сумму всех значений в этой колонке (преобразуя строки в числа).
**Тесты:**
1. Создайте CSV-файл `"students.csv"` с заголовками `["Имя", "Возраст", "Оценка"]` и данными:
```python
[
["Иван", "20", "85"],
["Мария", "22", "92"],
["Петр", "20", "78"],
["Анна", "21", "88"]
]
```
2. Прочитайте файл
3. Отфильтруйте студентов с возрастом "20"
4. Посчитайте сумму всех оценок
5. Выведите результаты
+23
View File
@@ -0,0 +1,23 @@
### Задача 9. Генераторы и итераторы (Генераторы, yield, Итераторы)
Напишите программу с генераторами и кастомными итераторами:
1. `number_generator(start, end, step=1)` — генератор, который yield'ит числа от `start` до `end` с шагом `step`.
2. `fibonacci_generator(limit)` — генератор чисел Фибоначчи до `limit`. Используйте `yield` и цикл `while`.
3. **Класс `RangeIterator`:**
- В `__init__(self, start, end)` сохраните границы
- Метод `__iter__(self)` возвращает `self`
- Метод `__next__(self)` возвращает следующее число или поднимает `StopIteration`
4. `filter_even(generator)` — принимает генератор и возвращает новый генератор, который yield'ит только четные числа. Используйте `yield from` или цикл с `yield`.
**Тесты:**
1. Создайте генератор чисел от 1 до 20 с шагом 2
2. Выведите все значения через цикл `for`
3. Создайте генератор Фибоначчи до 100
4. Отфильтруйте только четные числа
5. Создайте `RangeIterator(5, 10)` и выведите все значения
6. Проверьте, что можно использовать в `list()`: `list(number_generator(1, 10))`
+3 -1
View File
@@ -1,4 +1,6 @@
ruff
commitizen
parameterized
pre_commit
pre_commit
flake8
flake8-import-order