Назва пітона манґлінг


109

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

Чи справедливо те саме для 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

А як з подвійним підкресленням? Ну, подвійна магія підкреслення використовується головним чином, щоб уникнути випадкових перевантажень методів і конфліктів імен з атрибутами суперкласів . Це може бути дуже корисно, якщо ви пишете клас, який, як очікується, буде розширений у багато разів.

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

EDIT : Чому це так? Ну, звичайний стиль Python не наголошує на тому, щоб робити речі приватними - навпаки! Причин тому багато - більшість з них суперечливі ... Давайте подивимось деякі з них.

Python має властивості

Більшість мов OO сьогодні використовують протилежний підхід: те, що не слід використовувати, не повинно бути видимим, тому атрибути повинні бути приватними. Теоретично це дало б більш керовані, менш зв'язані класи, оскільки ніхто не міг би змінювати значення всередині об’єктів.

Однак це не так просто. Наприклад, у класів 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, які скажуть вам видалити __з коду, коли ви задасте питання в переповненні стека :)


1. Інкапсуляція призначена для захисту інваріантів класу. Не ховати зайвих деталей із зовнішнього світу, бо це буде роздратування. 2. "Справа в тому, що ваш API повинен бути хорошим, а решта - деталями." Це правда. А публічні атрибути є частиною вашого API. Крім того, іноді підходять загальнодоступні спеціалісти (стосовно інваріантів класу), а іноді - ні. API, який має публічні налаштування, які не повинні бути загальнодоступними (ризик порушення інваріантів) - це поганий API. Це означає, що ви все одно повинні думати про видимість кожного сетера і мати "за замовчуванням" - це означає менше.
Юпітер

21

По-перше - що таке ім'я 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 не є Лісковим заміним, і жодні приклади не приходять до уваги, де я вважаю це корисним.

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

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

Вам це потрібно знати, щоб ви знали це, коли бачите.

PEP 8

PEP 8 , стандартний посібник зі стилю бібліотеки Python, на даний момент говорить (скорочено):

Існує певна суперечка щодо використання __names.

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

  1. Зауважте, що лише ім'я класу використовується в назві, яке керується, тому якщо підклас вибирає і те саме ім'я класу, і ім'я атрибута, ви все одно можете отримати зіткнення імен.

  2. Управління іменами може зробити певне використання, наприклад, налагодження і __getattr__(), менш зручним. Однак алгоритм керування іменами добре задокументований і простий у виконанні вручну.

  3. Не всім подобається ім'я mangling. Спробуйте врівноважити необхідність уникнути випадкових зіткнень з іменами з потенційним використанням передових абонентів.

Як це працює?

Якщо ви додасте два підкреслення (без закінчення подвійних підкреслень) у визначенні класу, ім'я буде змінено, і підкреслення з наступним іменем класу буде попередньо висунуте на об'єкт:

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

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

Якщо угода полягає у використанні лише одного підкреслення, я також хотів би знати її обґрунтування.

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

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


15

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

Набагато кращим рішенням є маршрут, який заохочує Python: Ваші класи та змінні повинні бути добре задокументовані, а їх поведінка - чіткою. Джерело має бути доступним. Це набагато більш розширений і надійний спосіб написання коду.

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

  1. Просто напишіть прокляту річ, не робіть припущень щодо захисту ваших даних. Це передбачає, що ви пишете для створення ідеальних інтерфейсів для своїх проблем.
  2. Використовуйте провідне підкреслення для речей, які, ймовірно, , не будуть використовуватися зовні і не є частиною звичайного інтерфейсу "клієнтський код".
  3. Використовуйте подвійне підкреслення лише для речей, які суто зручні для занять усередині класу, або якщо вони випадково потрапляють у дію, вони завдають значної шкоди.

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

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


9

Не слід починати з приватних даних і оприлюднювати їх за потребою. Швидше, слід почати з з'ясування інтерфейсу вашого об'єкта. Тобто ви повинні почати з з'ясування того, що бачить світ (публічний матеріал), а потім з'ясувати, які приватні речі необхідні для того, щоб це відбулося.

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

Різниця між _ і __ полягає в тому, що python насправді робить спробу застосувати останнє. Звичайно, це не дуже намагається, але це ускладнює. Якщо _ просто розповідає іншим програмістам, який це задум, вони можуть ігнорувати їх на небезпеку. Але ігнорування цього правила іноді корисно. Приклади включають налагодження, тимчасові хаки та роботу з кодом третьої сторони, який не призначений для використання таким чином, як ви його використовуєте.


6

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

Якщо ви подивитеся на Java / C #, вони мають приватні / захищені / загальнодоступні. Все це - конструкції часу компіляції . Вони виконуються лише під час складання. Якщо ви використовували відображення в Java / C #, ви можете легко отримати доступ до приватного методу.

Тепер кожного разу, коли ви викликаєте функцію в Python, ви по суті використовуєте рефлексію. Ці фрагменти коду однакові у Python.

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

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

Тож з цим не можна бути приватної версії Java / C #, оскільки Python не компілює код. Java та C # не можуть перевірити, чи функція приватна чи публічна під час виконання, оскільки ця інформація відсутня (і вона не знає, звідки функція викликається).

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

Відмова: Я ніколи не чув, щоб хтось із розробки Python говорив щось подібне. Справжня причина відсутності "приватного" - це культурна, але ви також помітите, що більшість мов сценарію / інтерпретації не мають приватних. Суворо виконуваний приват не є практичним ні в чому, крім часу на компіляцію.


4

По-перше: Чому ви хочете приховати свої дані? Чому це так важливо?

Більшу частину часу ти насправді не хочеш це робити, але робиш, тому що це роблять інші.

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

Так ми живемо, і ми з цим все гаразд.

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


2
Ви пропустили причину подвійного підкреслення - це погано для підкласингу ... це покращить вашу відповідь.
Метт Столяр

2
Зважаючи на те, що подвійні підкреслення дійсно є лише для того, щоб запобігти зіткненням імен з підклассерами (як спосіб сказати «від руки» субклассерам), я не бачу, як керування іменами створює проблему.
Аарон Холл

4

Обрана відповідь добре допомагає пояснити, як властивості усувають потребу в приватних атрибутах , але я також додам, що функції на рівні модуля усувають потребу в приватних методах .

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


3

Наступний фрагмент коду пояснить усі різні випадки:

  • дві провідні підкреслення (__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, щоб запобігти заміні цієї змінної будь-яким з підкласу. Ця концепція відома під назвою "Назва Mangling" в 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
Але чи це не робить дві підкреслення схожими на приватні та одну підкреслення схожу на захищену? Чому б просто не почати з "приватного"?
Пол Манта

@Paul Ні, це не так. У Python немає приватного життя, і ви не повинні намагатися цього досягти.
Роман Боднарчук

@ Роман Концептуально кажучи ... Зауважте цитати навколо "приватного".
Пол Манта

1

"Якщо ви сумніваєтесь у тому, чи повинна змінна бути приватною чи захищеною, краще перейти з приватною." - так, те ж саме в Python.

Деякі відповіді тут говорять про «конвенції», але не посилаються на ці конвенції. В авторитетному посібнику для Python, PEP 8 прямо вказано:

Якщо ви сумніваєтесь, вибирайте непублічні; пізніше зробити його публічним пізніше, ніж зробити публічний атрибут непублічним.

Відмінність між державним та приватним, а також ім'ям, що маніпулюють в Python, розглядалися в інших відповідях. З цього ж посилання,

Тут ми не використовуємо термін "приватний", оскільки жоден атрибут не є насправді приватним в Python (без загальної непотрібної кількості роботи).

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