Изменение имени Python

110

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

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

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

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

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

Пол Манта
источник

Ответы:

182

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

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

Brandizzi
источник
1. Инкапсуляция предназначена для защиты инвариантов классов. Не скрывать ненужные детали от внешнего мира, потому что это будет раздражать. 2. «Дело в том, что ваш API должен быть хорошим, а остальное - детали». Это верно. А общедоступные атрибуты являются частью вашего API. Кроме того, иногда подходят общедоступные сеттеры (в отношении инвариантов вашего класса), а иногда нет. API, у которого есть общедоступные сеттеры, которые не должны быть общедоступными (риск нарушения инвариантов), является плохим API. Это означает, что вы в любом случае должны думать о видимости каждого сеттера, а наличие «по умолчанию» значит меньше.
Юпитер
21

Во-первых, что такое искажение имен?

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

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

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

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

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

Аарон Холл
источник
16

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

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

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

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

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

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

Мэтт Джойнер
источник
9

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

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

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

Уинстон Эверт
источник
6

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

Если вы посмотрите на 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

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

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

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

Мы так живем, и нас это устраивает.

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

JBernardo
источник
2
Вы пропустили причину, по которой двойное подчеркивание не подходит для подкласса ... это улучшит ваш ответ.
Мэтт Джойнер
2
Учитывая, что двойное подчеркивание на самом деле предназначено только для предотвращения конфликтов имен с подклассами (как способ сказать «руки прочь» подклассам), я не понимаю, как искажение имен создает проблему.
Аарон Холл
4

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

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

Tanner_Wauchope
источник
3

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

  • два ведущих символа подчеркивания (__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

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

Нитиш Чаухан
источник
2

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

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

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

Роман Боднарчук
источник
Используйте двойное подчеркивание для «закрытого» и одинарное подчеркивание для «защищенного». Обычно люди просто используют одинарное подчеркивание для всего (двойное подчеркивание помогает обеспечить конфиденциальность, что обычно противоречит стилю Python).
Джонатан Стернберг,
1
Но разве это не делает два подчеркивания похожими на закрытые, а одно подчеркивание - на защищенное? Почему бы просто не начать с «приват»?
Пол Манта
@ Пол Нет, это не так. В Python нет частного, и вам не следует пытаться этого добиться.
Роман Боднарчук
@Roman С концептуальной точки зрения ... Обратите внимание на цитаты вокруг слова "private".
Пол Манта
1

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

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

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

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

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

Ярослав Никитенко
источник
-2

# ПРИМЕР ПРОГРАММЫ ДЛЯ изменения имени Python

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


[n for n in dir(Demo) if 'any' in n]   # GIVES OUTPUT AS ['_Demo__any_name', 
                                       #    '_Demo__any_other_name_']
джай ганеш
источник
1
Это вообще не отвечает на вопрос - это показывает пример, но не затрагивает суть вопроса. На этот и этот вопрос уже почти 9 лет с принятым ответом. Добавляет ли это что-нибудь к уже предоставленным здесь ответам?
Rayryeng