Есть ли в Python закрытые методы, как и в других языках программирования? В этом уроке по Python мы ответим на этот вопрос.
В Python вы можете определить закрытый метод, добавив к имени метода один символ подчеркивания. В отличие от других языков программирования, объявление метода закрытым в Python не мешает вам получать к нему доступ извне вашего класса. Это просто соглашение, чтобы сообщить другим разработчикам, что метод предназначен «только для внутреннего использования» для вашего класса.
Мы рассмотрим несколько примеров, которые покажут вам, как это работает…
…и вы также увидите, почему приватные методы в Python не являются по-настоящему приватными!
Давайте начнем этот объектно-ориентированный урок!
Что такое закрытый метод в Python?
Частный метод — это метод, который должен вызываться только внутри класса Python, где он определен. Чтобы указать частный метод Python, добавьте к его имени префикс в виде одного подчеркивания.
Я говорю, что его «следует» вызывать только внутри класса, в котором он определен, поскольку это не то, что требуется интерпретатором Python.
Давайте посмотрим, как можно определить закрытый метод в Python и чем он отличается от открытого метода.
Начнем с определения класса, имеющего один открытый метод:
class Person:
def __init__(self, name):
self.name = name
def run(self):
print("{} is running".format(self.name))
Когда мы создаем экземпляр и выполняем метод run(), мы получаем ожидаемое сообщение:
jack = Person("Jack")
jack.run()
[output]
Jack is running
Открытый метод, определенный в классе Python, может быть вызван для экземпляра этого класса.
Теперь предположим, что мы хотим добавить метод warmup(), который метод run вызывает внутри. В то же время метод warmup() не должен вызываться за пределами класса.
Возможно ли это?
Теоретически этого можно добиться, добавив одно подчеркивание перед именем метода:
def run(self):
self._warmup()
print("{} is running".format(self.name))
def _warmup(self):
print("{} is warming up".format(self.name))
Как видите, мы определили метод _warmup()
, а затем вызываем закрытый метод внутри метода run().
Ничего не меняется в способе вызова run()
для экземпляра, который мы создали ранее:
jack = Person("Jack")
jack.run()
[output]
Jack is warming up
Jack is running
Теперь давайте посмотрим, что произойдет, если мы попытаемся вызвать метод _warmup()
непосредственно для экземпляра Person.
jack._warmup()
[output]
Jack is warming up
Метод работает!
Почему можно вызвать закрытый метод Python вне класса?
Подождите минутку, почему последний пример кода Python работает, если мы сказали, что _warmup
— это закрытый метод?!?
Это потому что…
Использование одинарного подчеркивания для указания имени закрытого метода Python в классе — это всего лишь соглашение об именовании между разработчиками, и оно не соблюдается интерпретатором Python.
Другими словами, добавляя к имени метода Python один символ подчеркивания, вы сообщаете другим разработчикам (и себе в случае будущих изменений кода), что метод следует вызывать только внутри класса.
Если вы не следуете этому принципу, вы делаете это на свой страх и риск.
В будущем разработчик класса сможет принять решение об изменении закрытого метода, не беспокоясь об обратной совместимости, учитывая, что закрытый метод не должен вызываться за пределами класса.
Частный метод, определенный в классе Python, не должен вызываться в экземпляре этого класса. Он должен вызываться только внутри самого класса.
Что означает двойное подчеркивание в начале имени метода в Python?
В Python также можно добавлять к имени метода префикс в виде двойного подчеркивания вместо одинарного.
Что именно это означает?
Обновите метод _warmup() из предыдущего примера и добавьте еще одно подчеркивание в начале имени метода: __warmup()
.
def run(self):
self.__warmup()
print("{} is running".format(self.name))
def __warmup(self):
print("{} is warming up".format(self.name))
Метод run()
ведет себя таким же образом при вызове экземпляра:
jack = Person("Jack")
jack.run()
[output]
Jack is warming up
Jack is running
А что произойдет, если мы вызовем метод __warmup()
для экземпляра Person?
jack.__warmup()
[output]
Traceback (most recent call last):
File "private.py", line 45, in <module>
jack.__warmup()
AttributeError: 'Person' object has no attribute '__warmup'
Интерпретатор Python выдает исключение и сообщает нам, что у этого объекта Person нет атрибута __warmup
.
Это сообщение об ошибке может ввести в заблуждение, учитывая, что этот метод присутствует в классе, но интерпретатор Python «скрывает» его с помощью так называемого искажения имен.
Целью искажения имен является избежание конфликтов с именами методов при наследовании класса.
В следующем разделе мы рассмотрим, что именно происходит при искажении имен.
Что такое искажение имен в Python?
В предыдущем разделе мы увидели, что происходит, если добавить к именам методов два символа подчеркивания.
Но…
…интерпретатор Python полностью скрывает эти методы?
Чтобы ответить на этот вопрос, мы воспользуемся функцией dir(), чтобы увидеть атрибуты и методы, доступные в нашем экземпляре Person.
print(dir(jack))
[output]
['_Person__warmup', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'run']
Интересно, что в списке доступных методов мы не видим __warmup
, но видим _Person__warmup
.
Может быть, это наш метод двойного подчеркивания?
Давайте попробуем вызвать его на экземпляре:
jack = Person("Jack")
jack._Person__warmup()
[output]
Jack is warming up
Это сработало!
Таким образом, похоже, что наш метод с искаженным именем не полностью скрыт, учитывая, что мы можем получить к нему доступ, добавив подчеркивание и имя класса перед именем метода.
_{class-name}__{name-mangled-method}
В Python вы можете получить доступ к методу, имя которого начинается с двойных подчеркиваний (и не заканчивается подчеркиваниями) из экземпляра класса. Вы можете сделать это, добавив подчеркивание и имя класса перед именем метода. Это называется искажением имени.
Изменение имен и наследование в Python
Давайте узнаем больше об искажении имен применительно к наследованию классов в Python.
Определите класс Runner, который наследует базовый класс Person.
class Runner(Person):
def __init__(self, name, fitness_level):
super().__init__(name)
self.fitness_level = fitness_level
Затем выполните публичный метод run() родительского класса на экземпляре Runner.
kate = Runner("Kate", "high")
kate.run()
[output]
Kate is warming up
Kate is running
Это работает нормально, дочерний класс унаследовал публичный метод run().
А что произойдет, если мы попробуем назвать метод mangled?
kate.__warmup()
[output]
Traceback (most recent call last):
File "private.py", line 19, in <module>
kate.__warmup()
AttributeError: 'Runner' object has no attribute '__warmup'
Мы получаем ожидаемую ошибку из-за искажения имени.
Обратите внимание, что мы по-прежнему можем вызывать его, используя имя родительского класса, как мы видели в предыдущем разделе:
kate._Person__warmup()
[output]
Kate is warming up
Ниже вы можете увидеть вывод функции dir()
для дочернего класса.
['_Person__warmup', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'fitness_level', 'name', 'run']
Определение метода с искаженным именем в дочернем классе
Что произойдет, если мы определим искаженный метод с тем же именем в нашем дочернем классе?
Давайте узнаем!
Переопределите публичный метод run() и «скрытый» метод __warmup() в классе Runner.
class Runner(Person):
def __init__(self, name, fitness_level):
super().__init__(name)
self.fitness_level = fitness_level
def run(self):
self.__warmup()
print("{} has started a race".format(self.name))
def __warmup(self):
print("{} is warming up before a race".format(self.name))
Пришло время пробежаться!
kate = Runner("Kate", "high")
kate.run()
[output]
Kate is warming up before a race
Kate has started a race
Итак, выполняются оба метода в дочернем классе.
Мне интересно, как интерпретатор Python представляет новый метод с искаженным именем в дочернем объекте, учитывая, что для родительского объекта он использовал подчеркивание, за которым следовало имя класса.
print(dir(kate))
[output]
['_Person__warmup', '_Runner__warmup', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'fitness_level', 'name', 'run']
Вы можете видеть, что дочерний объект теперь имеет два искаженных метода:
- _Person_warmup
- _Runner__warmup
Это показывает, как искажение имен предотвращает конфликты с именами методов при наследовании класса.
Существуют ли в Python приватные атрибуты?
Можем ли мы, имея класс Python, определить частные атрибуты аналогично тому, как мы это делали с частными методами?
Давайте возьмем класс Person и добавим к нему закрытый атрибут, например, адрес электронной почты.
Чтобы указать, что атрибут класса является закрытым, добавьте к его имени префикс в виде одного подчеркивания. Это соглашение об именовании, сообщающее разработчикам, что атрибут является закрытым, но интерпретатор Python не обеспечивает соблюдения этого соглашения.
class Person:
def __init__(self, name, email):
self.name = name
self._email = email
Теперь создайте экземпляр Person и попробуйте получить доступ к обоим атрибутам.
>>> mark = Person("Mark", "[email protected]")
>>> print(mark.name)
Mark
>>> print(mark._email)
[email protected]
Из экземпляра вы можете получить доступ к публичным и приватным атрибутам.
Это показывает, что, как и в случае с закрытыми методами, закрытые атрибуты также доступны извне класса Python.
Снова…
…одинарное подчеркивание — это просто соглашение об именовании, которое сообщает разработчикам, что не следует напрямую обращаться к этим конкретным атрибутам класса или изменять их.
Для доступа к закрытым атрибутам и их изменения реализуйте публичные методы класса, которые это делают. Затем вы можете вызывать эти публичные методы из экземпляров вашего класса.
Весь смысл закрытых атрибутов и методов в том, что их не следует использовать напрямую.
Искажение имени, примененное к атрибутам класса
В этом последнем разделе мы увидим, как работает искажение имен в атрибутах класса.
Добавьте к имени атрибута _email
еще одно подчеркивание. Атрибут станет __email
.
class Person:
def __init__(self, name, email):
self.name = name
self.__email = email
Затем создайте экземпляр Person и попробуйте получить доступ к обоим атрибутам.
>>> mark = Person("Mark", "[email protected]")
>>> print(mark.name)
Mark
>>> print(mark.__email)
Traceback (most recent call last):
File "private.py", line 30, in <module>
print(mark.__email)
AttributeError: 'Person' object has no attribute '__email'
Точно так же, как мы уже видели ранее для методов с искаженным именем, мы не можем напрямую получить доступ к атрибуту с искаженным именем.
Но мы можем получить к нему доступ, используя следующий синтаксис:
_{class-name}__{attribute-name}
Давайте проверим…
print(mark._Person__email)
[output]
[email protected]
Заключение
В этом уроке мы увидели, что для создания закрытого метода или закрытого атрибута Python в классе необходимо добавить к имени метода или атрибута один символ подчеркивания.
Интерпретатор Python не применяет это соглашение при работе с классами, это просто соглашение об именовании.
Использование закрытых методов или закрытых атрибутов позволяет вам показать другим разработчикам, какие методы или атрибуты предназначены «только для внутреннего использования» в классе Python.
Если вы хотите узнать больше об этой конвенции, вы можете ознакомиться с PEP 8.