Подтвердить что ты не робот

Python name mangling

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

То же самое верно для Python? Должен ли я сначала использовать два ведущих подчеркивания во всем, и только сделать их менее скрытыми (только один подчеркивающий), поскольку они мне нужны?

Если соглашение должно использовать только одно подчеркивание, я также хотел бы знать логическое обоснование.

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

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

4b9b3361

Ответ 1

Если вы сомневаетесь, оставьте это "публичным" - я имею в виду, не добавляйте ничего, чтобы скрыть название вашего атрибута. Если у вас есть класс с некоторым внутренним значением, не беспокойтесь об этом. Вместо того, чтобы писать:

class Stack(object):

    def __init__(self):
        self.__storage = [] # Too uptight

    def push(self, value):
        self.__storage.append(value)

напишите это по умолчанию:

class Stack(object):

    def __init__(self):
        self.storage = [] # No mangling

    def push(self, value):
        self.storage.append(value)

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

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

class Stack(object):

    def __init__(self):
        self._storage = [] # This is ok but pythonistas use it to be relaxed about it

    def push(self, value):
        self._storage.append(value)

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

 class Person(object):
     def __init__(self, name, age):
         self.name = name
         self._age = age if age >= 0 else 0

     @property
     def age(self):
         return self._age

     @age.setter
     def age(self, age):
         if age >= 0:
             self._age = age
         else:
             self._age  = 0

А как насчет двойного подчеркивания? Итак, магия двойного подчеркивания используется в основном , чтобы избежать случайной перегрузки методов и конфликтов имен с атрибутами суперклассов. Это может быть очень полезно, если вы напишите класс, который, как ожидается, будет расширен многократно.

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

РЕДАКТИРОВАТЬ: Почему это так? Что ж, обычный стиль Python не подчеркивает необходимость делать вещи приватными - наоборот! Для этого есть много причин - большинство из них противоречивы... Давайте посмотрим на некоторые из них.

Python обладает свойствами

Сегодня большинство ОО-языков используют противоположный подход: то, что не должно использоваться, не должно быть видимым, поэтому атрибуты должны быть закрытыми. Теоретически это привело бы к более управляемым, менее связанным классам, потому что никто не мог бы безрассудно изменять значения внутри объектов.

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

Так зачем же следовать этой политике по умолчанию? Просто сделайте ваши атрибуты общедоступными по умолчанию. Конечно, это проблематично в Java, потому что если вы решите добавить некоторую проверку в ваш атрибут, вам потребуется изменить все

person.age = age;

в вашем коде, скажем,

person.setAge(age);

setAge() существо:

public void setAge(int age) {
    if (age >= 0) {
        this.age = age;
    } else {
        this.age = 0;
    }
}

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

Однако вам не нужно делать это в Python, поскольку у Python есть свойства. Если у вас есть этот класс:

 class Person(object):
     def __init__(self, name, age):
         self.name = name
         self.age = age

и затем вы решаете подтвердить возраст, вам не нужно изменять части кода person.age = age. Просто добавьте свойство (как показано ниже)

 class Person(object):
     def __init__(self, name, age):
         self.name = name
         self._age = age if age >= 0 else 0

     @property
     def age(self):
         return self._age

     @age.setter
     def age(self, age):
         if age >= 0:
             self._age = age
         else:
             self._age  = 0

Если вы можете сделать это и по-прежнему использовать person.age = age, зачем добавлять личные поля, методы получения и установки?

(Также см. Python не Java и эту статью о вреде использования геттеров и сеттеров.).

В любом случае все видно - и попытка скрыть только усложняет вашу работу

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

Поскольку Python является очень динамичным языком, добавление этого бремени к вашим классам просто контрпродуктивно.

Проблему невозможно увидеть - она нужна, чтобы увидеть

Для Pythonista инкапсуляция - это не неспособность видеть внутренности классов, а возможность избежать их просмотра. Я имею в виду, что инкапсуляция - это свойство компонента, которое позволяет использовать его, не заботясь о внутренних деталях пользователя. Если вы можете использовать компонент, не заботясь о его реализации, то он инкапсулирован (по мнению программиста на Python).

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

Гвидо так сказал

Ну, это не спорный: он сказал так, на самом деле. (Ищите "открытое кимоно".)

Это культура

Да, есть несколько причин, но нет критических. Это в основном культурный аспект программирования на Python. Честно говоря, это может быть и другой путь, но это не так. Кроме того, вы могли бы так же легко задать другой вопрос: почему некоторые языки по умолчанию используют закрытые атрибуты? По той же основной причине, что и для практики Python: потому что это культура этих языков, и каждый выбор имеет свои преимущества и недостатки.

Поскольку эта культура уже существует, вам рекомендуется следовать ей. В противном случае вы будете раздражены программистами Python, которые скажут вам удалить __ из вашего кода, когда вы задаете вопрос в Qaru :)

Ответ 2

Я бы не сказал, что практика дает лучший код. Модификаторы видимости только отвлекают вас от поставленной задачи и в качестве побочного эффекта заставляют использовать ваш интерфейс по своему усмотрению. Вообще говоря, усиление видимости не позволяет программистам запутывать вещи, если они не правильно прочитали документацию.

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

Моя стратегия в Python такова:

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

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

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

Ответ 3

Во-первых - Что называется mangling?

Манипуляция имени вызывается, когда вы находитесь в определении класса и используете __any_name или __any_name_, то есть два (или более) ведущих символа подчеркивания и не более одного нижнего подчеркивания.

class Demo:
    __any_name = "__any_name"
    __any_other_name_ = "__any_other_name_"

И сейчас:

>>> [n for n in dir(Demo) if 'any' in n]
['_Demo__any_name', '_Demo__any_other_name_']
>>> Demo._Demo__any_name
'__any_name'
>>> Demo._Demo__any_other_name_
'__any_other_name_'

Когда вы сомневаетесь, что?

Явное использование заключается в том, чтобы запретить подклассам использовать атрибут, который использует класс.

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

Недостатком является то, что он увеличивает когнитивную нагрузку для чтения и понимания базы кода, и особенно при отладке, когда вы видите двойное подчеркивание в источнике и искаженное имя в отладчике.

Мой личный подход заключается в намерении избежать этого. Я работаю над очень большой базой кода. Редкие его применения торчат как больной палец и не кажутся оправданными.

Вам нужно знать об этом, чтобы вы знали это, когда видите это.

PEP 8

PEP 8, руководство по стилю стандартной библиотеки Python, в настоящее время говорит (сокращенно):

Существует несколько споров об использовании __names.

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

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

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

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

Как это работает?

Если вы добавите два символа подчеркивания (без окончания двойных подчеркиваний) в определении класса, имя будет искажено, а символ подчеркивания, за которым следует имя класса, будет добавлен к объекту:

>>> class Foo(object):
...     __foobar = None
...     _foobaz = None
...     __fooquux__ = None
... 
>>> [name for name in dir(Foo) if 'foo' in name]
['_Foo__foobar', '__fooquux__', '_foobaz']

Обратите внимание, что имена обрабатываются только при анализе определения класса:

>>> Foo.__test = None
>>> Foo.__test
>>> Foo._Foo__test
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'Foo' has no attribute '_Foo__test'

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

Один подчёркивающий?

Если в соглашении будет использоваться только одно подчеркивание, я также хотел бы узнать обоснование.

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

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

Ответ 4

Вам не следует начинать с частных данных и публиковать их по мере необходимости. Скорее, вы должны начать с выяснения интерфейса вашего объекта. То есть вы должны начать с выяснения того, что мир видит (общественные вещи), а затем выяснить, какие частные вещи необходимы для этого.

Другой язык делает сложным сделать частным то, что когда-то было публичным. То есть Я переломаю много кода, если я сделаю свою переменную частной или защищенной. Но со свойствами в python это не так. Скорее, я могу поддерживать один и тот же интерфейс даже при перестановке внутренних данных.

Разница между _ и __ заключается в том, что python фактически пытается применить последнее. Конечно, это не очень сложно, но это мешает. Имея _ просто говорит другим программистам, что такое намерение, они могут игнорировать их опасность. Но игнорирование этого правила иногда полезно. Примеры включают отладку, временные хаки и работу с сторонним кодом, который не предназначен для использования так, как вы его используете.

Ответ 5

На это уже много хороших ответов, но я собираюсь предложить еще один. Это также частично ответ на людей, которые продолжают говорить, что двойной подчеркивание не является частным (это действительно так).

Если вы посмотрите на Java/С#, у них обоих есть private/protected/public. Все это конструкции времени компиляции. Они применяются только во время компиляции. Если вы хотите использовать отражение в Java/С#, вы можете легко получить доступ к закрытому методу.

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

lst = []
lst.append(1)
getattr(lst, 'append')(1)

Синтаксис "точка" - это только синтаксический сахар для последней части кода. В основном потому, что использование getattr уже является уродливым только с одним вызовом функции. Это только ухудшается оттуда.

Таким образом, не может быть Java/С# версии private, поскольку Python не компилирует код. Java и С# не могут проверить, является ли функция закрытой или общедоступной во время выполнения, поскольку эта информация отсутствует (и она не знает, откуда вызывается функция).

Теперь с этой информацией имя, определяющее двойное подчеркивание, имеет наибольший смысл для достижения "частной". Теперь, когда функция вызывается из экземпляра "self", и она замечает, что она начинается с "__", она просто выполняет имя, управляющее прямо там. Это просто более синтаксический сахар. Этот синтаксический сахар позволяет эквивалент 'private' на языке, который использует отражение только для доступа к члену данных.

Отказ от ответственности: я никогда не слышал, чтобы кто-либо из разработчиков Python говорил что-то вроде этого. Настоящая причина отсутствия "private" является культурной, но вы также заметите, что большинство сценариев/интерпретируемых языков не имеют личных. Строго принудительное закрытие не является практичным ничем, кроме времени компиляции.

Ответ 6

Во-первых: Почему вы хотите скрыть свои данные? Почему это так важно?

В большинстве случаев вы действительно не хотите этого делать, но вы делаете, потому что другие делают.

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

То, как мы живем, и мы в порядке с этим.

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

Ответ 7

В выбранном ответе хорошо объясняется, как свойства устраняют необходимость в частных атрибутах, но я бы также добавил, что функции на уровне модуля устраняют необходимость в частных методах.

Если вы превратите метод в функцию на уровне модуля, вы удалите возможность переопределения подклассов. Перемещение некоторой функциональности на уровень модуля более Pythonic, чем попытка скрыть методы с помощью mangling.

Ответ 8

Следующий фрагмент кода объяснит все разные случаи:

  • два ведущих символа подчеркивания (__a)
  • одиночное подчеркивание (_a)
  • нет подчеркивания (a)

    class Test:
    
    def __init__(self):
        self.__a = 'test1'
        self._a = 'test2'
        self.a = 'test3'
    
    def change_value(self,value):
        self.__a = value
        return self.__a
    

печать всех действительных атрибутов тестового объекта

testObj1 = Test()
valid_attributes = dir(testObj1)
print valid_attributes

['_Test__a', '__doc__', '__init__', '__module__', '_a', 'a', 
'change_value']

Здесь вы можете увидеть, что имя __a было изменено на _Test__a, чтобы эта переменная не была переопределена каким-либо подклассом. Эта концепция называется "Name Mangling" в python. Вы можете получить доступ к этому следующим образом:

testObj2 = Test()
print testObj2._Test__a

test1

Аналогично, в случае _a переменная должна только уведомить разработчика о том, что он должен использоваться как внутренняя переменная этого класса, интерпретатор python ничего не сделает, даже если вы его получите, но это не очень хорошая практика.

testObj3 = Test()
print testObj3._a

test2

переменная может быть доступна из любой точки мира, как переменная открытого класса.

testObj4 = Test()
print testObj4.a

test3

Надеюсь, ответ вам помог :)

Ответ 9

На первый взгляд он должен быть таким же, как для других языков (под "другим" я подразумеваю Java или С++), но это не так.

В Java вы сделали закрытыми все переменные, которые не должны быть доступны снаружи. В то же время в Python вы не можете этого добиться, поскольку нет "приватности" (как говорит один из принципов Python - "Мы все взрослые" ). Таким образом, двойной подчеркивание означает только "Ребята, не используйте это поле напрямую". Тот же смысл имеет одно подчеркивание, которое в то же время не вызывает головной боли, когда вам нужно наследовать от рассматриваемого класса (просто пример возможной проблемы, вызванной двойным подчеркиванием).

Итак, я рекомендую вам использовать одно подчеркивание по умолчанию для "private" членов.

Ответ 10

"Если вы сомневаетесь в том, должна ли переменная быть закрытой или защищенной, лучше перейти с частной". - да, то же самое в Python.

Некоторые ответы здесь говорят о "соглашениях", но не дают ссылок на эти соглашения. В авторитетном руководстве по Python PEP 8 прямо говорится:

В случае сомнений, выберите не публичный; проще сделать это позже, чем сделать публичный атрибут закрытым.

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

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

Ответ 11

Все замечательные комментарии. Ну, если вам все еще трудно понять, как обращаться с именами, перейдите по ссылке https://dbader.org/blog/meaning-of-underscores-in-python

Это было объяснено очень просто.

Благодарю.