Подтвердить что ты не робот

Инициализация членов класса Python

Я только что недавно столкнулся с ошибкой в ​​Python. Это был один из тех глупых ошибок новичка, но он заставил меня задуматься о механизмах Python (я долго программист на С++, новый для Python). Я выложу код ошибки и объясню, что я сделал, чтобы исправить это, а затем у меня есть пара вопросов...

Сценарий: у меня есть класс под названием A, у которого есть член данных словаря, следующий его код (это, конечно же, упрощение):

class A:
    dict1={}

    def add_stuff_to_1(self, k, v):
        self.dict1[k]=v

    def print_stuff(self):
        print(self.dict1)

Класс с использованием этого кода - класс B:

class B:

    def do_something_with_a1(self):
        a_instance = A()
        a_instance.print_stuff()        
        a_instance.add_stuff_to_1('a', 1)
        a_instance.add_stuff_to_1('b', 2)    
        a_instance.print_stuff()

    def do_something_with_a2(self):
        a_instance = A()    
        a_instance.print_stuff()            
        a_instance.add_stuff_to_1('c', 1)
        a_instance.add_stuff_to_1('d', 2)    
        a_instance.print_stuff()

    def do_something_with_a3(self):
        a_instance = A()    
        a_instance.print_stuff()            
        a_instance.add_stuff_to_1('e', 1)
        a_instance.add_stuff_to_1('f', 2)    
        a_instance.print_stuff()

    def __init__(self):
        self.do_something_with_a1()
        print("---")
        self.do_something_with_a2()
        print("---")
        self.do_something_with_a3()

Обратите внимание, что каждый вызов do_something_with_aX() инициализирует новый "чистый" экземпляр класса A и печатает словарь до и после добавления.

Ошибка (если вы еще не определились):

>>> b_instance = B()
{}
{'a': 1, 'b': 2}
---
{'a': 1, 'b': 2}
{'a': 1, 'c': 1, 'b': 2, 'd': 2}
---
{'a': 1, 'c': 1, 'b': 2, 'd': 2}
{'a': 1, 'c': 1, 'b': 2, 'e': 1, 'd': 2, 'f': 2}

Во второй инициализации класса A словари не пусты, а начинаются с содержимого последней инициализации и т.д. Я ожидал, что они начнут "свежие".

То, что решает эту "ошибку", очевидно, добавляет:

self.dict1 = {}

В конструкторе __init__ класса A. Однако это заставило меня задуматься:

  • В чем смысл инициализации "dict1 = {}" в точке объявления dict1 (первая строка в классе A)? Это бессмысленно?
  • Каков механизм создания экземпляра, который вызывает копирование ссылки из последней инициализации?
  • Если я добавлю "self.dict1 = {}" в конструкторе (или любом другом элементе данных), как это не повлияет на член словаря ранее инициализированных экземпляров?

EDIT: после ответов я теперь понимаю, что, объявив член данных, а не ссылаясь на него в __init__ или где-то еще как self.dict1, я практически определяю, что вызвано в С++/Java статическим элементом данных, Вызывая его self.dict1, я делаю его "связанным с экземпляром".

4b9b3361

Ответ 1

То, что вы считаете ошибкой, - это documented, стандартное поведение классов Python.

Объявление dict вне __init__, как вы изначально делали, объявляя переменную уровня класса. Он создается только один раз, когда вы создаете новые объекты, он будет повторно использовать тот же самый dict. Чтобы создать переменные экземпляра, вы объявляете их с помощью self в __init__; это так просто.

Ответ 2

@Matthew: просмотрите разницу между членом класса и членом объекта в объектно-ориентированном программировании. Эта проблема возникает из-за объявления исходного dict делает его членом класса, а не членом объекта (как и оригинальное намерение плаката). Следовательно, он существует один раз для (разделяется всеми) всех экземпляров класса (т.е. один раз для самого класса, как элемент самого объекта класса), поэтому поведение совершенно корректно.

Ответ 3

Когда вы обращаетесь к атрибуту экземпляра, скажем, self.foo, python сначала найдет "foo" в self.__dict__. Если не найден, python найдет "foo" в TheClass.__dict__

В вашем случае dict1 имеет класс A, а не экземпляр.

Ответ 4

Объявления класса Pythons выполняются как кодовый блок, и любые локальные определения переменных (определения функций - особый вид) хранятся в экземпляре построенного класса. Из-за того, как поиск атрибутов работает в Python, если атрибут не найден в экземпляре, используется значение класса.

интересная статья о синтаксисе класса в истории блога Python.

Ответ 5

Если это ваш код:

class ClassA:
    dict1 = {}
a = ClassA()

Тогда вы, вероятно, ожидали, что это произойдет внутри Python:

class ClassA:
    __defaults__['dict1'] = {}

a = instance(ClassA)
# a bit of pseudo-code here:
for name, value in ClassA.__defaults__:
    a.<name> = value

Насколько я могу судить, это то, что происходит, за исключением того, что a dict скопировал свой указатель вместо значения, которое является поведением по умолчанию во всем Python. Посмотрите на этот код:

a = {}
b = a
a['foo'] = 'bar'
print b