Змеиное ООП. Основы создания классов
В предыдущей статье мы познакомились с общими принципами объектно ориентированного программирования. Сейчас же мы рассмотрим, как всё это можно применить в Python.
Первый класс
Объявление класса в Python похоже на объявление функции, только с использованием ключевого слова class. Вот простейший пример:
class Table: name = "Название"
В теле класса, как мы видим, можно объявлять переменные и присваивать им значения. А вот так можно создать объект этого класса:
current_table = Table() print(current_table.name) # Выведет "Название" current_table.name = "Кухонный стол" print(current_table.name) # Кухонный стол
name - свойство класса Table. Это переменная, которая есть у каждого объекта этого класса. Как и любую переменную, её можно менять и получать значение. Однако у классов могут быть не только свойства, но и методы. Метод - функция, которая находится внутри класса. Пример:
class Table: width = 300 height = 500 length = 100 def print_size(self): result = str(self.width) + "x" + str(self.height) + "x" + str(self.length) print(result) table = Table() table.print_size() # Вывод: 300x500x100 table.width = 200 table.print_size() # Вывод: 200x500x100
Давайте разбираться. print_size - метод класса Table. Любой метод(кроме статических, но статья не об этом) должен получать хотя бы один аргумент: текущий объект. Обычно его называют self и передаётся он автоматически первым. С ним мы можем работать, как с обычным объектом - ведь это он и есть. Соответственно, мы можем обращаться к его методам и свойствам. Поэтому когда мы поменяли width, то и вывод функции изменился. А что если мы хотим сразу при создании присвоить определённый размер? Специально для этого есть "волшебный" метод - конструктор. В питоне называется он __init__ и вызывается при создании объекта. Давайте модифицируем наш класс с применением конструктора.
class Table: def __ init__(self, width, height, length): self.width = width self.height = height self.length = length def print_size(self): result = str(self.width) + "x" + str(self.height) + "x" + str(self.length) print(result) new_table = Table(200, 500, 100) new_table.print_size() # Вывод: 200x500x100
Теперь, при создании объекта, мы указываем те значения свойств, которые нам нужны. Аргументы в скобочках и есть аргументы конструктора.
Не забывайте про первый аргумент метода: self, который является текущим объектом и не передаётся в явном виде.
Наследование
Теперь давайте научимся делать "родительские" и "потомственные" классы. Допустим, у нас есть класс пользователя User. У него есть 2 параметра: имя пользователя(строка) и активен ли этот аккаунт(логическое значение)
class User: def __init__(self, username, is_active): self.username = username self.is_active = is_active user = User("trololo", True) print(user.username) # trololo
Теперь мы хотим сделать класс "модератор", который сможет удалять нежелательных пользователей. Его мы сделаем потомком этого класса, ведь он тоже пользователь. Для этого, при объявлении класса в скобках мы указываем "родителя" этого класса.
class Moderator(User): def ban_user(self, other_user): other_user.is_active = False
Ему мы добавили метод "забанить пользователя", которым он и отличается от базового класса. При этом, важно помнить, что конструктор наследуется и создавать объект этого класса мы будем точно так же:
bad_user = User("not bad", True) moderator = Moderator("Just moderator", True) moderator.ban_user(user) moderator.ban_user(bad_user) print(bad_user.is_active) # False
Давайте добавим работника службы поддержки, который сможет менять имена пользователей. Он также будет потомком класса "пользователь".
class Staff(User): def change_username(self, other_user, new_username): other_user.username = new_username just_user = User("old nickname", True) staff = Staff("John123", True) staff.change_username(just_user, "new username") print(just_user.username) # new username
"Один родитель хорошо... А 2 лучше, а ещё лучше 3" - подумали разработчики питона и сделали множественное наследование. То есть один класс может иметь сколько угодно "родителей". Они перечисляются в скобках через запятую. Сначала идут те, свойства и методы которых приоритетнее. Если возникнут наложения, то будет унаследован член того класса, который указан ранее. Например, давайте создадим класс "администратор", который сможет делать и то, что умеет модератор и то, что умеет работник поддержки. И возвращать неактивные аккаунты к жизни, вдобавок. То есть его родителями будут уже Moderator и Staff.
class Admin(Moderatror, Staff): def return_user(self, other_user): other_user.is_active = True
Вот так вот коротко, без переписывания кода. А самое главное, что теперь наш код выглядит, как "общение" объектов, что упрощает его написание у увеличивает читаемость. Про наследование мы тут поговорили, инкапсуляция тоже есть: мы видим лишь человеческое название метода, а не то, как он работает, полиморфизм тоже имеется: мы использовали один и тот же код и для модератора и для админа, а не переписывали его. Теперь мы можем создавать классы, использовать наследование и наслаждаться написанием кода. В следующих статьях ждите инструкцию по стилю кода и статью по функции super.
Материал для группы SnakeBlog