Почему методы 'private' в Python на самом деле не являются приватными?
Описание проблемы:
В Python существует возможность создания "приватных" методов и переменных внутри класса, добавляя два подчеркивания перед именем, например: __myPrivateMethod()
. Но как объяснить следующую ситуацию?
class MyClass:
def myPublicMethod(self):
print 'public method'
def __myPrivateMethod(self):
print 'this is private!!'
obj = MyClass()
obj.myPublicMethod() # Вывод: public method
obj.__myPrivateMethod() # Ошибка
При попытке вызвать приватный метод возникает ошибка:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: MyClass instance has no attribute '__myPrivateMethod'
Однако если я выполню:
dir(obj)
То в списке атрибутов объекта я увижу:
['_MyClass__myPrivateMethod', '__doc__', '__module__', 'myPublicMethod']
Очевидно, что Python создает новое имя для приватных методов, добавляя к имени класса префикс. Таким образом, я могу получить доступ к приватному методу следующим образом:
obj._MyClass__myPrivateMethod() # Вывод: this is private!!
Но как же так? О каком принципе инкапсуляции можно говорить, если я все равно могу получить доступ к приватному методу? Я всегда слышал, что Python не поддерживает истинную инкапсуляцию, так зачем вообще пытаться её реализовать? В чем дело?
5 ответ(ов)
Имя "скрамблинг" (перемешивание) используется для того, чтобы гарантировать, что подклассы случайно не переопределят приватные методы и атрибуты своих суперклассов. Это не предназначено для предотвращения преднамеренного доступа из внешних источников.
Вот пример:
>>> class Foo(object):
... def __init__(self):
... self.__baz = 42
... def foo(self):
... print(self.__baz)
...
>>> class Bar(Foo):
... def __init__(self):
... super(Bar, self).__init__()
... self.__baz = 21
... def bar(self):
... print(self.__baz)
...
>>> x = Bar()
>>> x.foo()
42
>>> x.bar()
21
>>> print(x.__dict__)
{'_Bar__baz': 21, '_Foo__baz': 42}
Как видно из примера, при обращении к методу foo()
из экземпляра Bar
, мы получаем значение 42
, которое хранится в суперклассе Foo
. В то время как метод bar()
печатает 21
, которое присвоено в классе Bar
.
Однако стоит помнить, что эта механика может не сработать, если два разных класса имеют одинаковые имена приватных атрибутов — в таком случае возникнет конфликт.
Когда я впервые перешел с Java на Python, мне это очень не нравилось. Это напугало меня до смерти.
Сегодня это, возможно, одно из вещей, которое я люблю больше всего в Python.
Мне нравится находиться на платформе, где люди доверяют друг другу и не чувствуют необходимости строить вокруг своего кода непроницаемые стены. В языках с сильной инкапсуляцией, если в API есть ошибка, и вы поняли, что именно пошло не так, вы можете все равно не иметь возможности обойти ее, потому что нужный метод является приватным. В Python же настройка такая: "ну конечно". Если вы думаете, что поняли ситуацию, возможно, даже ознакомились с ней, то от нас можно услышать лишь "удачи!".
Помните, инкапсуляция даже слабосвязана с "безопасностью" или тем, чтобы удерживать детей подальше от газона. Это просто еще один шаблон, который следует использовать, чтобы сделать код более понятным.
Пример приватной функции
import re
import inspect
class MyClass:
def __init__(self):
pass
def private_function(self):
try:
function_call = inspect.stack()[1][4][0].strip()
# Проверяем, начинается ли вызов функции с "self."
matched = re.match('^self\.', function_call)
if not matched:
print('Это приватная функция. Уйди.')
return
except:
print('Это приватная функция. Уйди.')
return
# Это настоящая функция, доступная только внутри класса #
print('Привет, добро пожаловать в функцию.')
def public_function(self):
# Я могу вызвать приватную функцию изнутри класса
self.private_function()
### Конец ###
Этот код демонстрирует создание приватной функции в классе на Python. Приватная функция проверяет, была ли она вызвана изнутри класса, и если нет, выводит сообщение о том, что доступ запрещен.
Не то чтобы в любой языке вы абсолютно не могли обойти приватность членов класса (например, через арифметику указателей в C++ или рефлексию в .NET/Java).
Суть в том, что если вы случайно попытаетесь вызвать приватный метод, вы получите ошибку. Но если вы хотите наступить на грабли, никто вас не остановит.
Вы же не пытаетесь защитить свои данные исключительно за счет объектно-ориентированной инкапсуляции, верно?
Важно отметить, что любые идентификаторы в виде __name
(как минимум два ведущих подчеркивания, не более одного завершающего) будут публично заменены на _classname__name
, где classname
— это название текущего класса с удаленными ведущими подчеркиваниями.
Таким образом, __name
недоступен напрямую, но к нему можно получить доступ как к _classname__name
.
Это не означает, что вы можете надежно скрыть свои приватные данные, так как к ним можно легко получить доступ, просто изменив имя переменной.
Источник:
Раздел "Приватные переменные" в официальной документации: https://docs.python.org/3/tutorial/classes.html#tut-private
Пример
class Cat:
def __init__(self, name='unnamed'):
self.name = name
def __print_my_name(self):
print(self.name)
tom = Cat()
tom.__print_my_name() # Ошибка
tom._Cat__print_my_name() # Выведет имя
Как клонировать список, чтобы он не изменялся неожиданно после присваивания?
Преобразование списка словарей в DataFrame pandas
Ошибка: "'dict' объект не имеет метода 'iteritems'"
Как явно освободить память в Python?
Выбор строки из pandas Series/DataFrame по целочисленному индексу