← Назад к вопросам
Как скрыть методы и переменные класса в Python?
1.3 Junior🔥 161 комментариев
#Python Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Инкапсуляция: скрытие методов и переменных в Python
Это один из столпов объектно-ориентированного программирования. Python имеет несколько подходов к инкапсуляции, от конвенций до механизмов языка.
Уровни видимости в Python
Потому что Python руководствуется принципом "мы все взрослые люди", он не имеет жёстких ограничений видимости как Java или C++. Вместо этого используются соглашения:
1. Публичные атрибуты (public)
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner # Публичный атрибут
self.balance = balance # Публичный атрибут
def deposit(self, amount):
"""Публичный метод"""
self.balance += amount
def withdraw(self, amount):
"""Публичный метод"""
if amount <= self.balance:
self.balance -= amount
account = BankAccount("Alice", 1000)
print(account.balance) # 1000 - свободный доступ
account.balance = 5000000 # Можем просто изменить! (плохо)
print(account.balance) # 5000000
Проблема: ничто не мешает нарушить инвариант класса.
2. Protected атрибуты (защищённые с одним подчёркиванием)
Конвенция, которая говорит: "Используй это на свой риск".
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self._balance = balance # Protected: одно подчёркивание
def _validate_balance(self, amount): # Protected метод
"""Внутренняя проверка (не вызывай напрямую)"""
return amount > 0
def deposit(self, amount):
"""Публичный метод"""
if self._validate_balance(amount):
self._balance += amount
else:
raise ValueError("Сумма должна быть положительной")
account = BankAccount("Alice", 1000)
print(account._balance) # 1000 - можно, но не рекомендуется
account._balance = -5000 # Технически можно, но нарушаем инвариант
Соглашение: одно подчёркивание сигнализирует: "Это внутренний API, не используй".
3. Private атрибуты (приватные с двойным подчёркиванием)
Name mangling — механизм "скрывания" с двойным подчёркиванием.
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.__balance = balance # Private: двойное подчёркивание
def __validate_balance(self, amount): # Private метод
return amount > 0
def deposit(self, amount):
if self.__validate_balance(amount):
self.__balance += amount
def get_balance(self):
return self.__balance
account = BankAccount("Alice", 1000)
print(account.get_balance()) # 1000 - через getter
print(account.__balance) # AttributeError!
# Но Python переименовывает атрибут (name mangling)
print(account._BankAccount__balance) # 1000 - можно найти
account._BankAccount__balance = -5000 # Можно изменить (но не нужно)
Как работает name mangling:
class MyClass:
def __init__(self):
self.__private = 42
obj = MyClass()
print(dir(obj)) # Видим '_MyClass__private' вместо '__private'
# Python переименовывает __private в _ClassName__private
# Это НЕ криптографическое скрытие, а просто соглашение
Правильная инкапсуляция с @property
Управление доступом
class BankAccount:
def __init__(self, owner, balance):
self._owner = owner
self._balance = balance
@property
def balance(self):
"""Getter для баланса (читаем как атрибут)"""
return self._balance
@balance.setter
def balance(self, amount):
"""Setter с валидацией"""
if amount < 0:
raise ValueError("Баланс не может быть отрицательным")
self._balance = amount
@property
def owner(self):
return self._owner
@owner.setter
def owner(self, name):
if not name or len(name) < 2:
raise ValueError("Некорректное имя")
self._owner = name
# Использование как обычные атрибуты
account = BankAccount("Alice", 1000)
print(account.balance) # 1000 - читаем
account.balance = 2000 # 2000 - пишем с проверкой
account.balance = -100 # ValueError: Баланс не может быть отрицательным
Преимущества:
- API остаётся простой (выглядит как атрибут)
- Можем добавить валидацию позже
- Не нужно менять код у клиентов
Только чтение (read-only)
class User:
def __init__(self, user_id, name):
self._id = user_id # Только внутри
self._name = name
@property
def id(self):
"""Читаем ID, но не можем изменить"""
return self._id
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
user = User(123, "Alice")
print(user.id) # 123
user.id = 456 # AttributeError: can't set attribute
user.name = "Bob" # OK
Практические паттерны
Паттерн 1: Lazy initialization
class DataProcessor:
def __init__(self):
self._data = None # Ленивая инициализация
@property
def data(self):
if self._data is None:
self._data = self._load_data() # Загружаем при первом доступе
return self._data
def _load_data(self):
"""Дорогостоящая операция"""
return [1, 2, 3, 4, 5]
processor = DataProcessor()
print(processor.data) # Загружается здесь
print(processor.data) # Используется кэш
Паттерн 2: Computed property
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self):
"""Вычисляется при доступе"""
return self.width * self.height
@property
def perimeter(self):
return 2 * (self.width + self.height)
rect = Rectangle(5, 3)
print(rect.area) # 15 - вычисляется
print(rect.perimeter) # 16 - вычисляется
rect.width = 10
print(rect.area) # 30 - пересчитывается
Паттерн 3: Наблюдатель (Observer)
class User:
def __init__(self, name):
self._name = name
self._watchers = [] # Список наблюдателей
@property
def name(self):
return self._name
@name.setter
def name(self, value):
old_value = self._name
self._name = value
# Уведомляем наблюдателей об изменении
for watcher in self._watchers:
watcher(f"Name changed from {old_value} to {value}")
def watch(self, callback):
"""Добавляем наблюдателя"""
self._watchers.append(callback)
user = User("Alice")
user.watch(lambda msg: print(f"[Event] {msg}"))
user.name = "Bob" # [Event] Name changed from Alice to Bob
Защита класса целиком
Использование slots
Ограничиваем атрибуты класса для экономии памяти и защиты:
class Point:
__slots__ = ['_x', '_y'] # Только эти атрибуты разрешены
def __init__(self, x, y):
self._x = x
self._y = y
@property
def x(self):
return self._x
@property
def y(self):
return self._y
point = Point(3, 4)
print(point.x) # 3
point.z = 5 # AttributeError: 'Point' object has no attribute 'z'
# Преимущества:
# - Экономия памяти (~40%)
# - Предотвращение случайного добавления атрибутов
# - Быстрее доступ к атрибутам
Использование getattr и setattr
class RestrictedClass:
_allowed = {'name', 'age'}
def __setattr__(self, name, value):
if name not in self._allowed:
raise AttributeError(f"{name} не разрешён")
super().__setattr__(name, value)
def __getattr__(self, name):
if name not in self._allowed:
raise AttributeError(f"{name} не существует")
return super().__getattribute__(name)
obj = RestrictedClass()
obj.name = "Alice" # OK
obj.age = 25 # OK
obj.email = "test" # AttributeError
Сравнение подходов
# 1. Публичный (public)
self.value = 10 # Полный доступ, риск нарушения инварианта
# 2. Protected
self._value = 10 # Соглашение: "используй на свой риск"
# 3. Private
self.__value = 10 # Name mangling (не полная защита)
# 4. Property (РЕКОМЕНДУЕТСЯ)
@property
def value(self):
return self._value
@value.setter
def value(self, v):
if v < 0:
raise ValueError()
self._value = v
# 5. __slots__
__slots__ = ['_value'] # Ограничение атрибутов
Лучшие практики
class GoodClass:
"""Правильная инкапсуляция"""
__slots__ = ['_data', '_cache']
def __init__(self, data):
self._data = data # Protected для внутреннего использования
self._cache = None # Private для кэша
@property
def data(self):
"""Публичное свойство (read-only)"""
return self._data
@property
def cached_result(self):
"""Ленивое вычисление"""
if self._cache is None:
self._cache = self._expensive_computation()
return self._cache
def _expensive_computation(self):
"""Protected метод: часть внутреннего API"""
return sum(self._data) * 2
def reset_cache(self):
"""Публичный метод для управления"""
self._cache = None
# Использование
obj = GoodClass([1, 2, 3])
print(obj.data) # [1, 2, 3]
print(obj.cached_result) # 12 (вычисляется)
print(obj.cached_result) # 12 (из кэша)
obj.reset_cache()
print(obj.cached_result) # 12 (пересчитывается)
Резюме
- Публичные (
name) — полный доступ - Protected (
_name) — соглашение "не трогай" - Private (
__name) — name mangling (не полная защита) - @property — управление доступом с валидацией (РЕКОМЕНДУЕТСЯ)
- slots — экономия памяти и защита от случайных атрибутов
- Python не имеет полной инкапсуляции — используй соглашения и @property
- Лучший подход: _protected переменные + @property для публичного API