Part 10

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

Если свойство определено как приватное в базовом классе, оно недоступно напрямую ни в одном производном классе, как уже кратко упоминалось в предыдущем разделе. Рассмотрим пример. В классе Notebook ниже заметки хранятся в списке, и атрибут списка является приватным:


class Notebook:
    """ A Notebook stores notes in string format """

    def __init__(self):
        # private attribute
        self.__notes = []

    def add_note(self, note):
        self.__notes.append(note)

    def retrieve_note(self, index):
        return self.__notes[index]

    def all_notes(self):
        return ",".join(self.__notes)

Если целостность класса важна, имеет смысл сделать атрибут списка notes приватным. В конце концов, класс предоставляет клиентскому коду подходящие методы для добавления и просмотра заметок. Этот подход становится проблематичным, если мы определяем новый класс NotebookPro, который наследует класс Notebook. Приватный атрибут списка недоступен клиентскому коду, но он также недоступен и производным классам. Если попытаться обратиться к нему, как в методе find_notes ниже, мы получим ошибку:

class NotebookPro(Notebook):
    """ A better Notebook with search functionality """
    def __init__(self):
        # This is OK, the constructor is public despite the underscores
        super().__init__()

    # This causes an error
    def find_notes(self, search_term):
        found = []
        # the attribute __notes is private
        # the derived class can't access it directly
        for note in self.__notes:
            if search_term in note:
                found.append(note)

        return found
Пример вывода

AttributeError: 'NotebookPro' object has no attribute '_NotebookPro__notes'

Защищенные свойства

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

Напомним, свойство можно скрыть, добавив к его имени два подчеркивания в начале:

def __init__(self):
    self.__notes = []

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

def __init__(self):
    self._notes = []

Ниже приведен весь пример Notebook, но с защищенным _notes вместо приватного __notes:

class Notebook:
    """ A Notebook stores notes in string format """

    def __init__(self):
        # protected attribute
        self._notes = []

    def add_note(self, note):
        self._notes.append(note)

    def retrieve_note(self, index):
        return self._notes[index]

    def all_notes(self):
        return ",".join(self._notes)

class NotebookPro(Notebook):
    """ A better Notebook with search functionality """
    def __init__(self):
        # This is OK, the constructor is public despite the underscores
        super().__init__()

    # This works, the protected attribute is accessible to the derived class
    def find_notes(self, search_term):
        found = []
        for note in self._notes:
            if search_term in note:
                found.append(note)

        return found

Ниже приведена полезная таблица видимости атрибутов с разными модификаторами доступа:

Модификатор доступаПримерВидим клиентуВидим производному классу
Publicself.nameдада
Protectedself._nameнетда
Privateself.__nameнетнет

Модификаторы доступа одинаково работают со всеми свойствами. Например, в классе Person ниже есть защищенный метод capitalize_initials. Его можно использовать из производного класса Footballer:

class Person:
    def __init__(self, name: str):
        self._name = self._capitalize_initials(name)

    def _capitalize_initials(self, name):
        name_capitalized = []
        for n in name.split(" "):
            name_capitalized.append(n.capitalize())

        return " ".join(name_capitalized)

    def __repr__(self):
        return self.__name

class Footballer(Person):

    def __init__(self, name: str, nickname: str, position: str):
        super().__init__(name)
        # the method is available as it is protected in the base class
        self.__nickname = self._capitalize_initials(nickname)
        self.__position = position

    def __repr__(self):
        r =  f"Footballer - name: {self._name}, nickname: {self.__nickname}"
        r += f", position: {self.__position}"
        return r

# Test the classes
if __name__ == "__main__":
    jp = Footballer("peter pythons", "pyper", "forward")
    print(jp)
Пример вывода

Footballer - name: Peter Pythons, nickname: Pyper, position: forward

Loading
Loading
Вы дошли до конца этого раздела! Перейти к следующему разделу:

Текущие баллы можно посмотреть в синем индикаторе в правом нижнем углу страницы.