Приватные атрибуты
Часть раздела Object Oriented Programming путешествия по Python на Coddy — урок 15 из 64.
Приватные атрибуты используют подчеркивания, чтобы указать, что к определенным данным не следует обращаться напрямую извне класса.
Зачем нам нужны приватные атрибуты? Представьте, что у вас есть банковский счет. Вы не хотите, чтобы кто-то напрямую менял ваш баланс — они должны действовать через соответствующие каналы (методы внесения/снятия средств), чтобы банк мог проверить транзакцию, выявить мошенничество, вести учет и т. д. Приватные атрибуты работают так же — они защищают ваши данные от некорректного изменения.
Вот пример использования одного подчеркивания (соглашение для «внутреннего использования»):
class Person:
def __init__(self, name, age):
self._name = name # "protected" — внутреннее использование
self._age = age # "protected" — внутреннее использованиеК атрибутам с одним нижним подчеркиванием все еще можно получить доступ, но это сигнал о том, что этого делать не следует:
person = Person("Alice", 30)
print(person._name) # Работает, но не рекомендуетсяИспользуйте двойное подчеркивание для более строгой инкапсуляции (искажение имен):
class Person:
def __init__(self, name, age):
self.__name = name # "приватный" - применяется искажение имени
self.__age = age # "приватный" - применяется искажение имени
def get_name(self):
return self.__name
def set_age(self, age):
if age >= 0:
self.__age = age
else:
print("Age must be positive!")Зачем использовать методы геттеры и сеттеры? Они подобны привратникам. Вместо того чтобы позволять кому угодно изменять __age напрямую (что может привести к отрицательному возрасту или другим неверным данным), мы заставляем их использовать set_age(), который сначала проверяет входные данные. Это предотвращает ошибки и обеспечивает целостность данных.
У «лишнего кода» есть цель:
get_name()— контролируемый доступ для чтения имениset_age(age)— проверяет возраст перед разрешением изменений (никаких отрицательных значений!)- Без этих методов кто-то мог бы установить
age = -100и сломать вашу программу
Используйте методы доступа для взаимодействия с приватными атрибутами:
person = Person("Bob", 25)
print(person.get_name()) # Bob
person.set_age(30) # Допустимо: возраст становится 30
person.set_age(-5) # Недопустимо: Возраст должен быть положительным! (возраст остается 30)Видите преимущество? Метод set_age() предотвращает ввод некорректных данных. Без него вы могли бы случайно создать человека с возрастом -5, что не имеет смысла!
Атрибуты с двойным подчёркиванием подвергаются «искажению имён» (name mangling), но к ним всё равно можно получить доступ:
person = Person("Charlie", 35)
# Это не сработает:
# print(person.__name) # AttributeError
# Но это работает (не рекомендуется):
print(person._Person__name) # CharlieПример из реальной жизни — зачем весь этот код?
class BankAccount:
def __init__(self, balance):
self.__balance = balance # Приватный: нельзя изменить напрямую
def deposit(self, amount):
if amount > 0:
self.__balance += amount
return True
return False
def get_balance(self):
return self.__balance
# Без приватных атрибутов:
# account.__balance = -1000000 # Катастрофа! Разрешен отрицательный баланс
# С приватными атрибутами:
account = BankAccount(100)
account.deposit(50) # Безопасно: проверено
print(account.get_balance()) # 150«Лишний код» (методы) защищает ваши данные от недопустимых изменений. Это как нанять охранников вместо того, чтобы оставлять ценности без присмотра.
Вывод:
Alice
Bob
Age must be positive!
Charlie
150Одиночное подчеркивание _attribute по соглашению означает «только для внутреннего использования». Двойное подчеркивание __attribute активирует искажение имен (name mangling) для более строгой инкапсуляции. Используйте методы геттеры/сеттеры (методы доступа), чтобы правильно взаимодействовать с приватными данными и добавить валидацию. «Дополнительный код» предотвращает ошибки, гарантируя, что данные всегда корректны.
Задание
ЛегкоЗавершите класс User в user.py с функциональностью приватного пароля, затем используйте его в driver.py. Следуйте комментариям TODO, чтобы реализовать методы проверки и смены пароля.
Шпаргалка
Приватные атрибуты используют подчеркивания, чтобы указать, что к данным не следует обращаться напрямую извне класса.
Одинарное подчеркивание (_attribute) — соглашение для «внутреннего использования»:
class Person:
def __init__(self, name, age):
self._name = name # "protected" — внутреннее использование
self._age = age # "protected" — внутреннее использованиеДвойное подчеркивание (__attribute) — запускает искажение имен (name mangling) для более строгой инкапсуляции:
class Person:
def __init__(self, name, age):
self.__name = name # "private" — имя искажается
self.__age = age # "private" — имя искажается
def get_name(self):
return self.__name
def set_age(self, age):
if age >= 0:
self.__age = age
else:
print("Age must be positive!")Используйте методы доступа для взаимодействия с приватными атрибутами:
person = Person("Bob", 25)
print(person.get_name()) # Bob
person.set_age(30)
person.set_age(-5) # Age must be positive!Атрибуты с двойным подчеркиванием подвергаются искажению имен, но к ним все равно можно получить доступ (не рекомендуется):
# Это не сработает:
# print(person.__name) # AttributeError
# Но это работает (не рекомендуется):
print(person._Person__name) # CharlieПопробуйте сами
# TODO: Импортируйте класс User из модуля user
# TODO: Получите тестовый случай из входных данных
test_case = input()
if test_case == "constructor_test":
# TODO: Создайте объект User с начальным паролем "secure123"
user = User("secure123")
print("User created successfully")
print("Initial password set")
elif test_case == "correct_password_test":
# TODO: Создайте объект User с начальным паролем "secure123"
user = User("secure123")
# TODO: Протестируйте метод check_password с правильным паролем
# Выведите "Password check successful!", если он возвращает True
# Выведите "Password check failed!", если он возвращает False
result = user.check_password("secure123")
if result:
print("Password check successful!")
else:
print("Password check failed!")
elif test_case == "incorrect_password_test":
# TODO: Создайте объект User с начальным паролем "secure123"
user = User("secure123")
# TODO: Протестируйте метод check_password с неправильным паролем "wrong"
# Выведите "Incorrect password rejected!", если он возвращает False
# Выведите "Security issue: incorrect password accepted!", если он возвращает True
result = user.check_password("wrong")
if not result:
print("Incorrect password rejected!")
else:
print("Security issue: incorrect password accepted!")
elif test_case == "change_password_test":
# TODO: Создайте объект User с начальным паролем "secure123"
user = User("secure123")
# TODO: Протестируйте смену пароля с "secure123" на "newpass456"
# Выведите "Password changed successfully!", если метод возвращает True
# Выведите "Password change failed!", если метод возвращает False
result = user.change_password("secure123", "newpass456")
if result:
print("Password changed successfully!")
else:
print("Password change failed!")
# TODO: Проверьте, что новый пароль работает, вызвав check_password("newpass456")
# Выведите "New password works!", если метод возвращает True
# Выведите "New password doesn't work!", если метод возвращает False
if user.check_password("newpass456"):
print("New password works!")
else:
print("New password doesn't work!")
elif test_case == "change_password_wrong_old_test":
# TODO: Создайте объект User с начальным паролем "secure123"
user = User("secure123")
# TODO: Попробуйте сменить пароль, указав неверный старый пароль
# Выведите "Security working: incorrect old password rejected!", если метод возвращает False
# Выведите "Security issue: password changed with wrong old password!", если метод возвращает True
result = user.change_password("wrong", "hackerpw")
if not result:
print("Security working: incorrect old password rejected!")
else:
print("Security issue: password changed with wrong old password!")
elif test_case == "comprehensive_test":
# TODO: Создайте объект User с начальным паролем "secure123"
user = User("secure123")
print("User created with initial password")
# TODO: Протестируйте метод check_password с правильным паролем
if user.check_password("secure123"):
print("Initial password verification successful")
# TODO: Протестируйте метод check_password с неправильным паролем
if not user.check_password("wrongpass"):
print("Incorrect password properly rejected")
# TODO: Протестируйте смену пароля с "secure123" на "newpass456"
if user.change_password("secure123", "newpass456"):
print("Password successfully changed")
# TODO: Проверьте, что старый пароль больше не работает
if not user.check_password("secure123"):
print("Old password no longer works")
# TODO: Проверьте, что новый пароль работает
if user.check_password("newpass456"):
print("New password works correctly")
# TODO: Попробуйте сменить пароль, указав неверный старый пароль
if not user.change_password("wrongold", "hackerpw"):
print("Security maintained: wrong old password rejected")
# TODO: Проверьте, что пароль остается в безопасности после неудачной попытки смены
if user.check_password("newpass456"):
print("Password remains secure after failed change attempt")
else:
print("Unknown test case")В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыПараметр selfМетодыАтрибутыМетод-конструктор (__init__)Повторение — Простой калькулятор4Наследование
Базовое наследованиеФункция super()Переопределение методовМножественное наследованиеПорядок разрешения методовИтоги — Иерархия сотрудников7Специальные методы
Введение в магические методыПерегрузка операторовМагические методы контейнеровПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy5Полиморфизм
Снова о переопределении методовУтиная типизацияАбстрактные классы и методыПроектирование интерфейсовИтоги — Калькулятор фигур8Продвинутые концепции ООП
Композиция против наследованияМиксиныСтатические методы и методы классаДекораторы классовМенеджеры контекста11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Свойства классов
Переменные экземпляра и классаДекораторы свойствПриватные атрибутыИтоги — Менеджер банковского счета6Инкапсуляция
Public, Protected, Private члены классаМодификаторы доступаСокрытие данныхДекораторы property: продвинутый уровеньИтоги: Система учета студентов12Проект: Управление библиотекой
Обзор проектаКлассы Book и User