dict как свойство класса, по-видимому, одинаковое для всех экземпляров

#python

Вопрос:

Я распознаю странное поведение в своем коде. Я извлекаю некоторый тестовый код, чтобы продемонстрировать:

 class A:  def __init__(self):  super().__init__()  self.mystr = 'A'  self.mydict['key'] = 'value'  class B(A):  mystr = 'B'  mydict = {}  def __init__(self, name):  print(name, 'mystr (before)', self.mystr)  print(name, 'mydict (before)', self.mydict)  super().__init__()  print(name, 'mystr (after)', self.mystr)  print(name, 'mydict (after)', self.mydict)  if __name__ == '__main__':  b1 = B('b1')  b2 = B('b2')  

На выходе получается:

 b1 mystr (before) B b1 mydict (before) {} b1 mystr (after) A b1 mydict (after) {'key': 'value'} b2 mystr (before) B b2 mydict (before) {'key': 'value'} // lt;- I expect this to be {} b2 mystr (after) A b2 mydict (after) {'key': 'value'}  

Отель mystr работает так, как я и ожидал. Во втором случае b2 это «B», как оно было инициализировано. Но свойство mydict инициализировано неправильно для второго экземпляра и содержит пару ключ/значение:

b2 mydict (до) {‘ключ’: ‘значение’}

Я знаю, что в Python есть изменяемые и неизменяемые переменные и что первые из них хранятся по ссылке. Но я могу объяснить, почему мой код работает не так, как ожидалось.

Может кто-нибудь дать мне подсказку?

Ответ №1:

Да, у вас есть один диктант, общий для всех экземпляров класса. Ключ в том, что

 self.mydict['key'] = 'value'  

не является назначением атрибута mydict , поэтому он не создает новый атрибут экземпляра. Скорее, это вызов метода

 self.mydict.__setitem__('key', 'value')  

и поиск self.mydict будет разрешен A.mydict , так как нет именованного атрибута экземпляра mydict .

С другой стороны,

 self.mystr = 'A'  

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

 self.__class__.mystr = 'A' # Modifies A.mystr without creating self.mystr instead  

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

1. Я вижу, что я определяю атрибуты класса — я понимаю. И с собой.мистр = … Я создаю атрибут экземпляра, который затеняет атрибут класса, верно?

2. Правильный. Существует асимметрия между атрибутами класса и экземпляра. Доступ к атрибуту через экземпляр может вернуться к доступу к атрибуту класса, но назначение атрибута через экземпляр всегда создает атрибут экземпляра, если он не существует.