Отличие инициализации атрибута в методе __init__ и вне его
Объясните, пожалуйста, почему, когда я пишу:
class CustomerList():
def __init__(self):
self.names = []
def add_customer(self, customer):
self.names.append(customer)
print(self.names)
a, b = CustomerList(), CustomerList()
a.add_customer('Ben')
b.add_customer('Joe')
выводятся списки ['Ben'], ['Joe'].
А если написать без __init__:
class CustomerList():
names = []
def add_customer(self, customer):
self.names.append(customer)
print(self.names)
a, b = CustomerList(), CustomerList()
a.add_customer('Ben')
b.add_customer('Joe')
то выведутся списки ['Ben'], ['Ben', 'Joe']? В чем суть метода __init__? Почему в первом случае при добавлении 'Joe' он понимает, что объект b - новый объект и для него заново создается пустой список, в который мы потом добавляем 'Joe', а во втором случае при добавлении 'Joe' он считает, что уже до этого был добавлен 'Ben'? Как именно на все это влияет __init__? Заранее спасибо за ответ!
Ответы (2 шт):
Во втором случае:
class CustomerList():
names = []
def add_customer(self, customer):
self.names.append(customer)
print(self.names)
a, b = CustomerList(), CustomerList()
a.add_customer('Ben')
b.add_customer('Joe')
Создаётся names как переменная класса, то есть, общая для всех объектов класса. В итоге, при добавлении значений в список этой переменной, они добавляются сразу для всех. А в методе __init__ мы создаём переменную экземпляра, отдельную новую переменную для каждого нового объекта, что позволяет хранить в них разные значения, и что вероятно будет правильным в данном случае.
При этом, в случае с переменной класса, мы можем обращаться к самому классу, например: print(CustomerList.names). Более того, когда в коде экземпляра класса происходит обращение self.names и переменная не обнаруживается среди переменных экземпляра, она ищется как раз среди переменных класса (а также среди классов-родителей). Это работает через особую встроенную переменную __dict__, которая есть и у объектов-экземпляров, и у классов (которые тоже являются объектами), и она рекурсивно просматривается при обращении через точку.
Почитать по теме:
Конструктор init определяет атрибуты для каждого создаваемого экземпляра класса. Поэтому, в первом случае каждый экземпляр получает свой список.
Во втором случае атрибут names общий для всех объектов класса, поэтому каждый экземпляр, обращаясь этому списку, изменяет его.
Я думаю будет уместно добавить ещё про пространство имён. Во втором случае у каждого экземпляра нет в namespace списка names (т.к. он не был создан при инициализации), поэтому идёт обращение к namespace класса, откуда и берётся список names.