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

Зачем нужно нижнее подчеркивание в названии метода в Python?

1.3 Junior🔥 201 комментариев
#Python Core

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

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

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

Подчеркивание в названии методов Python

Подчеркивание (underscore) в Python используется для обозначения уровня видимости и назначения методов. Это не реальное ограничение доступа, а соглашение для других разработчиков.

Четыре типа методов

1. Публичный метод (нет подчеркивания)

class MyClass:
    def public_method(self):
        """Предназначен для использования снаружи класса"""
        return "I'm public"

obj = MyClass()
obj.public_method()  # ✅ Нормально, как ожидается

Используй когда:

  • Метод часть public API
  • Другие классы должны его вызывать
  • Это стабильный интерфейс

2. Защищённый метод (одно подчеркивание спереди)

class Parent:
    def _protected_method(self):
        """Для использования наследниками и внутри класса"""
        return "I'm protected"

class Child(Parent):
    def some_method(self):
        return self._protected_method()  # ✅ OK для наследников

obj = Child()
obj._protected_method()  # ⚠️ Технически работает, но соглашение: не трогай!

Используй когда:

  • Метод для internal использования
  • Может переопределяться наследниками
  • НЕ часть публичного API

3. Приватный метод (двойное подчеркивание спереди)

class MyClass:
    def __private_method(self):
        """Только для этого класса, не для наследников"""
        return "I'm private"
    
    def public_method(self):
        return self.__private_method()  # ✅ OK внутри класса

obj = MyClass()
obj.__private_method()  # ❌ AttributeError!
obj._MyClass__private_method()  # Это сработает, но очень плохая практика

Используй когда:

  • Метод только для этого класса
  • Даже наследники не должны его переопределять
  • Нужна настоящая приватность

4. Dunder методы (двойное подчеркивание с обеих сторон)

class MyClass:
    def __init__(self):  # Dunder (Double UNDERscore)
        self.value = 0
    
    def __str__(self):
        return f"MyClass: {self.value}"
    
    def __repr__(self):
        return f"MyClass({self.value})"

obj = MyClass()
print(obj)  # Вызывает __str__ автоматически
repr(obj)   # Вызывает __repr__ автоматически

Это специальные методы, которые Python вызывает неявно.

Практические примеры

1. Иерархия видимости в классе

class Database:
    def __init__(self, connection_string):
        self._connection = self._create_connection(connection_string)
    
    # Публичный метод — часть API
    def query(self, sql):
        """Выполнить запрос"""
        return self._execute(sql)
    
    # Защищённый метод — для наследников
    def _execute(self, sql):
        """Выполнить с логированием"""
        print(f"Executing: {sql}")
        return self.__run(sql)
    
    # Приватный метод — только для этого класса
    def __run(self, sql):
        """Реальное выполнение (implementation detail)"""
        return self._connection.execute(sql)
    
    def _create_connection(self, connection_string):
        """Создать connection (для наследников переопределять)"""
        # implementation
        pass

class CustomDatabase(Database):
    def _create_connection(self, connection_string):
        """Переопределяю для наследника"""
        return CustomConnection(connection_string)

db = CustomDatabase("postgres://...")
db.query("SELECT * FROM users")  # ✅ Public API
db._execute("SELECT ...")          # ⚠️ Возможно, но не должно
db.__run("SELECT ...")             # ❌ Ошибка (name mangling)

2. Когда использовать защищённый метод

class BaseCache:
    def __init__(self):
        self._data = {}  # Protected атрибут
    
    def get(self, key):
        """Public API"""
        if key in self._data:
            self._on_hit(key)  # Protected метод
        return self._data.get(key)
    
    def _on_hit(self, key):
        """Для наследников переопределять"""
        pass

class StatsCache(BaseCache):
    def __init__(self):
        super().__init__()
        self.hits = 0
    
    def _on_hit(self, key):
        """Переопределяю для сбора статистики"""
        self.hits += 1

3. Когда использовать приватный метод

class PaymentProcessor:
    def process(self, amount, card):
        """Public API для обработки платежа"""
        if not self.__validate_card(card):  # Private
            raise ValueError("Invalid card")
        return self.__charge_card(card, amount)  # Private
    
    def __validate_card(self, card):
        """Implementation detail, не переопределять"""
        return len(card.number) == 16
    
    def __charge_card(self, card, amount):
        """Implementation detail, не трогать"""
        # Реальная интеграция с платёжной системой
        pass

processor = PaymentProcessor()
processor.process(100, card)  # ✅ OK
processor.__validate_card(card)  # ❌ AttributeError

Name Mangling

Когда используешь __method, Python переименовывает его:

class MyClass:
    def __private(self):
        return "private"

obj = MyClass()
print(dir(obj))  # Видим: '_MyClass__private' вместо '__private'

# Python скрывает, но всё ещё доступно (для опытных)
obj._MyClass__private()  # Работает, но это плохо!

Best Practices

✅ Правильно:

class APIClient:
    def __init__(self, api_key):
        self._api_key = api_key  # Protected (может переопределяться)
    
    def request(self, endpoint, params):  # Public API
        headers = self._get_headers()  # Protected, для наследников
        return self.__send_request(endpoint, params, headers)  # Private
    
    def _get_headers(self):
        """Для наследников переопределять"""
        return {"Authorization": f"Bearer {self._api_key}"}
    
    def __send_request(self, endpoint, params, headers):
        """Implementation detail"""
        pass

❌ Неправильно:

# Слишком много privates (нельзя расширять)
class RigidClass:
    def __method1(self):
        pass
    def __method2(self):
        pass
    def __method3(self):
        pass

# Наследник не может ничего переопределить!

# Правильный подход
class FlexibleClass:
    def _method1(self):
        """Для наследников"""
        pass
    def _method2(self):
        pass
    def _method3(self):
        pass

Соглашения Python

class Example:
    # Публичный
    def method(self):
        """Часть API, стабилен"""
        pass
    
    # Защищённый (protected)
    def _internal_method(self):
        """Может меняться, для наследников"""
        pass
    
    # Приватный (private)
    def __implementation_detail(self):
        """Не трогать! Только этот класс"""
        pass
    
    # Magic/Dunder методы
    def __str__(self):
        """Python вызывает автоматически"""
        pass
    
    def __getattr__(self, name):
        """Динамический доступ к атрибутам"""
        pass

Правило thumb

  1. Нет подчеркивания — используй, если уверен, что это нужно другим
  2. Одно подчеркивание — используй по умолчанию для "внутреннего"
  3. Двойное подчеркивание — используй редко, только для реальной приватности
  4. Dunder методы — используй только когда нужна специальная семантика

Реальный пример

from sqlalchemy.orm import Session

class UserRepository:
    def __init__(self, db: Session):
        self._db = db  # Protected
    
    # Public API
    def get_user(self, user_id: int):
        return self._query_user(user_id)
    
    # Protected (для наследников)
    def _query_user(self, user_id: int):
        return self._db.query(User).filter(User.id == user_id).first()
    
    # Private (только детали реализации)
    def __build_cache_key(self, user_id: int) -> str:
        return f"user:{user_id}"

Резюме

Подчеркивание используется для:

  • Никакого — публичный интерфейс
  • Одно _ — внутренний код, может меняться
  • Двойное __ — приватный, не трогать
  • __dunder__ — магические методы Python

Это соглашение, не закон. Python их не запрещает, но разработчики их уважают.

Зачем нужно нижнее подчеркивание в названии метода в Python? | PrepBro