Зарегистрируйтесь, чтобы продолжить обучение

Модификаторы доступа Python: Погружаясь в классы

Наследование — это важная концепция объектно-ориентированного программирования, которая позволяет одним классам наследовать функциональность, свойства и методы других классов. Но нужно понимать, как отдельные методы и свойства становятся доступными в процессе наследования.

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

Модификаторы доступа в Python

Python не предоставляет никакой системы строгой инкапсуляции, которая присуща некоторым языкам программирования. Из-за этого вы можете получить доступ к любому атрибуту или методу класса из внешнего кода. Однако существует общепринятое соглашение между разработчиками, что атрибуты и методы, предназначенные для внутреннего использования, должны начинаться с подчеркивания (например, _visible). Это подчеркивание служит индикатором для других разработчиков, что этот атрибут или метод не должен изменяться напрямую извне. Но всегда следует помнить, что Python не предоставляет никаких средств для принудительного соблюдения этого соглашения, и атрибуты с подчеркиванием по-прежнему доступны извне.

Публичные атрибуты и методы

Свойства видимости объектов оказывают влияние не только на внешнее поведение объектов, но и на взаимодействие между классами, которые наследуют их. Публичные свойства и методы доступны всем наследникам и создаются простым определением их в теле класса без каких-либо особых префиксов. К ним можно обращаться как внутри объекта, так и снаружи.

В следующем примере мы создаем два класса: HTMLElement и DivElement. DivElement является потомком HTMLElement и наследует его атрибуты и методы:

class HTMLElement:
    def __init__(self):
        self.visible = True

    def is_visible(self):
        return self.visible

class DivElement(HTMLElement):
    def is_visible_property_from_parent(self):
        return self.visible

    def is_visible_method_from_parent(self):
        return self.is_visible()

div = DivElement()

# Вызов родительского свойства напрямую
print(div.visible) # => True
# Вызов родительского метода напрямую
print(div.is_visible()) # => True

# Вызов родительского свойства изнутри объекта
print(div.is_visible_property_from_parent()) # => True

# Вызов родительского метода изнутри объекта
print(div.is_visible_method_from_parent()) # => True

Здесь DivElement имеет доступ как к атрибуту visible, так и к методу is_visible(), унаследованным от HTMLElement.

Количество классов в цепочке наследования не влияет на это поведение. Любой подкласс DivElement тоже получит доступ к публичным частям HTMLElement:

class DivElementWithEmptyBody(DivElement):
    pass

div = DivElementWithEmptyBody()

# Вызов родительского свойства напрямую
print(div.visible) # => True
# Вызов родительского метода напрямую
print(div.is_visible()) # => True

# Вызов родительского свойства изнутри объекта
print(div.is_visible_property_from_parent()) # => True
# Вызов родительского метода изнутри объекта
print(div.is_visible_method_from_parent()) # => True

В данном примере мы видим, что DivElementWithEmptyBody наследуется от DivElement и имеет доступ к атрибутам и методам классов DivElement и HTMLElement.

Наследование не влияет на поведение свойств внутри объектов. Значение visible в каждом конкретном объекте связано только с этим объектом:

div1 = DivElementWithEmptyBody()
div2 = DivElement()

print(div1.visible) ## => True
print(div2.visible) ## => True

div1.visible = False
print(div1.visible) ## => False
print(div2.visible) ## => True

Хотя публичные атрибуты и методы имеют свои преимущества, например, доступность из любого места кода, они также могут представлять определенные риски. К ним можно обращаться и изменять их напрямую из любого класса, что может привести к неожиданным последствиям. Поэтому в некоторых случаях предпочтительно использовать приватные атрибуты и методы.

Приватные атрибуты и методы

Свойства и методы с приватным модификатором доступа доступны только внутри того класса, где они были определены. Приватные свойства и методы в Python создаются путем добавления двойного подчеркивания (__) перед именем атрибута или метода.

Наследники не могут получить доступ к приватным свойствам и методам. Подразумевается, что приватные сущности — это нечто персональное для класса — его внутренняя реализация, которую нельзя выставлять наружу. Но это не отменяет возможности взаимодействовать с приватными данными через публичный интерфейс:

class HTMLElement:
    def __init__(self):
        self.__visible = True

    def is_visible(self):
        return self.__visible

class DivElement(HTMLElement):
    pass

div = DivElement()

print(div.is_visible()) # => True
print(div.__visible)
# ...
# AttributeError: 'DivElement' object has no attribute '__visible'

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

Приватный модификатор доступа представляет собой мощный механизм инкапсуляции, который позволяет скрыть детали реализации класса. Это помогает сохранить целостность данных и избежать нежелательных изменений во внутреннем состоянии объекта.

Но что делать, когда нам нужен компромисс между полной открытостью публичных и строгим ограничением приватных атрибутов и методов? Для данной ситуации существуют защищенные атрибуты и методы.

Защищенные атрибуты и методы

Защищенные атрибуты и методы имеют необычное поведение. Это смесь между публичными и приватными.

Они создаются путем добавления одного подчеркивания (_) перед именем атрибута или метода. Также они используются, когда разработчик хочет запретить доступ снаружи объекта, но дать возможность работать с ними внутри объекта класса-наследника или суперкласса.

Посмотрим, как это работает на практике:

class HTMLElement:
    def __init__(self):
        self._visible = True

    def is_visible(self):
        return self._visible

class DivElement(HTMLElement):
    def isVisiblePropertyFromParent(self):
        return self._visible

div = DivElement()

# Доступно внутри через родительский метод
print(div.is_visible()) # => True

# Доступно внутри напрямую
print(div.isVisiblePropertyFromParent()) # => True

# Доступно снаружи, но это нарушает соглашение
print(div._visible)    # => True

Защищенные атрибуты и методы в Python рассматриваются как внутренние части класса, и, хотя они могут быть доступны извне, это считается плохой практикой. Вместо этого следует придерживаться соглашения и не обращаться к ним напрямую.

Name Mangling

В Python, в отличие от некоторых других языков, можно определять в наследниках методы совпадающие именами с приватными методами родителя. Но как тогда избежать перезаписи родительского метода? Для этого в языке незаметно от программиста используется механизм name mangling. К названию приватного метода добавляется также название его класса. Так, приватные методы в родителях на самом деле начинаются с имени родительского класса, а в наследниках - с имени наследника.

class User:
    def __init__(self):
        self.name = "User"

    # зададим приватный метод
    def __private_greet(self):
        return f"Hello, {self.name}!"

    # зададим публичный интерфейс для этого метода
    def greet(self):
        return self.__private_greet()

class John(User):
    # попробуем переопределить приватный метод родителя
    def __private_greet(self):
        return f"Hello, John!"

# метод не изменился
John().greet() # 'Hello, User!'

# посмотрим весь список методов John с помощью функции dir
# здесь мы видим, что к имени приватных методов для защиты от перезаписи добавляется их класс
# так в наследнике есть оригинальный метод и метод наследника
dir(John) # ['_John__private_greet', '_User__private_greet', ..]

Таким образом мы можем "защитить" родительские методы от изменения в наследниках. Но стоит помнить, что в Python по-настоящему все доступно для разработчика. И добраться до оригинального родительского метода всегда можно обойдя name mangling как _ИмяКласса__имя_метода.

class User:
    def __init__(self):
        self.name = "User"

    # зададим приватный метод
    def __private_greet(self):
        return f"Hello, {self.name}!"

    # зададим публичный интерфейс для этого метода
    def greet(self):
        return self.__private_greet()

class John(User):
    # попробуем переопределить приватный метод родителя
    # на этот раз доберемся до оригинального метода обойдя name mangling 
    def _User__private_greet(self):
        return f"Hello, John!"

# метод изменился
John().greet() # Hello, John!

Разумеется, несмотря на то, что мы знаем как добраться до "оригинала" обходить name mangling крайне опасно, ведь если родительский метод был приватным, значит были причины сделать его таким и неизменяемым в наследниках.

Выбор способа

Мы только начали знакомиться с наследованием, но уже сейчас видно, что всё не просто. Одну и ту же задачу можно сделать множеством способов. Какой предпочесть?

Универсальная стратегия, которой стоит придерживаться в большинстве случаев, – всегда работать через абстракцию, пока она не мешает. Это значит, что все свойства, которые не предназначены для прямого доступа, делаются с префиксом подчеркивания (соглашение о защищенности), а наружу выставляется публичный интерфейс (методы).

Выводы

Чтобы правильно использовать наследование в объектно-ориентированном программировании, необходимо понимать публичные, приватные и защищенные атрибуты и методы. Эти модификаторы доступа позволяют управлять уровнем доступа к функциональности и данным класса, обеспечивая инкапсуляцию и полиморфизм. В итоге это способствует созданию гибкого и надежного кода.


Дополнительные материалы

  1. Соглашение между разработчиками

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
Программирование на Python, Разработка веб-приложений и сервисов используя Django, проектирование и реализация REST API
10 месяцев
с нуля
Старт 26 декабря

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»