0 знаков
65. Продолжаем написание классов в Python
Кратко- В этом уроке мы построим более реалистичный класс для демонстрации всех основных идей ООП в Python.
- Реализуем два класса
User
иAdmin
для создания пользователей, хранения данных и выполнения функций.- Создадим конструктор класса
__init__
для заполнения основных полей (логин, пароль).- Добавим атрибуты класса (имя пользователя, рейтинг).
- Реализуем методы класса (
change_rating
,change_name
).- Перегрузка операторов для корректировки вывода экземпляров класса.
- Наследование классов для расширения возможностей класса
User
.- Записываем результат в базу данных с помощью модулей
pickle
,dbm
иshelve
.
Введение
В предыдущем уроке мы начали более глубокое изучение классов в Python. В этом уроке построим более реалистичный класс (и не один) для демонстрации всех основных идей ООП.
Мы реализуем два класса User
и Admin
, которые будут создавать пользователей, хранить определенные данные и выполнять определенные функции. Попутно повторим основные концепции ООП.
ПримечаниеВ этом уроке упущены некоторые детали основных концепций ООП, чтобы не нагружать урок теорией. В следующих уроках будем разбираться с ними более детально.
Начнем написание класса User
. Напомню, имена классов в Python принято начинать с буквы верхнего регистра. Так же мы создадим отдельный модуль user.py
для нашего класса.
''' user.py '''
class User:
pass
Далее начнем добавлять функциональности в наш класс User
.
Создаем конструктор и добавляем атрибуты класса
Следующим шагом будет написание конструктора класса __init__
. Создание нового экземпляра означает «регистрацию» нашего очередного пользователя, поэтому в конструкторе логичнее всего реализовать заполнение основных полей: логин и пароль.
''' user.py '''
class User:
def __init__(self, login, password):
self.login = login
self.password = password
Да, пароль будет храниться в открытом виде, что является категорически недопустимым в реальных системах. Не будем усложнять код и продолжим изучать принципы ООП в Python.
Вы так же могли заметить наличие в конструкторе имен, которые повторяются дважды. Давайте разберемся. Параметр 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
, узнали о простом способе сохранения любых объектов на физическом носителе. В следующем уроке продолжим изучение классов. Нам еще есть о чем поговорить.
Тест
Похожие уроки Codebra
Подписывайся на наш Telegram-канал!
Новости, полезный материал,
программирование и ИБ
Подписывайся на наш Telegram-канал!
Новости, полезный материал,
программирование и ИБ