Спадкування приватних та захищених методів у Python


79

Я знаю, що в Python немає `` справжніх '' приватних / захищених методів. Цей підхід не покликаний нічого приховувати; Я просто хочу зрозуміти, що робить Python.

class Parent(object):
    def _protected(self):
        pass

    def __private(self):
        pass

class Child(Parent):
    def foo(self):
        self._protected()   # This works

    def bar(self):
        self.__private()    # This doesn't work, I get a AttributeError:
                            # 'Child' object has no attribute '_Child__private'

Отже, чи означає ця поведінка, що «захищені» методи передаватимуться у спадок, а «приватні» - ні?
Або я щось пропустив?


Що ви маєте на увазі під "це не працює"?
tyteen4a03

Я відредагував оригінальне повідомлення.
uloco

6
ви повинні називати це так, припустимо, що c є екземпляром Childc._Parent__private()
vahid abdi

Хіба це не працює як слід? Приватні методи AFAIK не успадковуються. stackoverflow.com/questions/8241462 / ...
QBack

Відповіді:


115

Python не має моделі конфіденційності, не існує модифікаторів доступу, як у C ++, C # або Java. Не існує справді "захищених" або "приватних" атрибутів.

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

Мангулювання здійснюється шляхом попереднього додавання будь-якого такого імені з додатковим підкресленням та назвою класу (незалежно від того, як ім'я використовується або якщо воно існує), фактично надаючи їм простір імен . У Parentкласі будь-який __privateідентифікатор замінюється (під час компіляції) на ім'я _Parent__private, тоді як у Childкласі ідентифікатор замінюється на _Child__privateскрізь у визначенні класу.

Працюватиме наступне:

class Child(Parent):
    def foo(self):
        self._protected()

    def bar(self):
        self._Parent__private()

Див. Зарезервовані класи ідентифікаторів у документації лексичного аналізу:

__*
Клас-приватні імена. Імена в цій категорії, коли вони використовуються в контексті визначення класу, переписуються з використанням спотвореної форми, щоб уникнути зіткнень імен між "приватними" атрибутами базового та похідних класів.

та посилання на документацію на імена :

Маніпуляція приватним іменем : Коли ідентифікатор, який текстово зустрічається у визначенні класу, починається з двох або більше символів підкреслення і не закінчується двома або більше символами підкреслення, він вважається приватним ім'ям цього класу. Приватні імена трансформуються у довшу форму до того, як для них буде сформовано код. Трансформація вставляє назву класу з видаленими підкреслювальними знаками та вставляє єдине підкреслення перед іменем. Наприклад, ідентифікатор, __spamщо зустрічається в класі з іменем Ham, буде перетворений на _Ham__spam. Це перетворення не залежить від синтаксичного контексту, в якому використовується ідентифікатор.

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

PEP 8 Python Керівництво по стилю це сказати про приватному ім'я перекручуючи:

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

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

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

Примітка 3: Не всі люблять перекручування імен. Спробуйте збалансувати необхідність уникати випадкових зіткнень імен з потенційним використанням досвідчених абонентів.


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

Що про __getstate__()та __setstate__()? Дитина не зможе успадкувати ці методи від свого батька?
BoltzmannBrain

@BoltzmannBrain: Ці імена також містять підкреслення в кінці, а не лише на початку імені. Це інший клас імен . Дивіться документацію, на яку я посилався .
Мартін Пітерс

Дякую, але документи там не дають мені набагато кориснішої інформації. Я переніс це на інше питання: stackoverflow.com/questions/44444733/…
BoltzmannBrain

@Martijn: Дякую за відповідь. Можливо, я не використовую a, pythonic styleнамагаючись зробити усі змінні приватними всередині класу, а потім використовувати gettersі settersдля цих приватних змінних. Ну, мені слід загуглити це питання про використання приватних змінних.
FooBar167

14

Змінено подвійний __атрибут, _ClassName__method_nameщо робить його більш приватним, ніж семантична конфіденційність, що передбачається _method_name.

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

class Parent(object):
    def _protected(self):
        pass

    def __private(self):
        print("Is it really private?")

class Child(Parent):
    def foo(self):
        self._protected()

    def bar(self):
        self.__private()

c = Child()
c._Parent__private()

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


1
Це не спрацює у випадку дзвінка всередині дитини з приватним методом "Батько"
Ніко Колліє,

8

Оголосивши свого члена даних приватним:

__private()

ви просто не можете отримати доступ до нього поза класом

Python підтримує техніку, яка називається перекручуванням імен .

Ця функція перетворює учасника класу з префіксом у два підкреслення:

_className.memberName

якщо ви хочете отримати до нього доступ, Child()ви можете використовувати:self._Parent__private()


8

Також каже PEP8

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

Щоб уникнути зіткнень імен з підкласами, використовуйте два підкреслення, що ведуть, щоб викликати правила керування іменами Python.

Python обробляє ці імена іменем класу: якщо class Fooмає атрибут з іменем __a, він не може отримати до нього доступ Foo.__a. (Наполегливий користувач все одно може отримати доступ, зателефонувавши Foo._Foo__a.) Як правило, подвійні підкреслення, що ведуть, слід використовувати лише для того, щоб уникнути конфліктів імен з атрибутами в класах, призначених для підкласу.

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


3

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

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

parent_class_private_func_list = [func for func in dir(Child) if func.startswith ('_Parent__')]

for parent_private_func in parent_class_private_func_list:
        setattr(self, parent_private_func.replace("_Parent__", "_Child"), getattr(self, parent_private_func))        

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

self.__private()

2

AFAIK, у другому випадку Python виконує "маніпуляцію іменами", тому ім'я методу __private батьківського класу насправді:

_Parent__private

І ви не можете використовувати його у дитини в такому вигляді

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