Яке значення одиничного та подвійного підкреслення перед назвою об’єкта?


1291

Може хтось, будь ласка, пояснить точний сенс того, що мають провідні підкреслення перед назвою об’єкта в Python, і різницю між обома?

Крім того, чи залишається це значення однаковим, чи є предмет, про який йдеться, змінною, функцією, методом тощо?


2
Чудова коротка відповідь з іншої теми: stackoverflow.com/a/8689983/911945
Антон Тарасенко

Відповіді:


1154

Один підкреслення

Імена в класі з провідним підкресленням - просто вказати іншим програмістам, що атрибут або метод призначені для приватних. Однак нічого особливого не робиться із самою назвою.

Цитую PEP-8 :

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

Подвійне підкреслення (ім'я Mangling)

З документів 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
Ця відповідь вкрай вводить в оману, оскільки змушує читача вважати, що атрибути екземплярів "надприватними" використовують анонімні дані. Це не так, як тут пояснив Реймонд Хеттінгер, який чітко зазначає, що датчики помилок неправильно використовуються для позначення членів приватними, в той час як він був розроблений так, щоб протилежно приватному.
Маркус Месканен

13
@MarkusMeskanen Я не погоджуюсь, у відповіді прямо вказано використання датчика для створення екземплярів змінних класів і методів приватного класу. Незважаючи на те, що Dunderscore був розроблений для того, щоб ці методи та змінні легко перезаписалися підкласами (зробивши їх загальнодоступними), використання dunderscore зберігає приватний екземпляр для використання в цьому класі.
arewm

6
@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 нічого не є справді приватним . Під час занурення в семантику я б сказав, що ми можемо прив'язати _до захищеного Java, оскільки проголошений у Java означає "похідні класи та / або в межах одного пакету". Замініть пакет на модуль, оскільки PEP8 вже говорить нам, що _це не просто умова, коли йдеться про *імпорт, і там ви його маєте. І, безумовно, __було б рівнозначно приватному Java, коли говорити про ідентифікатори в класі.
Marius Mucenicu

2
Хоча гідна відповідь, вона також сильно саморекламується.
Гібридна веб-розробка

298

__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__імен неявні виклики пропускають словник екземплярів, тому це, мабуть, трохи більше, ніж лише умова іменування в деяких випадках (див. Розділ пошуку спеціальних методів у моделі даних).
Вім

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замість просто подвійного імені підкреслення, ви можете отримати доступ до прихованого значення


а як же бути, якщо "__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 / form.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__, не називайте його. Це метод, до якого призначений дзвонити пітон, а не ви. Давайте подивимось:

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

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

Завжди є оператор або нативна функція, яка викликає ці магічні методи. Іноді це просто гачок, який пітон дзвонить у конкретних ситуаціях. Наприклад __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 .


1
Після редагування цей відповідь сам, я вважаю за краще stackoverflow.com/a/8689983/1048186
JosiahYoder-Виключена крім ..

Що ви маєте на увазі під "Як ми бачили, A.test () не викликав методи B .__ test ()" - де ви назвали A.test ()?
змінна

18

Іноді у вас є, що здається, кортеж із провідним підкресленням, як у

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

У цьому випадку те, що відбувається, полягає в тому, що _ () - псевдонім для функції локалізації, яка оперує текстом, щоб перекласти його на належну мову тощо тощо на основі локалі. Наприклад, це робить Сфінкс, і ви знайдете серед імпорту

from sphinx.locale import l_, _

а в shinx.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

Подвійний підкреслення має назву mangling, тому є дуже мало шансів, що місцеві посилання в батьківському класі переможуть у похідному класі. таким чином " робить ваші підкласи вільними на зміну будь-якого одного методу, не порушуючи інших ".

Якщо ваш клас не буде успадкований або переосмислення методу нічого не порушує, то вам просто не потрібно __double_leading_underscore.


1
Дякую, слайд не відображався належним чином, тому я не зрозумів, чому мій код вийде з ладу.
cgte

8

Якщо дійсно хочеться зробити змінну лише для читання, IMHO найкращим способом було б використовувати властивість () з передачею до неї лише 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) 

Я розумію, що ОП задав дещо інше запитання, але оскільки я знайшов інше запитання про те, як "як встановити приватні змінні", позначений дублікатом цього, я подумав додати цю додаткову інформацію сюди.


7

Доступ до https://dbader.org/blog/meaning-of-underscores-in-python

  • Поодинокий підкреслення (_var) : Конвенція про іменування із зазначенням імені призначена для внутрішнього використання. Як правило, не застосовується інтерпретатором Python (за винятком імпорту шаблонів) і означає як підказку лише програмісту.
  • Один підкреслення підкреслення (var_) : використовується умовно, щоб уникнути називання конфліктів із ключовими словами Python.
  • Подвійний підкреслення підкреслення (__ var) : запускає керування іменами при використанні в контексті класу. Закріплений перекладачем Python.
  • Подвійне підкреслення підводки та відключення (__ var__) : Позначає спеціальні методи, визначені мовою Python. Уникайте цієї схеми іменування власних атрибутів.
  • Один підкреслення (_) : Іноді використовується як назва тимчасових або незначних змінних ("не байдуже"). Також: Результат останнього виразу в Python REPL.

5

Чудові відповіді, і всі вони правильні. Я подав простий приклад разом з простим визначенням / значенням.

Значення:

some_variable --► це загальнодоступний, хто це може бачити.

_some_variable --► це публічно кожен може бачити це, але це умовна умова вказувати приватне ... попередження , Python не виконує примусового виконання.

__some_varaible --► Python замінює ім'я змінної _classname__some_varaible (ім'я AKA mangling) і зменшує / приховує її видимість і більше нагадує приватну змінну.

Просто, щоб бути чесним тут, згідно документації 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)

_ _some_varaible - .... і це зменшує / приховує видимість і більше нагадує приватну змінну. Ні, ім'я mangling - справа, це не приховує метод.
AMC

4

Єдине провідне підкреслення - це конвенція. немає жодної різниці з точки зору перекладача, якщо імена починаються з одного підкреслення чи ні.

Подвійні початкові і кінцеві підкреслення використовуються для вбудованих методів, таких як __init__, __bool__і т.д.

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


3

Ваше запитання добре, справа не лише в методах. Функції та об'єкти в модулях, як правило, також є одним підкресленням і можуть бути двома префіксами.

Але, наприклад, імена __double_underscore не називаються іменами в модулях. Що відбувається, так це те, що імена, що починаються з однієї (або більше) підкреслень, не імпортуються, якщо ви імпортуєте все з модуля (з імпорту модуля *), а також імена, що не відображаються у довідці (модулі).


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


1
_ набагато більше схожий, наприклад, на внутрішній c #, а потім на приватний. Подвійне підкреслення це набагато більше схоже на приватне, ніж підкреслення - я б сказало.
Іні

2

Отримати факти _ та __ досить легко; інші відповіді висловлюють їх досить добре. Використання набагато складніше визначити.

Ось як я це бачу:

_

Потрібно використовувати для вказівки, що функція не для громадського використання, як, наприклад, API. Це та обмеження імпорту змушують його вести себе так само, як internalу c #.

__

Слід використовувати для уникнення зіткнення імен у спадковій хірархії та уникнення зав'язки. Наче схоже на приватне в c #.

==>

Якщо ви хочете вказати, що щось не для громадського використання, але воно повинно діяти як protectedвикористання _. Якщо ви хочете вказати, що щось не для громадського використання, але воно повинно діяти як privateвикористання __.

Це також цитата, яка мені дуже подобається:

Проблема полягає в тому, що автор класу може обгрунтовано вважати, що "цей атрибут / ім'я методу має бути приватним, доступним лише з цього визначення класу" та використовувати конвенцію __private. Але згодом користувач цього класу може створити підклас, який законно потребує доступу до цього імені. Отже, або надклас повинен бути модифікований (що може бути важко або неможливо), або код підкласу повинен використовувати вручну керовані імена (що в кращому випадку некрасиво і крихке).

Але проблема в цьому, на мою думку, полягає в тому, що якщо немає IDE, який би попереджав вас, коли ви переосмислюєте методи, пошук помилки може зайняти певний час, якщо ви несанкціоновано перекрили метод базового класу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.