Compare commits
9 Commits
5591222f7f
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a401a35b0 | |||
| 8cd4a8b928 | |||
| 2e823d96af | |||
| 3290ab91fc | |||
| ee235ece10 | |||
| 7e8afd2a7e | |||
| 8623d967f0 | |||
| da02f5305a | |||
| b14f14147f |
@@ -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
|
||||
@@ -0,0 +1,34 @@
|
||||
name: CI Check
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
lint-and-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Важно для commitizen, чтобы подтянуть историю всех коммитов
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install ruff commitizen
|
||||
|
||||
- name: Run Ruff Check
|
||||
# Ruff автоматически подхватит настройки из pyproject.toml
|
||||
run: ruff check .
|
||||
|
||||
- name: Run Ruff Format Check
|
||||
# Проверка соответствия стилю
|
||||
run: ruff format --check .
|
||||
|
||||
- name: Check Commit Convention
|
||||
# Проверка всех новых коммитов в PR или последней пуш-сессии
|
||||
run: cz check --rev-range HEAD~1..HEAD
|
||||
@@ -0,0 +1,34 @@
|
||||
def filter_numbers(data):
|
||||
new_data = []
|
||||
for dat in data:
|
||||
if type(dat) is int or float and not bool:
|
||||
new_data.append(float(dat))
|
||||
return new_data
|
||||
|
||||
|
||||
def calculate_average(numbers):
|
||||
result = 0
|
||||
if len(numbers) == 0:
|
||||
result = 0
|
||||
else:
|
||||
for number in numbers:
|
||||
result += float(number)
|
||||
return result
|
||||
|
||||
|
||||
def find_extremes(numbers):
|
||||
if not numbers:
|
||||
return (None, None)
|
||||
else:
|
||||
return min(numbers), max(numbers)
|
||||
|
||||
|
||||
def analyze_data(data):
|
||||
new_data = filter_numbers(data)
|
||||
print(f"Среднее: {calculate_average(new_data)}")
|
||||
print(f"Минимум: {find_extremes(new_data)[0]}")
|
||||
print(f"Максимум: {find_extremes(new_data)[1]}")
|
||||
|
||||
|
||||
my_list = []
|
||||
analyze_data(my_list)
|
||||
@@ -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,481 @@
|
||||
# -*- 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
|
||||
def calculations_price(shipping_cost, total_weight, total_price):
|
||||
|
||||
|
||||
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,8 @@
|
||||
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
|
||||
@@ -0,0 +1,38 @@
|
||||
import random
|
||||
import string
|
||||
|
||||
|
||||
def validate_length(length):
|
||||
if length < 8:
|
||||
print("Пароль должен быть не менее 8 символов")
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def generate_password(length, use_digits, use_special):
|
||||
password_book = string.ascii_letters
|
||||
if use_digits:
|
||||
password_book += string.digits
|
||||
if use_special:
|
||||
password_book += string.punctuation
|
||||
result = "".join(random.choices(password_book, k=length))
|
||||
return result
|
||||
|
||||
|
||||
def create_passwords(count, length):
|
||||
pasword_list = []
|
||||
i = 0
|
||||
for i in range(count):
|
||||
if validate_length(length):
|
||||
pasword = generate_password(
|
||||
length,
|
||||
use_digits=False,
|
||||
use_special=False
|
||||
)
|
||||
pasword_list.append(pasword)
|
||||
i += 1
|
||||
return pasword_list
|
||||
|
||||
|
||||
print(create_passwords(5, 12))
|
||||
@@ -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,58 @@
|
||||
def parse_sensor_data(record):
|
||||
number_data = []
|
||||
plants_data = []
|
||||
for recor in record:
|
||||
if type(recor) is float:
|
||||
number_data.append(recor)
|
||||
if type(recor) is str:
|
||||
plants_data.append(recor)
|
||||
result = (number_data, plants_data)
|
||||
return result
|
||||
|
||||
|
||||
def calculate_statistics(dataset):
|
||||
if not dataset:
|
||||
return []
|
||||
num = 0
|
||||
for data in dataset:
|
||||
num += data
|
||||
mean = num / len(dataset)
|
||||
std = ((sum((data - mean) ** 2)) / len(dataset)) ** 0,5
|
||||
statistic = {"mean": mean,"std": std}
|
||||
return statistic
|
||||
|
||||
|
||||
def normalize_features(features, stats):
|
||||
new_data = []
|
||||
for feat in features:
|
||||
fet = (feat - stats["std"]) / stats["mean"]
|
||||
new_data.append(fet)
|
||||
return new_data
|
||||
|
||||
|
||||
def euclidean_distance(vector_a, vector_b):
|
||||
summ = 0
|
||||
for i in range(len(vector_a)):
|
||||
summ += (vector_a[i] - vector_b[i]) ** 2
|
||||
return summ ** 0,5
|
||||
|
||||
|
||||
def find_k_nearest(train_set, test_point, k):
|
||||
distances = []
|
||||
for train_feater,train_laibel in train_set:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def majority_vote(neighbors):
|
||||
val = {}
|
||||
for lox in neighbors:
|
||||
val[lox] = val.get(lox, 0) +
|
||||
@@ -0,0 +1,45 @@
|
||||
### Последняя Надежда Эпсилон-9: Протокол «К-Резонанс»
|
||||
|
||||
**Ограничение по времени:** 3.0 с
|
||||
|
||||
**Ограничение по памяти:** 512 МБ
|
||||
|
||||
Год 2142. Межзвездная колония на планете Эпсилон-9 столкнулась с невидимой смертью. Внешне безобидная флора планеты скрывает фатальную угрозу: питательный, спасающий от голода «Звездный Корень» и парализующая нервную систему «Теневая Лоза» визуально абсолютно неразличимы. Их выдают только биоэлектрические поля, которые сканируют наши харвестеры.
|
||||
|
||||
До вчерашнего дня наш Центральный ИИ «Тензор-Оракул» успешно классифицировал растения. Но аномальная вспышка на местной звезде сожгла серверные стойки. Библиотеки машинного обучения расплавились в цифровом небытии. Остался только аварийный терминал жизнеобеспечения. Он принимает исключительно базовый, первобытный диалект языка Python. Использовать оператор `import` категорически запрещено — модулей просто не существует. Голод неминуем. Склады забиты смертельно опасным неклассифицированным урожаем.
|
||||
|
||||
Командор колонии Лира врывается в ваш инженерный отсек. В её руках дымящийся кристалл памяти.
|
||||
|
||||
— Инженер! — кричит она, смахивая пепел с терминала. — Я вытащила из мертвых архивов чертежи древнего алгоритма K-Ближайших Соседей. Мы должны воссоздать его с нуля, иначе завтра колония вымрет. Слушай меня внимательно, я зачитаю спецификацию протокола прямо с поврежденных секторов, а ты пиши код!
|
||||
|
||||
Лира подключает кристалл к вашему монитору, и аварийное освещение окрашивает отсек в красный цвет.
|
||||
|
||||
— Для начала, харвестеры присылают нам сплошной текстовый мусор, — диктует Командор, глядя на бегущие строки. — Тебе нужно написать первую функцию **`parse_sensor_data(record)`**. Она должна принимать сырую строку, где числа и класс разделены запятыми, и возвращать кортеж. Первым элементом в нем пойдет список чисел с плавающей точкой — это наши сырые признаки. Вторым — строка класса растения.
|
||||
|
||||
Она нервно стучит по экрану:
|
||||
|
||||
— Данные с сенсоров скачут! Чтобы алгоритм не сошел с ума от разницы в масштабах измерений, нам нужна калибровка. Создай функцию **`calculate_statistics(dataset)`**. Прогони через нее размеченные данные и вычисли для каждого признака среднее арифметическое и стандартное отклонение.s Верни это как список словарей с ключами `"mean"` и `"std"`. И помни, корень придется извлекать возведением в степень 0.5, у нас нет математических модулей!
|
||||
|
||||
— Как только получим статистическую базу, — продолжает Лира, — пиши **`normalize_features(features, stats)`**. Каждый сырой признак растения нужно очистить от шума, вычтя среднее и разделив на стандартное отклонение. Эта функция должна брать список признаков и словарь статистики, а возвращать новый список идеальных, нормализованных чисел.
|
||||
|
||||
Свет в отсеке мигает. Система охлаждения воет на пределе возможностей.
|
||||
|
||||
— Теперь самое главное — геометрия выживания! Пиши ядро: **`euclidean_distance(vector_a, vector_b)`**. Функция должна брать два нормализованных списка чисел и вычислять дистанцию между ними в многомерном пространстве. Просто верни одно число с плавающей точкой.
|
||||
|
||||
Лира выводит на экран голограмму базы данных:
|
||||
|
||||
— Когда мы умеем считать дистанцию, нам нужно найти сородичей. Функция **`find_k_nearest(train_set, test_point, k)`** должна прочесать историческую базу `train_set` и найти заданное число `k` самых близких соседей для неизвестного растения `test_point`. От тебя требуется вернуть только список их классов-строк.
|
||||
|
||||
— Но соседи могут расходиться во мнениях, — Лира хмурится. — Нам нужна логика большинства. Функция **`majority_vote(neighbors)`** примет этот список классов и вернет тот класс, который встречается чаще всего. Если будет ничья — система должна выжить, просто возвращай любой из лидирующих классов. Это и будет наш приговор.
|
||||
|
||||
Она тяжело вздыхает, опираясь на стол:
|
||||
|
||||
— Оберни этот поиск и голосование в единый боевой интерфейс **`predict_class(train_set, test_point, k)`**. Ей мы будем скармливать базу и одно неизвестное растение, чтобы сразу получить итоговый класс-строку.
|
||||
|
||||
— Но прежде чем отправить харвестеры в поля и кормить людей, — голос Лиры дрожит, — мы обязаны проверить алгоритм на архивах. Напиши **`calculate_accuracy(predictions, true_labels)`**. Сравни списки наших предсказаний с истинными классами на тестовом полигоне и верни долю правильных ответов от 0.0 до 1.0. Цена каждой ошибки — чья-то жизнь.
|
||||
|
||||
Лира кладет руку вам на плечо. В её глазах отражается код аварийного терминала:
|
||||
|
||||
— И наконец, Инженер... Великий Синтез. Финальная функция **`run_protocol(train_raw_lines, test_raw_lines, k)`**. Она должна объединить весь пайплайн воедино. Сначала распарсить обе выборки. Затем вычислить статистику **строго и только** по обучающей базе, чтобы не было утечки данных из будущего. Потом нормализовать и обучающую, и тестовую выборки. Пройтись циклом и сделать предсказания для каждого растения в тесте. В самом конце функция должна вернуть словарь с двумя ключами: `"accuracy"` с итоговой точностью и `"predictions"` со списком всех предсказанных классов.
|
||||
|
||||
Терминал призывно мигает пустым курсором. Система охлаждения реактора издает последний предупреждающий гудок. Жизнь Эпсилон-9 зависит от чистоты вашего кода. Приступайте.
|
||||
@@ -0,0 +1,44 @@
|
||||
import json
|
||||
|
||||
|
||||
class Config():
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
|
||||
def load(self):
|
||||
try:
|
||||
with open(self.filename, "r", encoding="utf-8") as file:
|
||||
return json.load(file)
|
||||
except FileNotFoundError:
|
||||
return {}
|
||||
|
||||
def save(self, data):
|
||||
with open(self.filename, "w", encoding="utf-8") as file:
|
||||
json.dump(data, file,indent=4, ensure_ascii=False)
|
||||
|
||||
def get(self, key, default=None):
|
||||
data = self.load()
|
||||
return data.get(key, default)
|
||||
|
||||
|
||||
def set(self, key, value):
|
||||
data = self.load()
|
||||
data[key]=value
|
||||
self.save(data)
|
||||
|
||||
|
||||
class Application():
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
|
||||
def run(self):
|
||||
username = self.config.get("username","гость")
|
||||
print(f"Hello,{username}")
|
||||
|
||||
def set_username(self, name):
|
||||
self.config.set("username", name)
|
||||
|
||||
config = Config("settings.json")
|
||||
ap = Application(config)
|
||||
ap.set_username("Иван")
|
||||
ap.run()
|
||||
@@ -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))`
|
||||
@@ -0,0 +1,24 @@
|
||||
def check_number(item):
|
||||
return isinstance(item, (int, float))
|
||||
|
||||
|
||||
def calculate_total_sum(nested_list):
|
||||
new_summ = 0
|
||||
for i in nested_list:
|
||||
if isinstance(i, (list, tuple)):
|
||||
new_summ += calculate_total_sum(i)
|
||||
elif check_number(i):
|
||||
new_summ += i
|
||||
return new_summ
|
||||
|
||||
|
||||
def process_data(data, limit):
|
||||
if calculate_total_sum(data) <= limit:
|
||||
return True
|
||||
else:
|
||||
print("Превышен лимит")
|
||||
return False
|
||||
|
||||
|
||||
print(check_number("u"))
|
||||
print(process_data(data=(3, 4, 6, 6, 7, (1000, 300)), limit=100))
|
||||
@@ -0,0 +1,23 @@
|
||||
def safe_input():
|
||||
while True:
|
||||
try:
|
||||
text = input("Введите строку")
|
||||
if not text.strip():
|
||||
raise ValueError
|
||||
return text
|
||||
except ValueError:
|
||||
print("Строка не может быть пустой")
|
||||
|
||||
|
||||
def count_chars(text):
|
||||
my_list = {}
|
||||
for char in text.replace(" ", ""):
|
||||
my_list[char] = my_list.get(char, 0) + 1
|
||||
return my_list
|
||||
|
||||
|
||||
def get_top_tuples(my_list):
|
||||
return sorted(my_list.items(), key=lambda x: x[1], reverse=True)
|
||||
|
||||
|
||||
print(get_top_tuples(count_chars(safe_input())))
|
||||
@@ -0,0 +1,23 @@
|
||||
def clean_word(word):
|
||||
words = word.lower()
|
||||
wordss = words.replace(" ", "")
|
||||
return wordss
|
||||
|
||||
|
||||
def is_palindrome(word):
|
||||
wolds = clean_word(word)
|
||||
if wolds == wolds[::-1]:
|
||||
return True
|
||||
|
||||
|
||||
def filter_palindromes(words_list):
|
||||
my_list = []
|
||||
for words in words_list:
|
||||
if is_palindrome(words) is True:
|
||||
my_list.append(words)
|
||||
else:
|
||||
continue
|
||||
return my_list
|
||||
|
||||
|
||||
print(filter_palindromes(["gfdfhfhdh", "fsadfdagfadg", "asa"]))
|
||||
@@ -43,78 +43,3 @@
|
||||
2. Создайте объект читателя, передав ему имя файла.
|
||||
3. Создайте объекты игрока и вселенной.
|
||||
4. Создайте объект поэмы и запустите титры.
|
||||
|
||||
---
|
||||
|
||||
### Задача 5. Библиотека (ООП - Инкапсуляция и Взаимодействие)
|
||||
|
||||
**Постановка**
|
||||
|
||||
* **Книга:** Имеет название, автора и статус (*"В библиотеке"*, *"Выдана"*). Может менять свой статус на противоположный.
|
||||
* **Читатель:** Имеет имя и список взятых книг. Может брать и возвращать книги.
|
||||
* **Библиотека:** Содержит список всех доступных книг. Может выдавать книгу читателю.
|
||||
|
||||
**Задание**
|
||||
|
||||
**Класс `Book`:**
|
||||
|
||||
1. Создайте класс `Book`.
|
||||
2. В `__init__(self, title, author)` определите свойства `title`, `author` и `status` (по умолчанию *"В библиотеке"*).
|
||||
3. Создайте метод `change_status(self)`, меняющий статус книги на *"Выдана"*, если она в библиотеке, и обратно.
|
||||
|
||||
**Класс `Reader`:**
|
||||
|
||||
1. Создайте класс `Reader`.
|
||||
2. В `__init__(self, name)` сохраните имя и создайте пустой список `borrowed_books`.
|
||||
3. Создайте метод `take_book(self, book)`, добавляющий переданную книгу в список.
|
||||
4. Создайте метод `return_book(self, book)`, удаляющий книгу из списка.
|
||||
|
||||
**Класс `Library`:**
|
||||
|
||||
1. Создайте класс `Library`.
|
||||
2. В `__init__(self)` создайте список `books` и добавьте туда несколько книг по умолчанию.
|
||||
3. Создайте метод `lend_book(self, book, reader)`, который проверяет статус книги, меняет его через метод книги на *"Выдана"* и передает читателю в его список.
|
||||
|
||||
**Тесты:**
|
||||
|
||||
1. Создайте библиотеку.
|
||||
2. Создайте читателя.
|
||||
3. Используя библиотеку, выдайте читателю книгу.
|
||||
|
||||
---
|
||||
|
||||
### Задача 6. Магазин (ООП - Агрегация)
|
||||
|
||||
**Постановка**
|
||||
|
||||
* **Товар:** Имеет название и цену.
|
||||
* **Корзина:** Содержит список товаров. Может добавлять товар и считать общую стоимость.
|
||||
* **Покупатель:** Имеет имя, баланс и корзину. Может оплачивать корзину.
|
||||
|
||||
**Задание**
|
||||
|
||||
**Класс `Product`:**
|
||||
|
||||
1. Создайте класс `Product`.
|
||||
2. Создайте метод `__init__(self, name, price)`.
|
||||
|
||||
**Класс `Cart`:**
|
||||
|
||||
1. Создайте класс `Cart`.
|
||||
2. В `__init__(self)` создайте пустой список `products`.
|
||||
3. Создайте метод `add_product(self, product)`, добавляющий товар в список.
|
||||
4. Создайте метод `get_total(self)`, возвращающий сумму цен всех товаров.
|
||||
|
||||
**Класс `Customer`:**
|
||||
|
||||
1. Создайте класс `Customer`.
|
||||
2. В `__init__(self, name, balance, cart)` сохраните переданные параметры.
|
||||
3. Создайте метод `pay(self)`, который проверяет, хватает ли баланса для оплаты корзины. Если хватает — вычитает сумму из баланса и очищает корзину. Если нет — выводит предупреждение.
|
||||
|
||||
**Тесты:**
|
||||
|
||||
1. Создайте несколько товаров.
|
||||
2. Создайте корзину и добавьте в нее товары.
|
||||
3. Создайте покупателя, передав ему корзину и баланс.
|
||||
4. Заставьте покупателя оплатить товары в корзине.
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import json
|
||||
|
||||
|
||||
class PoemReader():
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
|
||||
def read_poem(self):
|
||||
with open(self.filename, "r", encoding="utf-8") as file:
|
||||
return json.load(file)["poem_text"]
|
||||
|
||||
|
||||
class Player:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def see_words(self):
|
||||
print(f"{self.name} читает наши мысли так, словно это слова на экране.")
|
||||
|
||||
|
||||
class Universe:
|
||||
def __init__(self):
|
||||
self.name = "Вселенная"
|
||||
|
||||
def speak(self):
|
||||
print("И Вселенная говорит тебе: Я люблю тебя.")
|
||||
|
||||
|
||||
class EndPoem:
|
||||
def __init__(self, reader, player, universe):
|
||||
self.reader, self.player, self.universe = reader, player, universe
|
||||
|
||||
def play_credits(self):
|
||||
print(self.reader.read_poem())
|
||||
self.player.see_words()
|
||||
self.universe.speak()
|
||||
|
||||
|
||||
reader = PoemReader(r"data\main.json")
|
||||
player = Player("Дима")
|
||||
universete = Universe()
|
||||
poem = EndPoem(reader, player, universete)
|
||||
poem.play_credits()
|
||||
@@ -1,87 +1,47 @@
|
||||
# 🌱 Farm Automation System
|
||||
# Контрольные работы: Игорь
|
||||
|
||||
Учебный проект для практики работы с Git: слияние конфликтов и интерактивный ребейз.
|
||||
Добро пожаловать в рабочее пространство для выполнения контрольных работ. В этом репозитории мы учимся писать чистый, качественный и работающий код, используя современные инструменты разработки.
|
||||
|
||||
## Описание проекта
|
||||
## 🛠 Технологический стек
|
||||
* **Язык**: Python
|
||||
* **Автоматизация (CI)**: Gitea Actions
|
||||
* **Контроль качества**: Ruff (линтер)
|
||||
* **Стандарты коммитов**: Commitizen
|
||||
|
||||
Простая система автоматизации гидропонной фермы на Python с использованием ООП:
|
||||
|
||||
- **BioCulture** — класс биокультуры со стадиями роста ("Зеленый" → "Красный")
|
||||
- **HydroponicModule** — модуль, управляющий несколькими культурами
|
||||
- **FarmAgent** — агент-оператор для управления модулем и сбора урожая
|
||||
|
||||
## Структура проекта
|
||||
|
||||
```
|
||||
Igor-kr/
|
||||
├── main.py # Точка входа
|
||||
├── farm_core/
|
||||
│ ├── __init__.py
|
||||
│ ├── module.py # Классы BioCulture и HydroponicModule
|
||||
│ └── agent.py # Класс FarmAgent
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Ветки
|
||||
|
||||
- **main** — базовая заготовка со "сломанным" методом `grow()` (вызывает `NotImplementedError`)
|
||||
- **feature-api** — рабочая реализация метода `grow()` и новая функция `harvest()`
|
||||
|
||||
## 🎯 Практические задания
|
||||
|
||||
### Задание 1: Разрешение конфликтов при слиянии
|
||||
|
||||
**Цель:** Научиться разрешать merge conflicts.
|
||||
|
||||
**Шаги:**
|
||||
|
||||
1. Убедитесь, что вы на ветке `main`
|
||||
2. Попытайтесь слить ветку `feature-api`
|
||||
3. **Ожидаемый результат:** Конфликт в файле `farm_core/module.py` в методе `grow()`:
|
||||
- В `main`: `raise NotImplementedError("Рост не реализован в main!")`
|
||||
- В `feature-api`: `self._stage = "Красный"`
|
||||
4. Откройте файл `farm_core/module.py` и разрешите конфликт
|
||||
5. Завершите слияние
|
||||
|
||||
### Задание 2: Интерактивный ребейз (очистка истории)
|
||||
|
||||
**Цель:** Научиться объединять "мусорные" коммиты в один чистый коммит.
|
||||
|
||||
**Шаги:**
|
||||
|
||||
1. Переключитесь на ветку `feature-api`
|
||||
2. Посмотрите на историю коммитов (вы увидите три мусорных коммита: done, fix2, fix)
|
||||
3. Запустите интерактивный ребейз для последних 3 коммитов
|
||||
4. Объедините три мусорных коммита в один
|
||||
5. Проверьте результат — вместо трех мусорных коммитов должен быть один чистый коммит
|
||||
|
||||
### Задание 3 (дополнительно): Проверка работы кода
|
||||
|
||||
1. Переключитесь на `main` после успешного слияния
|
||||
2. Допишите в `main.py` цикл для проверки работы:
|
||||
```python
|
||||
for culture in module.cultures:
|
||||
culture.grow()
|
||||
|
||||
print(f"Готово к сбору: {agent.harvest()} культур(ы)")
|
||||
```
|
||||
3. Запустите код
|
||||
|
||||
**Ожидаемый вывод:**
|
||||
```
|
||||
Инструкция: pH 6.0
|
||||
Готово к сбору: 3 культур(ы)
|
||||
```
|
||||
|
||||
## ✅ Критерии успешного выполнения
|
||||
|
||||
- [ ] Конфликт в `module.py` успешно разрешен
|
||||
- [ ] Метод `grow()` работает корректно (изменяет стадию на "Красный")
|
||||
- [ ] Три мусорных коммита объединены в один
|
||||
- [ ] История коммитов чистая и понятная
|
||||
- [ ] Код запускается без ошибок
|
||||
## 📋 Статус проверок
|
||||
[](https://gitea.dimong.ru/Dimong4/Igor-kr/actions)
|
||||
|
||||
---
|
||||
|
||||
**Автор:** Dmitry Belan
|
||||
**Дата:** 2026-07-01
|
||||
## 🚀 Как работать с этим репозиторием
|
||||
|
||||
### 1. Подготовка окружения
|
||||
Перед началом работы убедись, что у тебя установлены:
|
||||
* **Git** — для управления версиями.
|
||||
* **Python 3.x** — для написания кода.
|
||||
* **pip** — для установки зависимостей.
|
||||
|
||||
### 2. Установка зависимостей
|
||||
Если твое решение требует дополнительных библиотек:
|
||||
1. Создай файл `requirements.txt` в корне проекта.
|
||||
2. Установи зависимости командой: `pip install -r requirements.txt`.
|
||||
3. Обязательно добавь все используемые библиотеки в `requirements.txt`, чтобы они корректно работали в среде автоматической проверки.
|
||||
|
||||
### 3. Рабочий процесс (Workflow)
|
||||
1. **Создай новую ветку** для своей работы: `git checkout -b task-name`.
|
||||
2. **Пиши код** и проверяй его локально.
|
||||
3. **Сделай коммит**, соблюдая правила (Commitizen): `git commit -m "feat: добавил решение задачи №1"`.
|
||||
4. **Отправь изменения** в репозиторий: `git push origin task-name`.
|
||||
|
||||
### 4. Проверка и сдача
|
||||
* После `push` перейди на страницу репозитория во вкладку **Actions**.
|
||||
* Если горит **зеленая галочка ✅** — работа прошла автоматический контроль качества.
|
||||
* Если **красный крестик ❌** — открой логи задачи, исправь замечания линтера и запушь исправления.
|
||||
* После успеха создай **Pull Request** из своей ветки в `main` для финальной проверки.
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Правила
|
||||
* **Чистота кода**: Игнорирование предупреждений линтера `Ruff` недопустимо.
|
||||
* **Ветка main**: Прямые пуши в `main` запрещены. Ветка защищена правилами CI/CD.
|
||||
* **Ответственность**: Ты несешь ответственность за свой код и его работоспособность в CI-среде.
|
||||
@@ -1,9 +0,0 @@
|
||||
temperatures = [37, 48, 30, 29, 41, -30, -29, -90]
|
||||
number_ice = 0
|
||||
for i in (temperatures):
|
||||
if i >= 0:
|
||||
print(i)
|
||||
else:
|
||||
number_ice += 1
|
||||
|
||||
print(f"Number ice Day: {number_ice}")
|
||||
@@ -0,0 +1,87 @@
|
||||
# 🌱 Farm Automation System
|
||||
|
||||
Учебный проект для практики работы с Git: слияние конфликтов и интерактивный ребейз.
|
||||
|
||||
## Описание проекта
|
||||
|
||||
Простая система автоматизации гидропонной фермы на Python с использованием ООП:
|
||||
|
||||
- **BioCulture** — класс биокультуры со стадиями роста ("Зеленый" → "Красный")
|
||||
- **HydroponicModule** — модуль, управляющий несколькими культурами
|
||||
- **FarmAgent** — агент-оператор для управления модулем и сбора урожая
|
||||
|
||||
## Структура проекта
|
||||
|
||||
```
|
||||
Igor-kr/
|
||||
├── main.py # Точка входа
|
||||
├── farm_core/
|
||||
│ ├── __init__.py
|
||||
│ ├── module.py # Классы BioCulture и HydroponicModule
|
||||
│ └── agent.py # Класс FarmAgent
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Ветки
|
||||
|
||||
- **main** — базовая заготовка со "сломанным" методом `grow()` (вызывает `NotImplementedError`)
|
||||
- **feature-api** — рабочая реализация метода `grow()` и новая функция `harvest()`
|
||||
|
||||
## 🎯 Практические задания
|
||||
|
||||
### Задание 1: Разрешение конфликтов при слиянии
|
||||
|
||||
**Цель:** Научиться разрешать merge conflicts.
|
||||
|
||||
**Шаги:**
|
||||
|
||||
1. Убедитесь, что вы на ветке `main`
|
||||
2. Попытайтесь слить ветку `feature-api`
|
||||
3. **Ожидаемый результат:** Конфликт в файле `farm_core/module.py` в методе `grow()`:
|
||||
- В `main`: `raise NotImplementedError("Рост не реализован в main!")`
|
||||
- В `feature-api`: `self._stage = "Красный"`
|
||||
4. Откройте файл `farm_core/module.py` и разрешите конфликт
|
||||
5. Завершите слияние
|
||||
|
||||
### Задание 2: Интерактивный ребейз (очистка истории)
|
||||
|
||||
**Цель:** Научиться объединять "мусорные" коммиты в один чистый коммит.
|
||||
|
||||
**Шаги:**
|
||||
|
||||
1. Переключитесь на ветку `feature-api`
|
||||
2. Посмотрите на историю коммитов (вы увидите три мусорных коммита: done, fix2, fix)
|
||||
3. Запустите интерактивный ребейз для последних 3 коммитов
|
||||
4. Объедините три мусорных коммита в один
|
||||
5. Проверьте результат — вместо трех мусорных коммитов должен быть один чистый коммит
|
||||
|
||||
### Задание 3 (дополнительно): Проверка работы кода
|
||||
|
||||
1. Переключитесь на `main` после успешного слияния
|
||||
2. Допишите в `main.py` цикл для проверки работы:
|
||||
```python
|
||||
for culture in module.cultures:
|
||||
culture.grow()
|
||||
|
||||
print(f"Готово к сбору: {agent.harvest()} культур(ы)")
|
||||
```
|
||||
3. Запустите код
|
||||
|
||||
**Ожидаемый вывод:**
|
||||
```
|
||||
Инструкция: pH 6.0
|
||||
Готово к сбору: 3 культур(ы)
|
||||
```
|
||||
|
||||
## ✅ Критерии успешного выполнения
|
||||
|
||||
- [ ] Конфликт в `module.py` успешно разрешен
|
||||
- [ ] Метод `grow()` работает корректно (изменяет стадию на "Красный")
|
||||
- [ ] Три мусорных коммита объединены в один
|
||||
- [ ] История коммитов чистая и понятная
|
||||
- [ ] Код запускается без ошибок
|
||||
|
||||
---
|
||||
|
||||
**Автор:** Dmitry Belan
|
||||
**Дата:** 2026-07-01
|
||||
@@ -1,5 +1,5 @@
|
||||
from farm_core.module import HydroponicModule
|
||||
from farm_core.agent import FarmAgent
|
||||
from farm_core.module import HydroponicModule
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(FarmAgent.get_manual())
|
||||
@@ -0,0 +1,54 @@
|
||||
[tool.commitizen]
|
||||
name = "cz_conventional_commits"
|
||||
tag_format = "0"
|
||||
version_scheme = "semver"
|
||||
version = "0.0.1"
|
||||
update_changelog_on_bump = true
|
||||
major_version_zero = true
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 80
|
||||
exclude = [
|
||||
".git",
|
||||
"__pycache__",
|
||||
"build",
|
||||
"dist",
|
||||
".venv",
|
||||
"venv",
|
||||
".mypy_cache",
|
||||
".pytest_cache",
|
||||
]
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E", "F", "W", "N", "I", "Q", "T20"]
|
||||
ignore = ["N802", "T201", "T001"]
|
||||
|
||||
[tool.ruff.lint.flake8-quotes]
|
||||
inline-quotes = "double"
|
||||
multiline-quotes = "double"
|
||||
docstring-quotes = "double"
|
||||
|
||||
# Аналог import-order-style = "google"
|
||||
[tool.ruff.lint.isort]
|
||||
known-first-party = []
|
||||
|
||||
[tool.flake8]
|
||||
max-line-length = 80
|
||||
import-order-style = "google"
|
||||
inline-quotes = "double"
|
||||
multiline-quotes = "double"
|
||||
docstring-quotes = "double"
|
||||
|
||||
# Записываем ignore как строку, если их несколько, или список
|
||||
ignore = "N802,T201,T001"
|
||||
|
||||
exclude = [
|
||||
".git",
|
||||
"__pycache__",
|
||||
"build",
|
||||
"dist",
|
||||
".venv",
|
||||
"venv",
|
||||
".mypy_cache",
|
||||
".pytest_cache"
|
||||
]
|
||||
@@ -0,0 +1,6 @@
|
||||
ruff
|
||||
commitizen
|
||||
parameterized
|
||||
pre_commit
|
||||
flake8
|
||||
flake8-import-order
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"username": "Иван"
|
||||
}
|
||||
Reference in New Issue
Block a user