Как удалить экземпляр класса Python в списке?
Делаю задачку "адресная книга".
Создаю экземпляры класса Contact, добавляя их в список, и записываю список в файл, потом при запуске считываю.
Все почти работает, но при удалении не вызывается метод __del__ (деструктор) из-за того, как я полагаю, что удаляется не сам объект, а лишь элемент списка. Как мне можно удалить объект?
import pickle
class Contact:
def __init__(self, name, number):
self.name = name
self.number = number
def info(self):
print(f'{self.name}: {self.number}')
def __del__(self):
print(f'Контакт {self.name}: {self.number} удален!')
try:
with open('contacts', 'rb') as f:
list_contacts = pickle.load(f)
except EOFError:
list_contacts = []
while (True):
action = input("Возможные действия:\n добавить - 1\n удалить - 2\n все контакты - 3\n выход - q\n> ")
if action == '1':
list_contacts.append(Contact(input("Имя: "), input("Номер: ")))
elif action == '2':
for i, j in enumerate(list_contacts, start=1):
print(i, end=') ')
j.info()
select_for_del = int(input("Введите номер удаляемого контакта: "))
del list_contacts[select_for_del - 1]
elif action == '3':
print('=' * 20)
for x in list_contacts:
x.info()
print('=' * 20)
elif action == 'q':
with open('contacts', 'wb') as f:
pickle.dump(list_contacts, f)
break
Ответы (2 шт):
Я немного улучшил ваш код:
import pickle
class Contact:
def __init__(self, name, number):
self.name = name
self.number = number
def info(self):
print(f'{self.name}: {self.number}')
def _del(self):
print(f'Контакт {self.name}: {self.number} удален!')
try:
with open('contacts', 'rb') as f:
list_contacts = pickle.load(f)
except:
list_contacts = {}
while (True):
action = input("Возможные действия:\n добавить - 1\n удалить - 2\n все контакты - 3\n выход - q\n> ")
if action == '1':
name = input('Имя: ')
number = int(input("Номер: "))
list_contacts[number] = Contact(name, number)
elif action == '2':
for i in list_contacts.values():
i.info()
select_for_del = int(input("Введите номер удаляемого контакта: "))
list_contacts[select_for_del]._del()
del list_contacts[select_for_del]
elif action == '3':
print('=' * 20)
for x in list_contacts:
x.info()
print('=' * 20)
elif action == 'q':
with open('contacts', 'wb') as f:
pickle.dump(list_contacts, f)
break
Я поэкспериментировал с выполнением кода, и вот что обнаружилось: сообщение о том что удалено либо появлялось на несколько пунктов дальше, либо не появлялось вообще. Дело в том что как и сказал @insolor в комментарии к вопросу, сборщик мусора был либо ленивый, либо скорее всего объекты и реально привязывались к j.
Я преобразовал список в словарь, так удобнее хранить номера. И просто вызываю .__del(). Потому что .__del__() для другого сделан.
В списке хранятся не объекты, а ссылки на них. При удалении элемента из списка удаляется ссылка на соответствующий объект. То есть, счетчик ссылок объекта понижается на единицу. Сборщик мусора проверяет, обнулен ли этот счетчик. Если да, то объект удаляется. Но в двух местах кода создаются переменные j и x, которые после завершения соответствующих циклов указывают на последний элемент списка. То есть, при удалении этого элемента, количество ссылок не обнулится и объект останется в памяти, пока на него хоть кто-то ссылается.
Я вижу три варианта, как исправить ситуацию. Первый: не создавать ссылки на объект (в частности, указывающие на него переменные) вне списка, то есть извлекать объекты строго через индекс:
...
for i in range(len(list_contacts)):
print(i+1, end=') ')
list_contacts[i].info()
...
Второй вариант: по завершению цикла "обнулить" его переменную, которая потенциально может ссылаться на удаляемый объект. Если список пустой, то имя переменной цикла не будет инициализировано. Следовательно использование инструкции del для её "обнуления" может вызвать ошибку. Будет лучше присвоить этому имени значение None:
...
for i, j in enumerate(list_contacts, start=1):
print(i, end=') ')
j.info()
j = None
...
Третий вариант: выделить действия по каждому пункту в отдельную функцию, ограничив действие переменных временем её выполнения:
def print_items(seq):
print('=' * 20)
for x in list_contacts:
x.info()
print('=' * 20)
while True:
action = input()
if action == '3':
print_items(list_contacts)
См. также: