2009-05-15 4 views
36

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

Исправлена ​​ошибка (в случае, если вы еще не поняли его еще нет):

>>> 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} 

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

Что решает эту «ошибку», очевидно, добавив:

self.dict1 = {} 

В __init__ конструктор класса А. Однако, что заставило меня задаться вопросом:

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

EDIT: После ответов Теперь я понимаю, что, объявляя элемент данных и не обращаясь к нему в __init__ или где-то еще, как self.dict1, я практически определение того, что называется в C++/Java статический член данных. Называя его self.dict1, я делаю его «привязанным к экземпляру».

+0

Вы должны использовать классы нового стиля, полученные из объекта. – nikow

ответ

49

Что вы делаете, считая ошибку, является documented, стандартное поведение классов Python.

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

+12

Я не сказал, что это ошибка Python, я сказал, что это моя ошибка ... Когда программист что-то делает против документации, это все еще ошибка (глупая, как я уже упоминал). Мой вопрос касается механизмов, которые вызывают это «документированное стандартное поведение» - я хотел бы понять, что находится под капотом. Благодарю. И, в частности, мой первый вопрос, я хотел бы понять, является ли инициализация декларации бесполезной. –

+3

Это не бесполезно; если вы хотите, чтобы переменная сохранялась через экземпляры, вы использовали бы ее. Ваша проблема в том, что когда вы выполняете x = A(), выполняется только код __init__. Переменная класса сохраняется. –

+0

Хорошо, если объявление не находится в __init__, это то же самое, что и «статический» элемент данных в C++, а объявление в «__init__» изменяет его на статичное на привязку к экземпляру? Теперь это понятно, спасибо. –

1

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

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

+1

Это действительно помогло моей интуитивной модели того, как это работает. Поэтому, как только вы свяжете self.x с чем-то в своем объекте (либо в __init__, либо где-то еще), будущие поиски self.x будут ссылаться на эту новую переменную в области вашего экземпляра. Если они не найдут его в области экземпляра, они перейдут в область класса. –

0

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

Это an interesting article about the class syntax по истории блога Python.

0

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

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 
2

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

Смежные вопросы