July 8, 2018

Змеиное ООП. Основы создания классов

В предыдущей статье мы познакомились с общими принципами объектно ориентированного программирования. Сейчас же мы рассмотрим, как всё это можно применить в 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