Страницы

Поиск по вопросам

среда, 5 июня 2019 г.

Почему меняется атрибут в конструкторе экземпляра класса?

Здравствуйте, пытаюсь разбираться с ООП в python, и возник такой вопрос: я создаю 2 экземпляра класса ListAdder. Сразу после создания первого объекта я меняю ему атрибут x.data и затем создаю второй объект этого же класса, но при этом атрибут y.data - уже не пустой список, который инициализируется в конструкторе, а такой же, как и x.data. Почему так происходит? Разве каждый новый объект не должен создаваться с атрибутом self.data, равным пустому списку?
class ListAdder: def __init__(self, start=[]): self.data = start
def add(self, other): self.data.extend(other) return self.data
if __name__ == '__main__': x = ListAdder() x.add([1, 2]) print(x.data) y = ListAdder() print(y.data)
>>> [1, 2] >>> [1, 2]


Ответ

Потому что значения по-умолчанию параметров функции или метода создаются не каждый раз при вызове этой функции/метода, а единожды в момент из создания. Смотрите, когда создается класс ListAdder, создаются так же и его методы, и тогда создается объект-пустой список, и указывается, что он является значением по-умолчанию для параметра start метода __init__, причем этот один и тот же объект будет значением по-умолчанию для всех вызовов этой функции.
Когда объект неизменяемый, как, например, число или кортеж, это проблемы не вызывает, но когда он изменяемый, то любые изменения, сделанные в этом объекте, в одном из вызовов функции, отражаются на остальных вызовах.
Вот более простой и наглядный пример, показывающий суть проблемы:
>>> def test(l=[]): ... print('id:', id(l)) ... l.append(1) ... return l ... >>> test() id: 139654229177416 [1] >>> test() id: 139654229177416 [1, 1] >>> test() id: 139654229177416 [1, 1, 1]
Функция id возвращает уникальный идентификатор объекта, и тут мы можем видеть, что при каждом вызове test, l ссылается на один и тот же список, что и подтверждается возвращаемым значением функции: при каждом вызове список l увеличивается на один элемент.
Чтоб избежать этой проблемы, нужно создавать изменяемый объект при каждом вызове самостоятельно. Классическим способом это сделать является установка значения по-умолчанию в None с последующей проверкой на то, было ли передано какое-то иное значение, или нет, и в последнем случае, создание нужного объекта (пустого списка в вашем случае).
class ListAdder: def __init__(self, start=None): if start is None: # Здесь каждый раз, когда start задан по умолчанию, # мы создаем новый экземпляр пустого списка, и # присваиваем его переменной start start = [] # Теперь все последующие изменения списка start не # затронут остальные вызовы метода. self.data = start
...

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

Отправить комментарий