Deleting class B when calling __del__ of class A

Hello everybody,

I am a newcomer in OOP and I am trying to code my own __del__ method.
I would need some advice to organize my code.

As an exercise, I would like to create a virtual storage furniture.
In this storage furnitures, there are several shelves.
On each shelf, there a k places for CD, DVD or books.

I have organized my code as follow :

  • The storage furniture is class Library initialized by __init__(self, nb_of_shelves=0) (number of shelves as possible arg).
    One of its attribute is self.shelves = [Shelf() for i in range(nb_of_shelves)] with Shelf the class designing one of the library shelves.
  • class Shelf is initialized using __init__(self, nb_of_products=0) and with an attribute self.products = [None for i in range(nb_of_products)]
  • Shelf has methods to add Book, CD or DVD at location k
  • Book, CD and DVD are classes with attributes such as name, date, authors …

My willing is to be able to create a library empty or not, with x number of shelves, each shelf having its own product storage capacity and being able to store anything at any location.

I am trying to create custom del methods, to delete every instances if I delete the library or one shelf.

I did like below in the class Library:
(sorry for the unindentation I don’t know how to add it)
def __del__(self):
for each shelf in self.shelves:(I have defined iterator in the class)
print(f"Deleting shelf {shelf.name}")
del shelf

However, when using del lib (with lib an instance of Library) it doesn’t delete sub-instances. But when I re-instanciate lib = Library I can see entering the for loop and deleting all “sub-instances”.

I have two questions:

  1. Do I am doing well ?
  2. How can I process to delete my sub-instances ?

Thank you for help,

Loupdmer

What you are trying to do here (manually deleting objects) is considered an anti-pattern in Python, and is intentionally not easy to do. Python is a garbage collected language, and objects are automatically deleted when the last reference to a certain object goes out of scope (although they are not guaranteed to be deleted immediately).

As to why your method isn’t working:

for shelf in self.shelves:
    del shelf

This just creates a new reference shelf to an existing object self.shelves[<index>] and immediately deletes it. The original reference self.shelves[<index>] is still there, and so is the underlying object.

You could do this:

while self.shelves:
    del self.shelves[0]

but you shouldn’t because

  1. It still won’t delete the underlying object if there are additional references still remaining.
  2. If there are no additional references, the garbage collector will delete it for you anyway when the parent object is deleted.
  3. Modifying iterators while you are looping over them is a bad idea.

In general, the __del__ method should be used to clean up resources that would not otherwise be automatically deleted, such certain external connections, or files opened outside of context managers.

1 Like

Thank you !

If there are no additional references, the garbage collector will delete it for you anyway when the parent object is deleted.

If I well understand, I just have to use del <instance> and every references with other instances will be deleted too ?
However, my classes don’t inherit from my Library class (because they do not represent the same thing), should I conceive them to inherit from Library ?

For now, what happen if I do this :
shelf_a = Shelf()
book_1 = shelf_a.add_book()
del shelf_a

Is my book_1 deleted ? (This I what I am looking for)

Instead of using del at all, why not just remove the instances you want to get rid of from the list?

As above, you don’t often need to worry about deleting items yourself.

No, and the functionality of the add_book method looks a little backwards. I would expect Shelf.add_book to add an existing book to the shelf, not create a new book.

If you do this:

shelf = Shelf()
shelf.add_book(Book(title="Moby Dick"))
del shelf

Then the book will be deleted along with the shelf, because nothing else references it.

However,

shelf = Shelf()
shelf.add_book(Book(title="Moby Dick"))
moby_dick = shelf.books.get_title("Moby Dick")
del shelf

Here the book will still exist, because an additional reference was created.

Again, while it is probably possible to make it so moby_dick is deleted when shelf is, I would consider that a code smell. Python objects do not typically behave like that, so this would make the code harder to understand.

Also, what ND said. You almost certainly don’t actually need to use del at all.

Ok, I better understand how to deal with it :slight_smile:
I would keep references because it would facilitate me to add books into the shelves, so I will not use del and just remove them from the list.
Thank you both for clarifying these notions. I guess I am going to go deeper into the notion of garbage collector and how python behaves.

I would have a last question, what if I decided to not use references and doing like that :
lib = Library()
lib.add_shelf(Shelf(name="shelf_for_books"))
lib.add_shelf(Shelf(name="shelf_for_CDs"))
lib.add_shelf(Shelf(name="shelf_for_DVDs"))

If I want to add a book on the shelf “shelf_for_books”, I would need to do lib.shelves[1].add(Book(title="Moby Dick") or lib.get_shelf("shelf_of_books").add(Book(title="Moby Dick") (with get_shelf returning the matching object from the lib.shelves list) or lib.add("shelf_of_books", Book(title="Moby Dick")) ? No way to make it easier/more efficient ?

I’m asking in case there are other ways I don’t know yet, I just discovered class and static methods so I’m open to any unknown ideas.

Thank you again for help !

If you want to be 100% certain the books go out of scope at the same time as the shelves (and the shelves at the same time as the library), then I am not aware of an easier way to do it.

But I would really encourage you to not worry about this and let the garbage collector handle it. That is literally what it’s for. Unless you are dealing with very memory intensive objects, this sort of optimization is more trouble than it’s worth.

1 Like

As a newcomer to OOP, the best advice for __del__ is “Never Use It”.

As an expert, you might, very very very rarely, use it. But probably not even then.

That’s not what the __del__ method is used for. The method doesn’t delete anything. It is called when the object is garbage collected. You almost never need to do anything when an object is garbage collected, which is why we almost never need to write a __del__ method.

The del command is different, but it too doesn’t delete objects. It deletes references to objects (such as names).

For example, when we have this:

x = None
del x

That doesn’t delete the None object. It would be a terrible disaster if the None object was deleted, because it is used by thousands of functions and classes all through Python. What it does is delete the name “x”, so that you can no longer use “x” to refer to None.

If you aren’t sure what this means, please see Ned Batchelder’s presentation on names and values.

You have this code:

def __del__(self):
    for shelf in self.shelves:
        print(f"Deleting shelf {shelf.name}")
        del shelf

But that doesn’t do what you think. Each iteration of the loop, the interpreter creates a name, “shelf”, and binds it to a value, one of your shelves. You print a message, and then you delete the name “shelf”, doing absolutely nothing to the shelf objects.

What’s the right way to delete the shelves?

Do nothing at all. Let the garbage collector (GC) delete the shelves, when they are no longer in use. The GC tracks when objects are in use, and so long as an object is used (which means your code or the interpreter may need to access it), the object is kept alive. As soon as the object is no longer accessible, the GC deletes it, freeing up its memory. You don’t have to do anything.

Objects assigned to local variables inside functions and methods are freed when the function exits, unless they are accessible to other parts of your program. E.g. if your function or method returns a value. Global variables may remain alive for longer, but when your script or program finishes, they too will be freed and deleted.

Honestly, you almost never need to worry about this.

Without seeing your code, it isn’t clear what is happening, but my guess is that your Library object has two names, or one name and some other reference to it. When you say del lib that removes one of the names, but the other hangs around until you do something else.

When you do that second thing, the last reference to the original library object is freed, and your __del__ method is called, which prints out a series of lies about deleting the shelves.

Then the shelves are no longer in use, so the garbage collector does actually delete them. So even though your code lies about deleting the shelves, they do actually get deleted, by the garbage collector. So the printed statements about deleting the shelves turned out to be true, but for the wrong reason! :slight_smile:

3 Likes

Steven D’Aprano has described things at length, but the short version:

No, the statement:

 del name

just removes name (eg a local variable, which is a reference to some
object). It has no direct effect on the object/instance itself at all.

The Python interpreter tracks how many references there are to an
object/instance and collects its memory when that count is seen to
become zero.

The __del__ method is called just before the memory for the instance
is collected. But that timing is not under your control. It is at the
whim of the Python interpreter.

Generally we just rely on letting go of things we no longer need, and
that cascades to everything the instance references etc.

Cheers,
Cameron Simpson cs@cskk.id.au