Что означает одиночное и двойное подчеркивание перед именем объекта?

1293

Может ли кто-нибудь объяснить точное значение наличия начальных подчеркиваний перед именем объекта в Python и разницу между ними?

Кроме того, остается ли это значение одинаковым, независимо от того, является ли рассматриваемый объект переменной, функцией, методом и т. Д.?

ivanleoncz
источник
2
Отличный короткий ответ из другой
Антон Тарасенко

Ответы:

1157

Единственный знак подчеркивания

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

Цитировать ПКП-8 :

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

Двойное подчеркивание (название искажения)

Из документов Python :

Любой идентификатор формы __spam(по крайней мере, два ведущих подчеркивания, максимум одно конечное подчеркивание) заменяется в текстовом виде _classname__spam, где classnameтекущее имя класса с лидирующими символами подчеркивания. Это искажение выполняется без учета синтаксической позиции идентификатора, поэтому его можно использовать для определения переменных экземпляра класса и класса, методов, переменных, хранящихся в глобальных переменных, и даже переменных, хранящихся в экземплярах. Приватный к этому классу на экземплярах других классов.

И предупреждение с той же страницы:

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

пример

>>> class MyClass():
...     def __init__(self):
...             self.__superprivate = "Hello"
...             self._semiprivate = ", world!"
...
>>> mc = MyClass()
>>> print mc.__superprivate
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: myClass instance has no attribute '__superprivate'
>>> print mc._semiprivate
, world!
>>> print mc.__dict__
{'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}
Эндрю Китон
источник
17
Что если есть имя переменной, объявленное с двумя подчеркиваниями, которого нет в классе? Это просто нормальная переменная, верно?
Дхрув Рамани
5
что означает просто __двойное подчеркивание как имя переменной? какa, __ = foo()
AJ
104
Этот ответ чрезвычайно вводит в заблуждение, поскольку он заставляет читателя полагать, что dunderscore используется для того, чтобы сделать атрибуты экземпляра «суперприватными». Это не тот случай, как объяснил здесь Рэймонд Хеттингер, который прямо заявляет, что dunderscore неправильно используется, чтобы пометить участников как приватные, в то время как он был разработан как противоположность приватным.
Маркус Месканен
14
@MarkusMeskanen Я не согласен с тем, что в ответе явно указывается использование дополнительного ключа для создания частных и переменных класса. В то время как dunderscore был разработан для того, чтобы эти методы и переменные были легко перезаписаны подклассами (делая их общедоступными), использование dunderscore сохраняет частный экземпляр для использования в этом классе.
Arewm
7
@MarkusMeskanen: у подклассов есть свобода использовать те же имена, что и у суперкласса, без зазубрин надкласса - другими словами, имена дурда суперклассов становятся частными для себя.
Итан Фурман
311

Отличные ответы до сих пор, но некоторые лакомые кусочки отсутствуют. Одно начальное подчеркивание - это не просто соглашение: если вы используете from foobar import *, а модуль foobarне определяет __all__список, имена, импортированные из модуля , не включают имена с начальным подчеркиванием. Допустим, это в основном соглашение, так как этот случай довольно неясен ;-).

Соглашение о нижнем подчеркивании широко используется не только для частных имен, но и для того, что C ++ назвал бы защищенными - например, для имен методов, которые полностью предназначены для переопределения подклассами (даже тех, которые должны быть переопределены, поскольку в базовый класс они raise NotImplementedError! -) часто являются именами, начинающимися с символа подчеркивания, чтобы указать коду, использующему экземпляры этого класса (или подклассы), что указанные методы не предназначены для непосредственного вызова.

Например, чтобы создать потокобезопасную очередь с дисциплиной очереди, отличной от FIFO, необходимо импортировать очередь, подклассы Queue.Queue и переопределить такие методы, как _getи _put; «Код клиента» никогда не называет тех ( «крючок») методы, а скорее ( «организация») общественные методы , такие , как putи get(это известно как метод шаблона дизайна шаблона - смотри , например , здесь за интересную презентацию на основе видео моего разговора на эту тему, с добавлением краткого изложения стенограммы).

Изменить: ссылки на видео в описании переговоров теперь не работает. Вы можете найти первые два видео здесь и здесь .

Алекс Мартелли
источник
1
Итак, как вы решаете, использовать ли _var_nameили использовать var_name+, исключая его из __all__?
эндолиты
3
@endolith Используйте начальное подчеркивание, чтобы сообщить читателю вашего кода, что он, вероятно, не должен использовать это (например, потому что вы можете изменить его в версии 2.0 или даже 1.1); используйте явный __all__всякий раз, когда вы хотите сделать модуль from spam import *дружественным (в том числе в интерактивном переводчике). Таким образом, в большинстве случаев ответ - оба .
abarnert
@AlexMartelli Обсуждается ли это правило, связанное с импортом, где-то в документации или где-либо еще?
Vicrobot
1
Мне нравится аналогия C ++. Во-первых, мне не нравится, когда люди называют _ личное . Очевидно, я говорю об аналогиях, поскольку в Python нет ничего действительно частного . Во время погружения в семантику я бы сказал , что мы можем связать _с Явой защитой , поскольку proctected в Java с помощью «производных классов и / или в одном пакете». Замените пакет модулем, так как PEP8 уже говорит нам, что _это не просто соглашение, когда речь идет об *импорте, и у вас это есть. И определенно __было бы эквивалентно приватности Java, когда речь идет об идентификаторах в классе.
Мариус Мусенику
2
В то время как достойный ответ, это также в значительной степени самореклама.
Гибридный веб-разработчик
299

__foo__Это просто соглашение, способ для системы Python использовать имена, которые не будут конфликтовать с именами пользователей.

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

__fooЭто имеет реальное значение: интерпретатор заменяет это имя _classname__fooна способ, чтобы гарантировать, что имя не будет совпадать с аналогичным именем в другом классе.

Никакая другая форма подчеркивания не имеет значения в мире Python.

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

Нед Бэтчелдер
источник
6
Просто наткнулся __fooи на любопытство. Как это может совпадать с аналогичными именами методов с другими классами? Я имею в виду, что вы все еще должны получить к нему доступ instance.__foo()(если он не был переименован переводчиком), верно?
Бибхас Дебнатх
81
Этот парень заявляет, что from module import *не импортирует объекты с префиксом подчеркивания. Следовательно, _fooэто больше, чем просто соглашение.
dotancohen
4
@Bibhas: если класс Bподклассов класса A, и оба реализуют foo(), то B.foo()переопределяет .foo()унаследованный от A. BДоступ к экземпляру будет возможен B.foo()только через super(B).foo().
naught101
3
Для __dunder__имен неявные вызовы пропускают словарь экземпляров, так что в некоторых случаях это может быть немного больше, чем просто соглашение об именах (см. Раздел поиска специальных методов в datamodel).
Вим
206

._variable является полуприватным и предназначен только для конвенции

.__variableчасто ошибочно считается суперприватным, в то время как его реальный смысл - просто поменять имя, чтобы предотвратить случайный доступ [1]

.__variable__ обычно зарезервирован для встроенных методов или переменных

Вы по-прежнему можете обращаться к .__mangledпеременным, если вы отчаянно хотите. Двойное подчеркивание просто меняет или переименовывает переменную в нечто вродеinstance._className__mangled

Пример:

class Test(object):
    def __init__(self):
        self.__a = 'a'
        self._b = 'b'

>>> t = Test()
>>> t._b
'b'

t._b доступен, потому что он скрыт только по соглашению

>>> t.__a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute '__a'

t .__ a не найден, потому что он больше не существует из-за искажения имен

>>> t._Test__a
'a'

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

NickCSE
источник
но как насчет того, если «__a» была переменной класса, то вы не можете получить к ней доступ даже с инструкциями из документации по Python ..
Виталий Терзиев,
Можете ли вы обновить свой ответ примером двойного подчеркивания в отношении наследования?
переменная
116

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

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

class BaseForm(StrAndUnicode):

    def _get_errors(self):
        "Returns an ErrorDict for the data provided for the form"
        if self._errors is None:
            self.full_clean()
        return self._errors

    errors = property(_get_errors)

(Этот фрагмент кода был взят из исходного кода django: django / forms / forms.py). В этом коде errorsэто открытое свойство, но метод, который вызывает это свойство, _get_errors, является «частным», поэтому вы не должны обращаться к нему.

Два подчеркивания в начале:

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

class A(object):
    def __test(self):
        print "I'm a test method in class A"

    def test(self):
        self.__test()

a = A()
a.test()
# a.__test() # This fails with an AttributeError
a._A__test() # Works! We can access the mangled name directly!

Вывод:

$ python test.py
I'm test method in class A
I'm test method in class A

Теперь создайте подкласс B и выполните настройку для метода __test.

class B(A):
    def __test(self):
        print "I'm test method in class B"

b = B()
b.test()

Выходной будет ....

$ python test.py
I'm test method in class A

Как мы уже видели, A.test () не вызывал методы B .__ test (), как мы могли ожидать. Но на самом деле это правильное поведение для __. Два метода с именем __test () автоматически переименовываются (искажаются) в _A__test () и _B__test (), поэтому они не переопределяются случайно. Когда вы создаете метод, начинающийся с __, это означает, что вы не хотите, чтобы кто-либо мог его переопределить, и вы намереваетесь получить к нему доступ только из его собственного класса.

Два подчеркивания в начале и в конце:

Когда мы видим такой метод __this__, не вызывайте его. Это метод, который Python должен вызывать, а не вы. Давайте взглянем:

>>> name = "test string"
>>> name.__len__()
11
>>> len(name)
11

>>> number = 10
>>> number.__add__(40)
50
>>> number + 50
60

Всегда есть оператор или нативная функция, которая вызывает эти магические методы. Иногда это просто зацепка вызовов Python в определенных ситуациях. Например, __init__()вызывается при создании объекта после __new__()вызова для создания экземпляра ...

Давайте возьмем пример ...

class FalseCalculator(object):

    def __init__(self, number):
        self.number = number

    def __add__(self, number):
        return self.number - number

    def __sub__(self, number):
        return self.number + number

number = FalseCalculator(20)
print number + 10      # 10
print number - 20      # 40

Для получения дополнительной информации см. Руководство PEP-8 . Дополнительные магические методы смотрите в этом PDF .

PythonDev
источник
1
После редактирования этого ответа я предпочитаю stackoverflow.com/a/8689983/1048186
Джозия Йодер
Что вы подразумеваете под «как мы уже видели, A.test () не вызывал методы B .__ test ()» - где вы вызвали A.test ()?
переменная
18

Иногда у вас есть то, что выглядит как кортеж с подчеркиванием, как в

def foo(bar):
    return _('my_' + bar)

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

from sphinx.locale import l_, _

а в sphinx.locale _ () назначается псевдонимом некоторой функции локализации.

Тим Д
источник
11

Так как многие люди имеют в виду Реймонда говорить , я просто сделать его немного легче, написав , что он сказал:

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

class Circle(object):

    def __init__(self, radius):
        self.radius = radius

    def area(self):
        p = self.__perimeter()
        r = p / math.pi / 2.0
        return math.pi * r ** 2.0

    def perimeter(self):
        return 2.0 * math.pi * self.radius

    __perimeter = perimeter  # local reference


class Tire(Circle):

    def perimeter(self):
        return Circle.perimeter(self) * 1.25

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

Скажем, вы не держите местную ссылку perimeterв Circle. Теперь производный класс Tireпереопределяет реализацию perimeter, не касаясь area. Когда вы звоните Tire(5).area(), теоретически его все равно следует использовать Circle.perimeterдля вычислений, но на самом деле он использует Tire.perimeter, что не является предполагаемым поведением. Вот почему нам нужна местная ссылка в Circle.

Но почему __perimeterвместо _perimeter? Потому что _perimeterвсе еще дает производному классу возможность переопределить:

class Tire(Circle):

    def perimeter(self):
        return Circle.perimeter(self) * 1.25

    _perimeter = perimeter

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

Если ваш класс не будет унаследован, или переопределение метода ничего не нарушает, тогда вам просто не нужно __double_leading_underscore.

оборота laike9m
источник
1
Спасибо, слайд не отображался должным образом, поэтому я не понял, почему мой код потерпел неудачу.
Cgte
8

Если кто-то действительно хочет сделать переменную только для чтения, IMHO, лучшим способом будет использование property () с передачей только getter. С помощью свойства () мы можем полностью контролировать данные.

class PrivateVarC(object):

    def get_x(self):
        pass

    def set_x(self, val):
        pass

    rwvar = property(get_p, set_p)  

    ronly = property(get_p) 

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

Дев Маха
источник
8

Согласно https://dbader.org/blog/meaning-of-underscores-in-python

  • Single Leading Underscore (_var) : Соглашение об именовании, указывающее имя, предназначено для внутреннего использования. Обычно не применяется интерпретатором Python (за исключением импорта с использованием подстановочных знаков) и предназначен только для подсказки программисту.
  • Single Trailing Underscore (var_) : используется по соглашению, чтобы избежать конфликтов имен с ключевыми словами Python.
  • Двойное начальное подчеркивание (__ var) : запускает искажение имени при использовании в контексте класса. Осуществляется интерпретатором Python.
  • Двойной начальный и конечный знак подчеркивания (__ var__) : обозначает специальные методы, определенные языком Python. Избегайте этой схемы именования для своих собственных атрибутов.
  • Single Underscore (_) : иногда используется как имя для временных или незначительных переменных («все равно»). Также: результат последнего выражения в Python REPL.
Feuda
источник
5

Отличные ответы, и все они правильные. Я привел простой пример вместе с простым определением / значением.

Смысл:

some_variable --► это публично, каждый может увидеть это.

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

__some_varaible --► Python заменяет имя переменной на _classname__some_varaible (искажение имени AKA) и уменьшает / скрывает ее видимость и больше напоминает приватную переменную.

Просто чтобы быть честным здесь Согласно документации Python

«Закрытые» переменные экземпляра, к которым нельзя получить доступ, кроме как изнутри объекта, не существуют в Python »

Пример:

class A():
    here="abc"
    _here="_abc"
    __here="__abc"


aObject=A()
print(aObject.here) 
print(aObject._here)
# now if we try to print __here then it will fail because it's not public variable 
#print(aObject.__here)
grepit
источник
_ _some_varaible - .... и это уменьшает / скрывает его видимость и больше напоминает приватную переменную. Нет, искажение имени - это главное, оно не скрывает метод.
AMC
4

Одиночные ведущие подчеркивания - это соглашение. нет никакой разницы с точки зрения переводчика, если имена начинаются с одного подчеркивания или нет.

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

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

SilentGhost
источник
3

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

Но, например, имена __double_underscore не искажаются в модулях. Что происходит, так это то, что имена, начинающиеся с одного (или более) подчеркивания, не импортируются, если вы импортируете все из модуля (из модуля import *), а также имена не отображаются в справке (module).

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

Вот простой иллюстративный пример того, как свойства двойного подчеркивания могут влиять на унаследованный класс. Итак, со следующей настройкой:

class parent(object):
    __default = "parent"
    def __init__(self, name=None):
        self.default = name or self.__default

    @property
    def default(self):
        return self.__default

    @default.setter
    def default(self, value):
        self.__default = value


class child(parent):
    __default = "child"

если вы потом создадите дочерний экземпляр в python REPL, вы увидите ниже

child_a = child()
child_a.default            # 'parent'
child_a._child__default    # 'child'
child_a._parent__default   # 'parent'

child_b = child("orphan")
## this will show 
child_b.default            # 'orphan'
child_a._child__default    # 'child'
child_a._parent__default   # 'orphan'

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

Марк
источник
3

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

ссылка https://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references

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

Получить факты _ и __ довольно легко; другие ответы выражают их довольно хорошо. Использование намного сложнее определить.

Вот как я это вижу:

_

Должен использоваться для указания того, что функция не предназначена для публичного использования, как, например, API. Это и ограничение импорта заставляют его вести себя так же, как internalв c #.

__

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

==>

Если вы хотите указать, что что-то не для публичного использования, но оно должно действовать как protectedиспользование _. Если вы хотите указать, что что-то не для публичного использования, но оно должно действовать как privateиспользование __.

Это также цитата, которая мне очень нравится:

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

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

захватчик
источник