Требует ли Python глубокого знания всех классов в цепочке наследования?

классы Python не имеют понятия public / private, поэтому нам говорят не трогать то, что начинается с подчеркивания, если мы его не создали. Но разве это не требует полного знания всех классов, от которых мы прямо или косвенно наследуем? Свидетель:

class Base(object):
    def __init__(self):
        super(Base, self).__init__()
        self._foo = 0

    def foo(self):
        return self._foo + 1

class Sub(Base):
    def __init__(self):
        super(Sub, self).__init__()
        self._foo = None

Sub().foo()

ожидается, что в TypeError, когда None + 1 оценивается. Поэтому я должен знать, что _foo существует в базовом классе. Чтобы обойти это, __foo может использоваться вместо этого, который решает проблему исказив имя. Это, по-видимому, если не элегантное, то приемлемое решение. Однако, что произойдет, если Base наследуется от класса (в отдельном пакете) с именем Sub? Теперь __foo в своем Sub переопределяет __foo в прародительских Sub.

это означает, что я должен знать всю цепочку наследования, включая все "частные" объекты, которые каждый использует. Тот факт, что Python динамически типизирован, делает это еще сложнее, так как нет объявлений для поиска. Но хуже всего, вероятно, то, что Base может наследовать от object прямо сейчас, но в какой-то будущей версии он переключается на наследование от Sub. Ясно, если я знаю Sub наследуется от, я могу переименовать свой класс, однако это раздражает. Но я не могу заглянуть в будущее.

разве это не тот случай, когда истинный частный тип данных предотвратит проблему? Как в Python я могу быть уверен, что случайно не наступаю на чьи-то пальцы, если эти пальцы могут возродиться в какой-то момент в будущем?

редактировать: я, по-видимому, не прояснил основной вопрос. Я знаком с именем mangling и разницей между одним и двойным подчеркиванием. Вопрос в том, как мне справиться с тем, что я могу столкнуться с классами о чьем существовании я не знаю прямо сейчас? Если мой родительский класс (который находится в пакете, который я не писал) начинает наследовать от класса с тем же имя как мой класс, даже искажение имени не поможет. Я ошибаюсь, рассматривая это как (угловой) случай, который решат истинные частные члены, но у Python есть проблемы?

редактировать: по просьбе, ниже приведен полный пример:

parent.py:
class Sub(object):
    def __init__(self):
        self.__foo = 12
    def foo(self):
        return self.__foo + 1
class Base(Sub):
    pass
sub.py:
import parent
class Sub(parent.Base):
    def __init__(self):
        super(Sub, self).__init__()
        self.__foo = None
Sub().foo()

и дедушки foo называется, но мой это.

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

6 ответов


Это означает, что я должен знать всю цепочку наследования. . .

Да, вы должны знать всю цепочку наследования, или документы на объект непосредственно суб-причислять должен сказать вам, что вы должны знать.

наследование-это сложная функция, и к ним следует относиться с осторожностью.

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

этот класс представляет действие, которое выполняется в отдельном потоке управления. Существует два способа указать действие: путем передачи вызываемого объекта конструктору или путем переопределения run() метод в подклассе. Никакие другие методы (кроме конструктора) не должны быть переопределены в подклассе. Другими словами, только переопределить __init__() и run() методы этого класса.


использовать частная имена (вместо защищенный ones), начиная с двойного подчеркивания:

class Sub(Base):
    def __init__(self):
        super(Sub, self).__init__()
        self.__foo = None
        #    ^^

не будет конфликтов с _foo или __foo на Base. Это связано с тем, что Python заменяет двойное подчеркивание одним подчеркиванием и именем класса; следующие две строки эквивалентны:

class Sub(Base):
    def x(self):
        self.__foo = None # .. is the same as ..
        self._Sub__foo = None

(в ответ на edit:) вероятность того, что два класса в иерархии классов не только имеют одинаковый имя, но они оба используют одно и то же имя свойства и оба используют частный mangled (__) форма настолько мала, что ее можно смело игнорировать на практике (я до сих пор не слышал ни об одном случае).

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

это дух Дзен питона, которая включает

практичность бьет чистоты.


  1. имя mangling включает в себя класс, поэтому ваш Base.__foo и Sub.__foo будут иметь разные имена. Это была вся причина для добавления функции искажения имени в Python в первую очередь. Один будет _Base__foo, другой _Sub__foo.

  2. многие люди предпочитают использовать композицию (has-a) вместо наследования (is-a) по некоторым из этих самых причин.


как часто вы изменяете базовые классы в цепочках наследования, чтобы ввести наследование от класса с тем же именем, что и подкласс дальше по цепочке???

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

за годы программирования на Python я никогда не считал это большой проблемой на практике. Когда вы называете переменные экземпляра, вы должны иметь довольно хорошее представление о том, является ли (a) имя достаточно общим, чтобы оно могло использоваться в других контекстах, и (b) класс, который вы пишете, вероятно, будет участвовать в иерархии наследования с другими неизвестными классами. В таких случаях вы думаете немного более тщательно об именах, которые вы используете; self.value Не отличная идея для имя атрибута, и ни что-то вроде Adaptor отличное имя класса.

напротив, я столкнулся с трудностями при чрезмерном использовании имен с двойным подчеркиванием несколько раз. Python является Python, даже "частные" имена, как правило, доступны кодом, определенным вне класса. Вы можете подумать, что всегда было бы плохой практикой позволять внешней функции получать доступ к" частным " атрибутам, но как насчет таких вещей, как getattr и hasattr? The ссылка из них можете быть в собственном коде класса, поэтому класс по-прежнему контролирует весь доступ к частным атрибутам, но они по-прежнему не работают без ручного управления именем. Если Python фактически принудительно использовал частные переменные, вы вообще не могли использовать такие функции, как на них. В эти дни я обычно резервирую имена с двойным подчеркиванием для случаев, когда я пишу что-то очень общее, как декоратор, метакласс или миксин, который должен добавить "секретный атрибут" к экземплярам (неизвестных) классов это применимо.

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

в целом, в отсутствие частных переменных просто не так много в идиоматическом коде Python на практике, и добавление истинных частных переменных вызовет более частые проблемы другими способами IMHO.


искажение происходит с двойным подчеркиванием. Один подчеркивает более "пожалуйста".

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


Как уже отмечалось, вы можете использовать имя mangling. Тем не менее, вы можете придерживаться одного подчеркивания (или никто!) если вы документируете свой код адекватно - у вас не должно быть так много частных переменных, что это окажется проблемой. Просто скажите, если метод полагается на закрытую переменную, и добавьте переменную или имя метода в класс docstring для предупреждения пользователей.

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

Если вы действительно хотите иметь "частные" переменные, и по какой-либо причине искажение имен не соответствует вашим потребностям, вы можете разложить свое частное состояние на другой объект:

class Foo(object):

     class Stateholder(object): pass

     def __init__(self):
         self._state = Stateholder()
         self.state.private = 1