Инициализация членов класса Python - PullRequest
41 голосов
/ 15 мая 2009

Я недавно боролся с ошибкой в ​​Python. Это была одна из тех глупых ошибок новичка, но она заставила меня задуматься о механизмах Python (я долгое время программист на C ++, новичок в 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. Однако это заставило меня задуматься:

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

РЕДАКТИРОВАТЬ: Следуя ответам, я теперь понимаю, что, объявив элемент данных и не ссылаясь на него в __init__ или где-либо еще как self.dict1, я практически определяю то, что в C ++ / Java называется статическим элементом данных , Называя его self.dict1, я делаю его привязанным к экземпляру.

Ответы [ 5 ]

52 голосов
/ 15 мая 2009

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

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

2 голосов
/ 09 октября 2009

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

1 голос
/ 15 мая 2009

Когда вы обращаетесь к атрибуту instance, скажем, self.foo, python сначала находит 'foo' в self.__dict__. Если не найден, python найдет 'foo' в TheClass.__dict__

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

0 голосов
/ 15 мая 2009

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

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

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

a = {}
b = a
a['foo'] = 'bar'
print b
0 голосов
/ 15 мая 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...