feat: implement legacy shop management system and add task documentation files
CI Check / lint-and-check (push) Failing after 9s
CI Check / lint-and-check (push) Failing after 9s
This commit is contained in:
@@ -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. Проверьте работу с пустым списком
|
||||
@@ -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`.
|
||||
@@ -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/`.
|
||||
|
||||
@@ -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.')
|
||||
@@ -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. Создайте пароль без цифр и спецсимволов
|
||||
@@ -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 обратно и сравните с оригиналом
|
||||
@@ -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` создался и содержит правильные данные
|
||||
@@ -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. Проверьте, что сообщения появились и в файле, и в консоли
|
||||
@@ -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 раза
|
||||
@@ -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. Выведите все задачи
|
||||
@@ -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. Выведите результаты
|
||||
@@ -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))`
|
||||
Reference in New Issue
Block a user