Шаг 134.
Python: сборник рецептов.
Классы и объекты. Инкапсуляция имен в классе

    На этом шаге мы рассмотрим вопросы, связанные с использованием одного и двух подчеркиваний.

Задача

    Вам нужно инкапсулировать "приватные" данные в экземпляре класса, но вас беспокоит, что в Python нет контроля доступа.

Решение

    Вместо того чтобы полагаться на возможности языка по инкапсулированию данных, от программистов на Python ожидается соблюдение определенных соглашений о наименовании, касающихся намеренного использования данных и методов. Первое соглашение состоит в том, что любое имя, которое начинается с одного нижнего подчеркивания (_), должно рассматриваться как внутренняя реализация. Например:

class A:
    def  _init__(self):
        self._internal = 0  # Внутренний атрибут
        self.public = 1	    # Внешний атрибут

    def public_method(self):
        ''''
        A public method
        ''''
        .  .  .
    def _internal_method(self):
        .  .  .

    Python не запрещает доступ к внутренним именам. Однако это считается неправильным, и в результате может получиться хрупкий код. Также стоит отметить, что имена, начинающиеся с нижнего подчеркивания, еще используются для модулей и функций уровня модуля. Например, если вы видите имя модуля, которое начинается с нижнего подчеркивания (например, _socket), то это внутренняя реализация. Аналогично функции уровня модуля, такие как sys._getframe(), должны применяться очень осторожно.

    Вы можете натолкнуться на имена внутри классов, которые начинаются с двух нижних подчеркиваний (__). Например:

class B:
    def  __init__(self):
         self.__private = 0
    def __private_method(self):
        .  .  .
    def public_method(self):
        .  .  .
    self.__private_method()
        .  .  .

    Использование двойного нижнего подчеркивания вызывает искажение имени в другое. Если говорить конкретно, то приватные атрибуты в представленном выше классе переименуются в _B__private и _B__private method соответственно. Здесь вы можете спросить, зачем нужны такие искажения. Причина - наследование: такие атрибуты не могут быть переопределены через наследование. Например:

class C(B):
    def __init__(self):
        super().__init__()
        self.private = 1 # Не перегружает B.__private
    # Не перегружает B.__private_method()
    def __private_method(self):
        .  .  .

    Здесь приватные имена __private и __private_method переименуются в _C__private и _C__private_method, которые отличаются от искаженных имен в базовом классе B.

Обсуждение

    Факт того, что существует два разных соглашения (одиночное нижнее подчеркивание против двойного) для определения "приватных" атрибутов, приводит к логичному вопросу того, какой стиль вам стоит использовать. Для большей части вашего кода вы, вероятно, должны делать ваши имена непубличными с помощью одного нижнего подчеркивания. Если же, однако, вы знаете, что ваш код будет использовать подклассы и имеет внутренние атрибуты, которые должны быть скрыты от подклассов, то используйте двойные подчеркивания.

    Также стоит отметить, что иногда вы можете определить переменную, которая вступает в конфликт с зарезервированным именем. Для таких случаев нужно использовать нижнее подчеркивание в конце:

lambda_ = 2.0
# Завершающий _ помогает избежать проблем с ключевым словом lambda

    Причина не использовать нижнее подчеркивание в начале имени в этом случае заключается в том, что это позволяет избежать сомнений по поводу причины его использования (то есть использование подчеркивания в начале может быть истолковано как указание на приватность значения). Использование одного подчеркивания в конце решает эту проблему.

    На следующем шаге мы рассмотрим создание управляемых атрибутов.




Предыдущий шаг Содержание Следующий шаг