Искажение имени Python - PullRequest
       7

Искажение имени Python

81 голосов
/ 17 сентября 2011

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

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

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

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

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

Ответы [ 9 ]

158 голосов
/ 17 сентября 2011

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

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 презирают это значение по умолчанию, но в любом случае это значение по умолчанию, поэтому я очень рекомендую вам следовать ему, даже если вы чувствуете себя неловко.1010 * хочу отправить сообщение "Не могу потрогать это!"для ваших пользователей обычным способом является добавление к переменной one подчеркивания.Это просто соглашение, но люди понимают его и проявляют двойную осторожность при работе с такими вещами:

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, которые скажут вам удалить __ из вашего кода, когда вы зададите вопрос в переполнении стека:)

14 голосов
/ 17 сентября 2011

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

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

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

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

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

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

13 голосов
/ 20 января 2016

Первый - Что такое искажение имени?

Манглинг имен вызывается, когда вы находитесь в определении класса и используете __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 не может быть заменен Лисковым, и ни один пример не приходит на ум, где я нашел это полезным.

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

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

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

PEP 8

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

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

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

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

  2. Измена имен может сделать определенные применения, такие как отладка и __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 иногда трудно понять, что происходит, когда они не могут вручную получить доступ к имени, которое они видят определенным в определении класса. Это не является веской причиной против этого, но стоит подумать, если у вас есть обучающая аудитория.

Один знак подчеркивания?

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

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

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

8 голосов
/ 17 сентября 2011

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

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

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

5 голосов
/ 18 сентября 2011

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

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

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

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

Синтаксис "точка" является только синтаксическим сахаром для последнего фрагмента кода. Главным образом потому, что использование getattr уже безобразно только с одним вызовом функции. От этого становится только хуже.

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

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

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

4 голосов
/ 17 сентября 2011

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

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

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

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

3 голосов
/ 15 апреля 2018

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

  • два ведущих подчеркивания (__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, чтобы предотвратить переопределение этой переменной любым из подклассов. Эта концепция в Python известна как «искажение имен». Вы можете получить доступ к этому, как это:

testObj2 = Test()
print testObj2._Test__a

test1

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

testObj3 = Test()
print testObj3._a

test2

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

testObj4 = Test()
print testObj4.a

test3

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

3 голосов
/ 21 сентября 2015

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

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

2 голосов
/ 17 сентября 2011

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

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

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

...