This is expected behavior.
When a program terminates, all objects remaining in its memory are deleted (which means that their destructors will be called).
When I run the program, the print statement for Tennis ball 2345 has been deleted :( is executed after the print statement for Num balls after del ball a: 1. In other words, the destructor for ball b is called when the program terminates, after all the code has been executed.