Форум > Private & Protected в ООП

Private & Protected в ООП

Георги Кунчев
04.11.2023 16:14
Тъй като по време на проверка на домашните се случва доста да коментирам по тази тема, реших да се опитам да направя резюме на правилата за използване на `_` и `__` при именуване на атрибути и методи в класове. **В Python няма истински private атрибути и методи.** По думите на Гуидо - _"We're all consenting adults here"_. Нямаме нужда от специфични рестрикции по темата. В ръцете на програмистите е да се държат отговорно и да знаят кое може и кое не може да използват. И Гуидо допълва _"abundant syntax bring more burden than help"_. Има поне три елемента от Питонския Зен, които биха подкрепили това становище. **Ние сме възрастни, но не можем да четем мисли...** ...затова се въвежда конвенция за именуване на атрибути и методи. Базирано на PEP8, можем да дефинираме следните правила. - `no_underscores` - публичен достъп. Това са атрибути и методи, които класът предоставя за външно ползване. Това е всичко, за което бихте се интересували, ако искате да използвате инстанция на този клас. - `_single_leading_underscore` - само за вътрешно ползване. Това са атрибути и методи, които не са нужни, за да се възползвате от функционалността на инстанция на този клас. Достъп до тях би могъл да доведе до неочаквани резултати, но дори това да не е така, авторът на кода ви казва - тези неща не касаят кода извън този клас. Доста линтери ще се оплакват, ако използвате имена, започващи с `_` извън класа им. Имайте предвид, че това не е просто конвенция, а има един дребен детайл около тези имена. Ако в даден модул има обекти с подобни имена и се опитате да импортирате всичко от този модул (`from some_model import *`), това няма да импортира обектите, които започват с долна черта. - `__double_leading_underscore` - отново само за вътрешно ползване, но с разликата, че двете долни черти прилагат "name mangling" на съответния метод или атрибут. Името мутира в `_someclassname__double_leading_underscore`. Това до известна степен скрива този метод/атрибут, НО той пак е достъпен. Истинската причина тази функционалност да присъства е, че това позволява да се справите с конфликт на имена при наследяване на класове. Имате нужда от него много рядко и не се толерира използването му, освен ако наистина не е нужно. Ето пример. ``` class Limb: @staticmethod def __introduce(): return "I am a limb" def introduce(self): return getattr(self, f'_{self.__class__.__name__}__introduce')() class Hand(Limb): @staticmethod def __introduce(): return "I am a hand" limb = Limb() hand = Hand() print(limb.introduce()) # I am a limb print(hand.introduce()) # I am a hand print("But also...") print(hand._Limb__introduce()) # I am a limb ``` Класът `Limb` има метод `introduce`, който извиква `__introduce`, но при наследяване, можем да дефинираме друг `__introduce` метод. `introduce` е дефиниран по такъв начин, че извиква `__introduce` на класа, дори той да наследява нещо друго. Това не е толкова интересно. Интересно и важно, обаче, е, че все още имате достъп до `__introduce` и на детето, и на родителя, възползвайки се от мутираното име на метода. **Изводът е** - Ако това, което дефинирате, ще се използва извън класа (такава е идеята му - с него изграждате интерфейса към обектите си), не слагате никакви долни черти. - Ако това, което дефинирате, е само за вътрешно ползване и тази информация не касае код, използващ инстанциите ви, слагата една долна черта. - Ако _случайно_ ви трябва функционалност, която искате да остане налична дори при насляване на класа и преизползване на името на метода/атрибута, използвайте две долни черти. **Материали за справка** [PEP8](https://peps.python.org/pep-0008/#descriptive-naming-styles) [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html#3162-naming-conventions)
Дискусия