For these types of applications, it's the same whether you're using Python or Java or C or C++ or anything else. The strategy to resource management to me begins with asking, "Who owns what?" It might be easy to think everything should "share ownership" of images that needs to reference them, but that's getting seducted by the temptations of GC. From a user-end standpoint only layers should own images. Only the document should own layers. Things of this sort. You might make one exception for the application history since it needs to make sure the necessary data is kept around to be capable of being undone depending on how you implement it (I'd find shared ownership reasonable there, but there should be a separate history per document, and not one for the entire application).
Similar to C where every kind of memory deallocation is completely manual (even short-lived memory, not persistent application state), it's a nice habit to write your code to free the memory as soon or even before you even write the code to allocate it. Similarly if you're implementing like that hash table to cache objects in your example, I think it'd be a smart idea to write the code to remove the object reference from the hash table at the appropriate times if you can't use weak references, perhaps even before writing the code to insert the object to it, and test it and make sure that removal part works because it could easily fly under the radar of testing like a stealth fighter bug with silent leaks unless you very specifically test for that case. With GC I'd double up my testing efforts here because while it's not susceptible to dangling pointers whatsoever, the types of leaks you can get in exchange if you don't remove these references at the appropriate times can be very difficult to detect and trace down otherwise (the mistake might not be as "fatal" to the application but more difficult to detect since it'll appear to work fine except it's not freeing memory at the relevant time).