Имя атрибута искажено именем родительского класса (при первом разборе) вместо имени дочернего класса (при создании экземпляра / вызове)

#python #inheritance #name-mangling

#python #наследование #искажение имени

Вопрос:

Я столкнулся с неожиданным поведением при попытке протестировать некоторый код, где я пытался получить доступ к искаженному имени атрибута дочернего класса (__var). Вместо того, чтобы получать значение дочернего элемента, я думал, что вместо этого получаю __var родительского класса.

Окончательное редактирование: дочерний объект обращается к своей собственной версии переменной, имя которой искажено именем родителя вместо своего собственного. Теперь понятно ранее неожиданное поведение.

 class test_parent(object):
    __var = 1

    def getvar(self):
        a = self.__class__.__var
        return a

class test_child(test_parent):
    __var = 2

a = test_parent()
b = test_child()
b.__class__._test_parent__var = 3

print(a.getvar())                       # Prints 1, as expected
print(b.__class__._test_child__var)     # Prints 2, as expected
print(b.__class__._test_parent__var)    # Prints 3, as expected
print(b.getvar())                       # Prints 3
  

Комментарии:

1. Если бы произошло то, что вы ожидали, это разрушило бы всю цель искажения имени. Весь смысл искажения имени заключается в том, чтобы этого не произошло. В противном случае вы бы просто использовали обычное имя атрибута.

2. Весь смысл искажения имени заключается в том, чтобы убедиться, что, когда суперкласс и подкласс оба хотят использовать имя __var , эти варианты использования не имеют ничего общего друг с другом и вообще не влияют друг на друга. Влияние подкласса __var на код суперкласса приведет к поражению этой цели.

3. @jrmylow потому что это и есть цель искажения имен . Это для предотвращения конфликтов имен в подклассах . Просто не используйте искажение имени с двойным подчеркиванием, если вы не хотите такого поведения, потому что, опять же, в этом вся его цель.

4. self.__var будет ссылаться на __<class>_var of <class> , внутри которого оно было лексически определено. Его не волнует тип self или то, как он относится к наследованию.

5.@geckos нет, идентификатор self не имеет значения, и когда вы вызываете child_instance.getvar() self , это фактически Child экземпляр. Важно то, определен ли метод в родительском или дочернем классе *. Не все, что self есть, потому что, опять же, self будет соответствовать экземпляру

Ответ №1:

Ответил с помощью комментариев:

Искажение имени Python происходит, когда интерпретатор «читает» метод (возможно, неверная терминология), а не при его вызове. Тесты подтверждают процесс, который происходит в фоновом режиме.

Когда __var встречается впервые, он находится внутри тела test_parent и искажается именем:

 class test_parent(object):
    __var = 1
  

становится:

 class test_parent(object):
    _test_parent__var = 1
  

Аналогичная вещь происходит, когда __var встречается в test_child , становление _test_child__var . Ранее неожиданное поведение возникло из-за того, что то же самое произошло внутри getvar .

 class test_parent(object):
    ...
    def getvar(self):
        ...
        a = self.__class__.__var
  

становится:

 class test_parent(object):
    ...
    def getvar(self):
        ...
        a = self.__class__._test__parent_var
  

Вот почему тестовый код b.getvar() , возвращаемый 3 один раз b.__class__._test_parent__var , присваивается ему, потому что это значение, к которому обращается метод getvar .