Теория урока

74. Продолжаем написание классов в Python

Оглавление урока

Введение

В предыдущем уроке мы начали более глубокое изучение классов в Python. В этом уроке построим более реалистичный класс (и не один) для демонстрации всех основных идей ООП.

Мы реализуем два класса User и Admin, которые будут создавать пользователей, хранить определенные данные и выполнять определенные функции. Попутно повторим основные концепции ООП.

Начнем написание класса User. Напомню, имена классов в Python принято начинать с буквы верхнего регистра. Так же мы создадим отдельный модуль user.py для нашего класса.

Пример
'''  user.py  '''

class User:
pass

Создаем конструктор и добавляем атрибуты класса

Следующим шагом будет написание конструктора класса __init__. Создание нового экземпляра означает «регистрацию» нашего очередного пользователя, поэтому в конструкторе логичнее всего реализовать заполнение основных полей: логин и пароль.

Пример
'''  user.py  '''

class User:
def __init__(self, login, password):
self.login = login
self.password = password

Да, пароль будет храниться в открытом виде, что является категорически недопустимым в реальных системах. Не будем усложнять код и продолжим изучать принципы ООП.

Вы так же могли заметить наличие в конструкторе имен, которые повторяются дважды. Давайте разберемся. Параметр login (если вы забыли отличие аргументов и параметров, вернитесь к уроку «Аргументы и параметры функций, операторы * и ** в Python») хранит в себе значение, переданное в конструктор при инициализации экземпляра. Переменная login является локальной в области функции __init__ (про область видимости мы говорили в уроке «Область видимости в Python»). Но self.login – это атрибут экземпляра в который мы и сохраняем значение параметра login в экземпляре для последующего применения.

Мы уже выяснили, конструктор класса __init__ - обычная функция, которая вызывается при создании экземпляра. Напомню про создание экземпляра на примере:

Пример
''' main.py '''
from user import User

user_1 = User('ivan', '123') # создаем экземпляр с именем user_1
print(user_1.login) # => ivan

В файле user.py находится только класс User. Больше там ничего находиться не будет. В main.py будем работать с написанными нами классами. Прежде не забудьте подключить этот файл как модуль при помощи оператора import или from (если забыли про различие, вернитесь к уроку «Написание модулей в Python»)

Возвращаемся к реализации класса User. Добавим еще один параметр – имя пользователя.

Пример
'''  user.py  '''
class User:
def __init__(self, login, password, name=None):
self.login = login
self.password = password
self.name = name

Обратите внимание на значение после параметра name – это стандартное значение. Мы можем указывать любое значение по умолчанию, которое будет хранится в атрибуте self.name, если имя не было передано при создании экземпляра. Как все запутал. Посмотрим на пример создания двух экземпляров:

Пример
user_1 = User('ivan', '123')
user_2 = User('pt', '123456', 'Петр')
print(user_1.name) # => None
print(user_2.name) # => Петр

При создании экземпляра user_1 не было передано имя, поэтому в self.name было сохранено значение по умолчанию (в нашем случае None, но мы можем указать любое другое значение). В соответствии с правилами синтаксиса Python необходимо устанавливать значение по умолчанию для всех последующих параметров. Добавим еще атрибут self.rating, который хранит очки рейтинга пользователя.

Пример
'''  user.py  '''
class User:
def __init__(self, login, password, name=None, rating=0):
self.login = login
self.password = password
self.name = name
self.rating = rating

Добавляем методы класса

Поговорим об инкапсуляции – объединение деталей и сокрытие их реализации в оболочку интерфейсов таким образом, чтобы код каждой операции был написан всего один раз. Например, нам нужно увеличивать рейтинг пользователя на определенное значение, для реализации чего в коде будет фигурировать одно и тоже выражение:

Пример
n = 10 # на сколько очков увеличить рейтинг
self.rating = self.rating + n

Лучше инкапсулировать это выражение в метод change_rating. Добавим этот метод после конструктора __init__.

Пример
def change_rating(self, N):
self.rating += N

Это очень удобно, особенно если вдруг необходимо поменять реализацию, не придется исправлять в нескольких местах. Добавим еще один метод, который позволит изменить имя:

Пример
def change_name(self, new_name):
self.name = new_name
self.change_rating(-5)

Как вы видите, мы добавили метод change_rating в change_name, который понижает рейтинг на 5 очков после изменения имени.

Перегрузка операторов

Попробуем вывести экземпляр user_1. Как и ожидалось, Python сообщил нам о том, что это объект.

Пример
user_1 = User('ivan', '123')
print(user_1) # => <user.User object at 0x0000027A575A2820>

Благодаря перегрузке операторов мы можем скорректировать это вывод. Рассмотрим второй по популярности перегружаемый метод __repr__:

Пример
def __repr__(self):
return f'{self.login} ({self.rating})'

Теперь экземпляры класса будут возвращать логин и количество очков рейтинга. Если вы забыли о f-строках, которые используются для форматирования, вернитесь к уроку «Форматирование строк в Python». На данном этапе наш класс User выглядит следующим образом:

Пример
'''  user.py  '''

class User:
def __init__(self, login, password, name=None, rating=0):
self.login = login
self.password = password
self.name = name
self.rating = rating

def __repr__(self):
return f'{self.login} ({self.rating})'

def change_rating(self, N):
self.rating += N

def change_name(self, new_name):
self.name = new_name
self.change_rating(-5)

Класс User уже реализует большую часть механизма ООП в Python. Осталась только одна значительная для ООП концепция – наследование.

Наследование классов

Мы имеем класс User, который выполняет основные функции для пользователя. По большей части, администратор, тот же пользователь, но с более расширенными правами. Давайте создадим файл admin.py и создадим класс Admin, расширяющий возможности класса User.

Пример
'''  admin.py  '''
from user import User

class Admin(User):
pass

Для начала мы импортируем модуль с классом User. Далее обратите внимание как происходит наследование. Мы так же создаем класс при помощи оператора class, указываем имя класса и в круглых скобках записываем суперкласс. На данный момент класс Admin пустой. Попробуем создать экземпляр класса и посмотрим работает ли наследование?

Пример
''' main.py '''
from user import User
from admin import Admin

admin = Admin('admin', '123456')
print(admin) # => admin (0)

В классе User есть метод change_rating, который изменяет рейтинг пользователя. Переопределим этот метод в классе Admin: у администратора будет бонус 10% при изменении рейтинга (и в отрицательную сторону тоже, для справедливости):

Пример
class Admin(User):
def change_rating(self, N, pr=.1):
self.rating += N + N * pr

Вынужден признать, что пример выше это плохой способ переопределения метода change_rating, так как мы просто скопировали код из класса User, вставили сюда и немного отредактировали. Если будет необходимость изменить код в классе User, то придется так же менять и в классе Admin. Так не годится. Немного перепишем и сделаем вызов предыдущей версии метода change_rating с дополненными аргументами:

Пример
class Admin(User):
def change_rating(self, N, pr=.1):
User.change_rating(self, N + N * pr)

В классах-наследниках можно не только переопределять методы суперкласса, но и добавлять свои:

Пример
def change_name_user(self, user, name):
user.name = name

Метод change_name_user позволяет поменять администратору имя у любого пользователя, передав экземпляр в первый аргумент и новое имя во второй. Посмотрим на применение:

Пример
''' main.py '''
from user import User
from admin import Admin

admin = Admin('admin', '123456')
user_1 = User('ivan', '123')
print(user_1.name) # => None
admin.change_name_user(user_1, 'Иван')
print(user_1.name) # => Иван

Как вы убедились, код может быть небольшим, но достаточно функциональным. В этом сосредоточена основная сила ООП: мы добавляем новые возможности за счет настройки уже существующего кода, а не множественного копирования и изменения кода.

Записываем результат в базу данных

Создаваемые экземпляры являются временными и после завершения программы удаляются. В Python есть возможность сделать объекты экземпляров постоянными при помощи специальных средств. Нам понадобятся три модуля: pickle (для сериализации объектов в строку), dbm (для реализации файловой системы с доступом по ключу) и shelve (для объединения результатов работы двух предыдущих модулей). Вот что получилось:

Пример
''' main.py '''
from user import User
from admin import Admin
import shelve
db = shelve.open('user')

admin = Admin('admin', '123456')
user_1 = User('ivan', '123', 'Иван')
user_2 = User('pt', '1234')

for obj in (admin, user_1, user_2):
db[obj.login] = obj
db.close()

Теперь попробуем обработать сохраненные данные.

Пример
import shelve
db = shelve.open('user')
print(len(db)) # => 3
print(list(db.keys())) # => ['admin', 'ivan', 'pt']
admin = db['admin']
print(admin) # => admin (0)

Как видите, после получения данных из базы данных user, мы можем найти по индексу необходимый объект и продолжить, присвоить ему имя и работать как с обычным экземпляром класса.

Сохранять измененные данные в базе данных так же просто:

Пример
import shelve
db = shelve.open('user')
print(len(db)) # => 3
print(list(db.keys())) # => ['admin', 'ivan', 'pt']
admin = db['admin']
print(admin) # => admin (0)
admin.change_rating(10)
db['admin'] = admin
db.close()

Чтобы сохранить измененный экземпляр, следует присвоить его элементу в базе данных с индексом измененного объекта.

Мы подошли к концу урока. В нем еще раз повторили как создавать экземпляры классов и методы с инкапсулированной в них логикой, еще раз перегрузили метод __init__ и познакомились с методом __repr__, настроили поведение класса Admin, который был наследован от User, узнали о простом способе сохранения любых объектов на физическом носителе.

ПРОЧИТАНО
Следующий урок

Похожие уроки и записи блога

Написание модулей в PythonЗнакомство с Python
Работа с файлами в Python Знакомство с Python
Обработка исключений (try/except) в PythonЗнакомство с Python
Первое знакомство с PythonЗнакомство с Python
Еще о возможностях модулей в Python Знакомство с Python
Типы данных в PythonЗнакомство с Python
Основы объектно-ориентированного программирования (ООП) в PythonЗнакомство с Python
Модули в PythonЗнакомство с Python
Основы функций в PythonЗнакомство с Python
<
×
>
Впервые на сайте Codebra?

Извините за это всплывающее окно, меня они тоже раздражают.

Образовательный ресурс codebra.ru полностью посвящен программированию. Все курсы и уроки находятся на главной странице. Ради интереса можете посмотреть на содержимое курсов по Python, HTML и CSS, JavaScript, C++ и другие, размещенные на главной странице.

Если что-то не нашли, то воспользуйтесь поиском по сайту, который находится на главной странице в самом верху.

Удачи в обучении!

Закрыть окно