Частные участники в Python

Как я могу сделать методы и члены данных приватными в Python? Или Python не поддерживает частных пользователей?

10 ответов

Решение

9.6. Частные переменные

"Частные" переменные экземпляра, к которым нельзя получить доступ, кроме как внутри объекта, в Python не существуют. Однако существует соглашение, которому следует большая часть кода Python: имя с префиксом подчеркивания (например, _spam) следует рассматривать как непубличную часть API (будь то функция, метод или элемент данных), Это следует считать деталями реализации и могут быть изменены без предварительного уведомления.

Since there is a valid use-case for class-private members (namely to avoid name clashes of names with names defined by subclasses), there is limited support for such a mechanism, called name mangling. Any identifier of the form __spam (at least two leading underscores, at most one trailing underscore) is textually replaced with _classname__spam, where classname is the current class name with leading underscore(s) stripped. Это искажение выполняется без учета синтаксической позиции идентификатора, если оно происходит в определении класса.

So, for example,

class Test:
    def __private_symbol(self):
        pass
    def normal_symbol(self):
        pass

print dir(Test)

будет выводить:

['_Test__private_symbol', 
'__doc__', 
'__module__', 
'normal_symbol']

__private_symbol should be considered a private method, but it would still be accessible through _Test__private_symbol,

Другие ответы содержат технические детали. Я хотел бы подчеркнуть различие в философии между Python, с одной стороны, и такими языками, как C++/Java (который, я полагаю, вы знакомы, основываясь на вашем вопросе).

Общее отношение в Python (и в этом отношении к Perl) заключается в том, что "конфиденциальность" атрибута является запросом к программисту, а не ограждением из колючей проволоки со стороны компилятора / интерпретатора. Идея хорошо изложена в этом письме и часто упоминается как "Мы ​​все взрослые по обоюдному согласию", поскольку она "предполагает", что программист достаточно ответственен, чтобы не вмешиваться во внутренности. Ведущие подчеркивания служат вежливым сообщением о том, что атрибут является внутренним.

С другой стороны, если вы хотите получить доступ к внутренним компонентам для некоторых приложений (ярким примером являются генераторы документации, такие как pydoc), вы можете это сделать. На вас, как на программиста, лежит ответственность знать, что вы делаете, и делать это правильно, а не на языке, который заставляет вас делать то, что вам нужно.

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

  • _single_leading_underscore: слабый индикатор "внутреннего использования". Например from M import * не импортирует объекты, имя которых начинается с подчеркивания.

  • single_trailing_underscore_: используется по соглашению, чтобы избежать конфликтов с ключевым словом Python, например Tkinter.Toplevel(master, class_='ClassName')

  • __double_leading_underscore: при именовании атрибута класса вызывает искажение имени (внутри класса FooBar__boo становится _FooBar__boo; см. ниже).

Если имя функции Python, метода класса или атрибута начинается с (но не заканчивается) двух подчеркиваний, оно является приватным; все остальное публично. Python не имеет понятия о методах защищенных классов (доступных только в их собственных классах и классах-потомках). Методы класса являются приватными (доступны только в своем собственном классе) или общедоступными (доступны из любого места).

Погружение в Python

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

Класс Person(объект):

    def __priva (self):
        печать "Я частный"

    def pub (self):
        печать "Я публичный"

    def callpriva (self):
        самостоятельно.__ Прива ()

Теперь, когда мы выполним:

>>> р = человек ()
>>> p.publ()
 Я публичный
>>> p.__priva()
Traceback (последний вызов был последним):
  Файл "", строка 1, в 
    р.__ Priva ()
AttributeError: у объекта "Person" нет атрибута "__priva"
# Объяснение: Вы видите, что мы не можем получить этот закрытый метод напрямую.
>>> p.callpriva ()
Я частный
# Объяснение: Здесь мы можем получить доступ к приватному методу внутри класса

Тогда как кто-то может получить доступ к этой переменной???
Вы можете сделать как:

>>> p._Person__priva
Я частный

Вау, на самом деле, если python получает любую переменную, начинающуюся с двойного подчеркивания, "переводится" путем добавления одного подчеркивания и имени класса в начало:

Примечание. Если вы не хотите, чтобы это имя изменялось, но вы все еще хотите отправить сигнал об удалении других объектов, вы можете использовать одиночные имена начальных подчеркиваний с начальным подчеркиванием, которые не импортируются с импортированными звездами (из импорта модуля *)
Пример:

# test.py
def hello ():
    печать "привет"
def _hello ():
    печать "Привет приват"

# ----------------------
# test2.py
из теста импорта *
распечатать привет ()
распечатать _hello ()

выход ->

Привет
Traceback (последний вызов был последним):
  Файл "", строка 1, в 
NameError: имя '_hello' не определено

Теперь, если мы будем называть _hello вручную.

# test2.py
из теста импорта _Hello, привет
распечатать привет ()
распечатать _hello ()

выход ->

Привет
привет приват

Наконец, Python на самом деле не имеет эквивалентной поддержки конфиденциальности, хотя одинарные и двойные начальные подчеркивания в некоторой степени дают вам два уровня конфиденциальности

Это может сработать:

import sys, functools

def private(member):
    @functools.wraps(member)
    def wrapper(*function_args):
      myself = member.__name__
      caller = sys._getframe(1).f_code.co_name
      if (not caller in dir(function_args[0]) and not caller is myself):
         raise Exception("%s called by %s is private"%(myself,caller))
      return member(*function_args)
    return wrapper

class test:
   def public_method(self):
      print('public method called')

   @private
   def private_method(self):
      print('private method called')

t = test()
t.public_method()
t.private_method()

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

Простой импорт модуля не обязательно должен предоставлять разработчику приложения доступ ко всем его классам или методам; если я не могу увидеть исходный код модуля, как я узнаю, что доступно? Кто-то (или что-то) должен сказать мне, что я могу сделать, и объяснить, как использовать те функции, которые мне разрешено использовать, иначе все это бесполезно для меня.

Те, кто разрабатывает высокоуровневые абстракции, основанные на фундаментальных классах и методах через импортированные модули, представлены со спецификацией DOCUMENT, а НЕ с фактическим исходным кодом.

В спецификации модуля описаны все функции, которые должны быть видны разработчику клиента. При работе с большими проектами и командами разработчиков программного обеспечения фактическая реализация модуля должна ВСЕГДА оставаться скрытой от тех, кто его использует - это черный ящик с интерфейсом для внешнего мира. Я полагаю, что для пуристов OOD технические термины - это "разъединение" и "согласованность". Пользователь модуля должен знать только методы интерфейса, не обременяя себя деталями реализации.

Модуль НИКОГДА не следует менять без предварительного изменения его базового спецификационного документа, что может потребовать проверки / одобрения в некоторых организациях до изменения кода.

Как программист-хобби (сейчас на пенсии), я запускаю новый модуль со спецификацией doc, фактически записанной в виде гигантского блока комментариев в верхней части модуля, это будет та часть, которую пользователь фактически видит в библиотеке спецификаций. Поскольку это только я, я еще не создал библиотеку, но это было бы достаточно легко сделать.

Затем я начинаю кодирование с написания различных классов и методов, но без функциональных тел - просто нулевых операторов печати, таких как "print()" - достаточно, чтобы модуль мог компилироваться без синтаксических ошибок. Когда этот шаг завершен, я компилирую завершенный нулевой модуль - это моя спецификация. Если бы я работал в команде проекта, я бы представил эту спецификацию / интерфейс для обзора и комментариев, прежде чем приступить к выделению тела.

Я конкретизирую тела каждого метода по одному и компилирую соответственно, гарантируя, что синтаксические ошибки исправляются немедленно на лету. Это также хорошее время, чтобы начать писать временную "основную" секцию выполнения внизу, чтобы протестировать каждый метод при его кодировании. Когда кодирование / тестирование завершено, весь тестовый код закомментирован до тех пор, пока он вам не понадобится, если потребуется обновление.

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

PS: задолго до начала времени я работал в оборонном аэрокосмическом сообществе, и мы сделали несколько довольно крутых вещей, но такие вещи, как проприетарные алгоритмы и логика управления чувствительными системами, были надежно зашифрованы и зашифрованы в супер-защищенных библиотеках программного обеспечения. У нас был доступ к интерфейсам модуля / пакета, но НЕ к телам реализации черного ящика. Был инструмент управления документами, который обрабатывал все проекты системного уровня, спецификации программного обеспечения, исходный код и записи тестов - все это было синхронизировано вместе. Государство предъявляло жесткие требования к программным стандартам обеспечения качества. Кто-нибудь помнит язык под названием "Ада"? Вот сколько мне лет!

ПКП 8

Раздел «Имена методов и переменные экземпляра» https://peps.python.org/pep-0008/#method-names-and-instance-variables

Используйте одно подчеркивание только для закрытых методов и переменных экземпляра.

Чтобы избежать конфликтов имен с подклассами, используйте два начальных подчеркивания для вызова правил изменения имен Python.

Python смешивает эти имена с именем класса: если класс Foo имеет атрибут с именем __a, Foo.__a не может получить к нему доступ. (Настойчивый пользователь все равно может получить доступ, вызвав Foo._Foo__a.) Обычно двойное подчеркивание следует использовать только во избежание конфликтов имен с атрибутами в классах, предназначенных для создания подклассов.

Примечание: существуют некоторые разногласия по поводу использования __names (см. ниже).

Раздел «Проектирование для наследования» https://peps.python.org/pep-0008/#designing-for-inheritance

Всегда решайте, должны ли методы класса и переменные экземпляра (совместно именуемые «атрибуты») быть открытыми или закрытыми. Если сомневаетесь, выберите непубличный; легче сделать его общедоступным позже, чем сделать общедоступный атрибут закрытым.

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

Мы не используем здесь термин «частный», поскольку в Python ни один атрибут не является действительно частным (без лишней работы).

Другая категория атрибутов — это те, которые являются частью «API подкласса» (часто называемого «защищенным» на других языках). Некоторые классы предназначены для наследования с целью расширения или изменения аспектов поведения класса. При разработке такого класса позаботьтесь о том, чтобы четко решить, какие атрибуты являются общедоступными, какие являются частью API подкласса, а какие действительно могут использоваться только вашим базовым классом.

Имея это в виду, вот рекомендации Pythonic:

  • Открытые атрибуты не должны иметь начального подчеркивания.

  • Если имя вашего общедоступного атрибута конфликтует с зарезервированным ключевым словом, добавьте к имени атрибута одно завершающее подчеркивание. Это предпочтительнее, чем сокращение или искаженное написание. (Однако, несмотря на это правило, «cls» является предпочтительным написанием для любой переменной или аргумента, который, как известно, является классом, особенно первого аргумента метода класса.)

    Примечание 1. Для методов класса см. рекомендацию по имени аргумента выше.

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

    Примечание 1. Старайтесь избегать побочных эффектов функционального поведения, хотя побочные эффекты, такие как кэширование, в целом допустимы.

    Примечание 2. Избегайте использования свойств для операций, требующих больших вычислительных затрат; нотация атрибута заставляет вызывающую сторону поверить, что доступ (относительно) дешев.

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

    Примечание 1. Обратите внимание, что в искаженном имени используется только простое имя класса, поэтому, если подкласс выбирает одно и то же имя класса и имя атрибута, вы все равно можете получить конфликты имен.

    Примечание 2. Изменение имен может сделать некоторые виды использования, такие как отладка и getattr(), менее удобными. Однако алгоритм искажения имен хорошо документирован и его легко выполнить вручную.

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

Если вы хотите сделать методы или члены данных приватными в Python, используйте __setattr __

class Number:
    def __init__(self,value):
        self.my_private = value

    def __setattr__(self, my_private, value):
        # the default behavior
        # self.__dict__[my_private] = value
        raise Exception("can't access private member-my_private")


def main():
    n = Number(2)
    print(n.my_private)

if __name__ == '__main__': 
    main()

Я использую Python 2.7 и 3.5. Я написал этот код:

class MyOBject(object):
    def __init__(self):
        self.__private_field = 10


my_object = MyOBject()
print(my_object.__private_field)

запустил его и получил:

AttributeError: у объекта 'MyOBject' нет атрибута '__private_field'

Пожалуйста, смотрите: https://www.tutorialsteacher.com/python/private-and-protected-access-modifiers-in-python

Другие вопросы по тегам