Модули и импорт
Модуль — это файл .py, который можно
импортировать в другой файл. Один файл — один модуль. Имя модуля совпадает с
именем файла без расширения.
Создадим файл math_utils.py:
# math_utils.py
PI = 3.14159
def circle_area(radius):
return PI * radius ** 2
def circle_perimeter(radius):
return 2 * PI * radius
Теперь в другом файле того же каталога импортируем его:
# main.py
import math_utils
print(math_utils.PI) # 3.14159
print(math_utils.circle_area(5)) # 78.53975
Варианты импорта
# Импорт всего модуля — обращение через имя модуля
import math_utils
math_utils.circle_area(5)
# Импорт конкретных имён — короче, но может конфликтовать
from math_utils import circle_area, PI
circle_area(5)
# Импорт со псевдонимом
import math_utils as mu
mu.circle_area(5)
# Импорт всего (звёздочка) — НЕ рекомендуется: засоряет пространство имён
# from math_utils import *
Стандартная библиотека
В Python встроена богатая стандартная библиотека — «батарейки в комплекте». Несколько ходовых модулей:
math — математика
import math
print(math.sqrt(16)) # 4.0 — квадратный корень
print(math.pi) # 3.141592653589793
print(math.ceil(2.1)) # 3 — округление вверх
print(math.floor(2.9)) # 2 — вниз
print(math.log(100, 10)) # 2.0 — логарифм
random — случайные числа
import random
print(random.randint(1, 100)) # случайное целое от 1 до 100
print(random.random()) # случайный float от 0 до 1
print(random.choice(["А", "Б", "В"])) # случайный элемент списка
# shuffle перемешивает список НА МЕСТЕ и возвращает None —
# поэтому печатаем сам список, а не результат вызова
my_list = [1, 2, 3, 4, 5]
random.shuffle(my_list)
print(my_list) # например, [3, 1, 5, 2, 4]
# sample — выбрать несколько разных элементов, не меняя исходный список
print(random.sample(range(1, 50), 6)) # 6 уникальных чисел для лото
datetime — дата и время
from datetime import datetime, date, timedelta
now = datetime.now()
print(now) # 2024-01-15 12:34:56.789
print(now.strftime("%d.%m.%Y")) # 15.01.2024 — форматирование
today = date.today()
week_later = today + timedelta(days=7)
print(week_later) # через неделю
os и pathlib — работа с путями и ОС
from pathlib import Path
# Path — объектно-ориентированный путь
p = Path("data/users.json")
print(p.name) # users.json
print(p.suffix) # .json
print(p.parent) # data
print(p.exists()) # True/False — существует ли файл
# Создать папки
Path("output/logs").mkdir(parents=True, exist_ok=True)
Модуль os отвечает за всё остальное, что связано с
операционной системой: переменные окружения, текущий каталог, список
файлов:
import os
print(os.getcwd()) # текущий рабочий каталог
print(os.listdir(".")) # список файлов в каталоге
# Переменные окружения — так передают настройки и секреты,
# чтобы не хранить их в коде
db_url = os.environ.get("DATABASE_URL") # None, если не задана
todo_file = os.environ.get("TODO_FILE", "tasks.json") # со значением по умолчанию
print(todo_file)
Правило простое: пути — через pathlib.Path,
окружение и процессы — через os.
Свой модуль для проекта
По мере роста проекта логику раскладывают по модулям. Пример структуры:
my-app/
├── main.py # точка входа
├── tasks.py # логика работы с задачами
├── storage.py # сохранение/загрузка в файл
└── utils.py # вспомогательные функции
В main.py импортируем свои модули так же, как стандартные:
from tasks import add_task, list_tasks
from storage import save, load
Зачем нужно ООП
Объектно-ориентированное программирование (ООП) — способ организации кода, при котором данные и действия над ними объединяются в объекты. Класс — это «чертёж» объекта, а объект — конкретный экземпляр.
ООП полезно, когда у вас есть несколько сущностей с одинаковым поведением: пользователи, товары, заказы. Вместо набора разрозненных функций и словарей вы создаёте класс и работаете с объектами.
Объявление класса
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def greet(self):
return f"Привет, я {self.name}!"
# Создаём объекты — экземпляры класса
anna = User("Анна", 28)
boris = User("Борис", 22)
print(anna.name) # Анна
print(anna.greet()) # Привет, я Анна!
print(boris.greet()) # Привет, я Борис!
Что здесь происходит
class User:— объявление класса (CamelCase по соглашению).__init__— конструктор, вызывается при создании объекта.self— ссылка на текущий объект. Первый параметр любого метода.self.name = name— создаём атрибут объекта.User("Анна", 28)— создаём объект (вызывается__init__).
Атрибуты и методы
Атрибут — данные объекта, метод — функция внутри класса. Доступ — через точку:
class Counter:
def __init__(self):
self.value = 0 # атрибут
def increment(self): # метод
self.value += 1
def reset(self):
self.value = 0
c = Counter()
c.increment()
c.increment()
print(c.value) # 2
c.reset()
print(c.value) # 0
Магические методы (dunder)
Методы вида __name__ («dunder» от double
underscore) определяют поведение объекта в стандартных ситуациях.
__init__ мы уже знаем. Полезные:
class Product:
def __init__(self, title, price):
self.title = title
self.price = price
def __str__(self):
'''Что покажет print().'''
return f"{self.title} — {self.price} руб."
def __repr__(self):
'''Техническое представление для отладки.'''
return f"Product({self.title!r}, {self.price})"
def __eq__(self, other):
'''Поведение оператора ==.'''
return self.title == other.title and self.price == other.price
p = Product("Ноутбук", 89990)
print(p) # Ноутбук — 89990 руб. (через __str__)
print(repr(p)) # Product('Ноутбук', 89990) (через __repr__)
Наследование
Один класс может наследовать атрибуты и методы другого. Это позволяет переиспользовать код и строить иерархии:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "Какой-то звук"
class Dog(Animal): # Dog наследует Animal
def speak(self): # переопределение метода
return "Гав!"
class Cat(Animal):
def speak(self):
return "Мяу!"
animals = [Dog("Шарик"), Cat("Мурка"), Animal("Существо")]
for a in animals:
print(f"{a.name}: {a.speak()}")
# Шарик: Гав!
# Мурка: Мяу!
# Существо: Какой-то звук
Это полиморфизм: мы вызываем один метод speak(),
а каждый объект отвечает по-своему.
super() — вызов родителя
Через super() можно вызвать метод родительского класса:
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # вызов конструктора родителя
self.breed = breed
d = Dog("Шарик", "Дворняга")
print(d.name, d.breed) # Шарик Дворняга
Когда класс, а когда функция
Новички часто переоценивают ООП и создают класс для всего. Простое правило:
- Функция — действие, не требующее состояния (посчитать, отформатировать, проверить).
- Класс — сущность с состоянием и связанным поведением (пользователь, заказ, сессия).
Если у класса только статические методы и нет self — это
просто модуль с функциями, класс тут лишний.
Кейс из реального проекта: класс Task
Менеджер задач — классический пример. Задача имеет состояние (название, статус выполнения), и с ней связаны действия (отметить выполненной). Создадим каркас, который в финальной главе превратится в CLI-приложение:
from datetime import datetime
class Task:
def __init__(self, title: str, done: bool = False):
self.title = title
self.done = done
self.created_at = datetime.now()
def mark_done(self) -> None:
self.done = True
def __str__(self) -> str:
status = "[x]" if self.done else "[ ]"
return f"{status} {self.title}"
def to_dict(self) -> dict:
return {
"title": self.title,
"done": self.done,
}
t1 = Task("Выучить Python")
t2 = Task("Написать CLI-утилиту")
t2.mark_done()
print(t1) # [ ] Выучить Python
print(t2) # [x] Написать CLI-утилиту
Заметьте: класс хранит состояние и предоставляет действия.
Сериализация в словарь (to_dict) позволит легко сохранить задачу
в JSON.
Типичные ошибки
1. Забыли self
class User:
def __init__(name, age): # ошибка! забыли self
self.name = name
class User:
def __init__(self, name, age): # правильно
self.name = name
2. Изменяемый атрибут класса
class User:
tags = [] # общий для ВСЕХ объектов — ловушка!
u1 = User()
u2 = User()
u1.tags.append("admin")
print(u2.tags) # ['admin'] — сюрприз!
# Правильно — создавать в __init__
class User:
def __init__(self):
self.tags = [] # у каждого свой
3. Класс ради класса
# Слишком сложно для простой операции
class Adder:
def add(self, a, b):
return a + b
# Достаточно функции
def add(a, b):
return a + b
Практика
- Создайте модуль
math_utils.pyс функциями площади и периметра круга. Импортируйте в другой файл. - Используйте
random, чтобы сгенерировать 6 уникальных чисел от 1 до 49 (лото). - Создайте класс
BankAccountс методамиdeposit,withdrawиbalance(атрибут). - Реализуйте
__str__для классаBookс полями title и author. - Сделайте иерархию
Shape→Circle,Squareс методомarea().
Итог
Мы научились импортировать стандартные модули и создавать свои, посмотрели
на полезные инструменты стандартной библиотеки (math,
random, datetime, pathlib, os), изучили
основы ООП: классы, объекты, __init__, self,
атрибуты и методы, магические методы и наследование. Всё это пригодится в
финальной главе, где мы соберём полноценное CLI-приложение.