← Назад к вопросам

Как реализовать приватный атрибут в классе?

1.7 Middle🔥 191 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Приватные атрибуты в Python

Python не имеет строгого понятия "приватных" переменных, как в Java или C++. Вместо этого используется соглашение и механизм name mangling. Это важный аспект проектирования классов.

Подход 1: Single Underscore (конвенция)

class BankAccount:
    def __init__(self, balance):
        self._balance = balance  # "Protected" — для внутреннего использования
    
    def deposit(self, amount):
        if amount > 0:
            self._balance += amount
    
    def get_balance(self):
        return self._balance

# Single underscore — это просто соглашение (convention)
# Ничто не мешает извне получить доступ
account = BankAccount(1000)
print(account._balance)  # 1000 — ничто не мешает!
account._balance = -999999  # Можно менять напрямую (плохая идея)

# Соглашение говорит: "Не трогай _balance, используй методы"

Подход 2: Double Underscore (Name Mangling)

class BankAccount:
    def __init__(self, balance):
        self.__balance = balance  # Double underscore — name mangling
    
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount  # Доступно внутри класса
    
    def get_balance(self):
        return self.__balance
    
    def withdraw(self, amount):
        if 0 < amount <= self.__balance:
            self.__balance -= amount
            return True
        return False

account = BankAccount(1000)

# Попытка прямого доступа вызовет ошибку
print(account.__balance)  # AttributeError: 'BankAccount' object has no attribute '__balance'

# Python переименовал атрибут в _BankAccount__balance (name mangling)
print(account._BankAccount__balance)  # 1000 — можно ещё получить доступ, но это плохая идея!

# Правильный способ:
print(account.get_balance())  # 1000
account.deposit(500)
print(account.get_balance())  # 1500

Как работает Name Mangling

class Parent:
    def __init__(self):
        self.__private = "parent private"
        self._protected = "parent protected"

class Child(Parent):
    def __init__(self):
        super().__init__()
        self.__private = "child private"  # Другой атрибут!
    
    def show_private(self):
        # __private в контексте Child указывает на _Child__private
        print(self.__private)  # "child private"

obj = Child()
obj.show_private()  # "child private"

# Parent и Child имеют разные __private атрибуты
print(dir(obj))  # видны _Parent__private и _Child__private

Name mangling не даёт безопасность, а просто предотвращает случайные конфликты имён в наследовании.

Подход 3: Properties (рекомендуется)

class BankAccount:
    def __init__(self, balance):
        self._balance = balance  # Single underscore — внутренние данные
    
    @property
    def balance(self):
        """Получить баланс (read-only)"""
        return self._balance
    
    def deposit(self, amount):
        if amount > 0:
            self._balance += amount
        else:
            raise ValueError("Amount must be positive")
    
    def withdraw(self, amount):
        if amount > self._balance:
            raise ValueError("Insufficient funds")
        self._balance -= amount

# Использование
account = BankAccount(1000)
print(account.balance)  # 1000 (свойство, как атрибут)

account.deposit(500)
print(account.balance)  # 1500

# account.balance = 999999  # AttributeError: can't set attribute
# Property запрещает прямое присваивание (read-only)

Подход 4: Setter и Getter

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius
    
    @property
    def celsius(self):
        """Получить температуру в Цельсиях"""
        return self._celsius
    
    @celsius.setter
    def celsius(self, value):
        """Установить температуру в Цельсиях"""
        if value < -273.15:
            raise ValueError("Below absolute zero")
        self._celsius = value
    
    @property
    def fahrenheit(self):
        """Автоматический расчёт температуры в Фаренгейтах"""
        return self._celsius * 9/5 + 32
    
    @fahrenheit.setter
    def fahrenheit(self, value):
        """Установить через Фаренгейты"""
        self._celsius = (value - 32) * 5/9

# Использование
temp = Temperature(0)
print(temp.celsius)  # 0
print(temp.fahrenheit)  # 32

temp.celsius = 100
print(temp.fahrenheit)  # 212

temp.fahrenheit = 32
print(temp.celsius)  # 0

# Валидация работает
temp.celsius = -300  # ValueError: Below absolute zero

Подход 5: 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(10, 20)
print(point.x, point.y)  # 10 20

point.z = 30  # AttributeError: 'Point' object has no attribute 'z'
# __slots__ запрещает добавлять новые атрибуты

Подход 6: Dataclass с field privacy

from dataclasses import dataclass, field
from typing import Any

@dataclass
class User:
    name: str
    _email: str = field(repr=False)  # Не показывать в repr
    _password_hash: str = field(repr=False, init=False)
    
    def __post_init__(self):
        # Вычислить хеш пароля после инициализации
        self._password_hash = hash(self._email)
    
    @property
    def email(self):
        return self._email
    
    @email.setter
    def email(self, value):
        if '@' not in value:
            raise ValueError("Invalid email")
        self._email = value
        self._password_hash = hash(value)  # Пересчитать хеш

user = User(name="John", _email="john@example.com")
print(user)  # User(name='John') — email скрыт в repr
print(user.email)  # john@example.com — но доступен через свойство

Сравнение подходов

# ❌ Плохо: всё public
class BadAccount:
    def __init__(self, balance):
        self.balance = balance  # Ничто не мешает нарушить инвариант

a = BadAccount(1000)
a.balance = -999  # Не должно быть возможно!

# ⚠️ Условно хорошо: single underscore (соглашение)
class OkAccount:
    def __init__(self, balance):
        self._balance = balance
    
    def deposit(self, amount):
        if amount > 0:
            self._balance += amount

a = OkAccount(1000)
a._balance = -999  # Можно, но "не трогай!"

# ✅ Хорошо: property (инкапсуляция)
class GoodAccount:
    def __init__(self, balance):
        self._balance = balance
    
    @property
    def balance(self):
        return self._balance
    
    def deposit(self, amount):
        if amount > 0:
            self._balance += amount

a = GoodAccount(1000)
print(a.balance)  # 1000
a.balance = -999  # AttributeError — невозможно!

# ✅✅ Лучше всего: явные методы
class BestAccount:
    def __init__(self, balance):
        if balance < 0:
            raise ValueError()
        self._balance = balance
    
    def get_balance(self):
        return self._balance
    
    def deposit(self, amount):
        if amount <= 0:
            raise ValueError()
        self._balance += amount
    
    def withdraw(self, amount):
        if amount > self._balance:
            raise ValueError()
        self._balance -= amount

Когда использовать что

  • Single underscore (_) — для "protected" данных, которые внутренние, но видны наследникам
  • Double underscore (__) — редко, только если нужна защита от конфликтов имён в наследовании
  • Properties — для controlled access (валидация, вычисления)
  • Явные методы — самый ясный и предсказуемый подход
  • Dataclass — для простых data containers с типизацией

Лучшая практика

from dataclasses import dataclass
from typing import Optional

@dataclass
class BankAccount:
    _balance: float = 0.0  # Private field
    
    def __post_init__(self):
        if self._balance < 0:
            raise ValueError("Balance cannot be negative")
    
    @property
    def balance(self):
        """Получить текущий баланс"""
        return self._balance
    
    def deposit(self, amount: float) -> None:
        """Пополнить счёт"""
        if amount <= 0:
            raise ValueError("Amount must be positive")
        self._balance += amount
    
    def withdraw(self, amount: float) -> bool:
        """Снять со счёта. Возвращает True если успешно"""
        if 0 < amount <= self._balance:
            self._balance -= amount
            return True
        return False

В Python нет истинной приватности, но есть соглашения и инструменты для инкапсуляции. Используй их для создания понятного и безопасного API класса.

Как реализовать приватный атрибут в классе? | PrepBro