Модификаторы доступа
Если свойство определено как приватное в базовом классе, оно недоступно напрямую ни в одном производном классе, как уже кратко упоминалось в предыдущем разделе. Рассмотрим пример. В классе 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 foundAttributeError: '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
Ниже приведена полезная таблица видимости атрибутов с разными модификаторами доступа:
| Модификатор доступа | Пример | Видим клиенту | Видим производному классу |
|---|---|---|---|
| Public | self.name | да | да |
| Protected | self._name | нет | да |
| Private | self.__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
Текущие баллы можно посмотреть в синем индикаторе в правом нижнем углу страницы.