0

In Tkinter, we sometimes have to create a reference to a converted image (for example), so that it will not be destroyed when it goes out of scope.

A common way is to add a variable to its widget instance. An example of this is:

 bard = Image.open("bardejov.jpg") bardejov = ImageTk.PhotoImage(bard) label1 = Label(self, image=bardejov) label1.image = bardejov #<<<<<<<<<<<<<<<<<<<<< label1.place(x=20, y=20) 

This is part of an example published by Jan Bodnar of Zetcode, with my flagging of the example. bardejov is a local variable in a function, and if you comment out the marked line you don't get the image, because it is destroyed when the function returns, and the label just sees 'none'.

I'm new to Tkinter, and this worried me rather, adding new properties to system code, and someone suggested this:

class S(): # To make an object 'accessible', and thus save # it from garbage collection. fred = [] @classmethod def save(cls, x): cls.fred.append(x) 

This certainly worked in Jan's example:

bard = Image.open("xxxx.jpg") bardejov = ImageTk.PhotoImage(bard) label1 = Label(self, image=bardejov) #label1.image = bardejov S.save(bardejov) 

But is it OK? Any undesirable side effects?

4
  • The advantage of storing the image reference in the widget itself is that it automatically goes away when the widget itself is disposed - no memory leak possible. Storing the reference in a global cache makes the image live forever, whether or not it needs to. Commented Nov 10, 2019 at 14:34
  • Perfectly true - in fact one of the suggestions I've seen was to store the reference in your own global variable!! I couldn't bring myself to do that. The memory cost of the class variable is the same - 4 bytes or so per image, but avoids the 'global' disadvantages. 'Forever' is a bit strong, no app lasts that long:-). The question is whether the cost outweighs the horrid practice of tinkering with system code. One day I guess the widget itself will fix the problem. What's your own feeling in the meantime? If the memory cost is the only problem, I'll use S for my work Commented Nov 10, 2019 at 15:06
  • 2
    I think "horrid practice" is vastly overstating the problem. Keep in mind that it's rather common practice to subclass Tkinter widgets to implement your code, in which case all of your data is living in the same namespace as Tkinter's attributes. Yes, there's a potential for name collisions, but you're going to notice pretty quickly if that happens. (And you can't actually break the widget that way, given that the implementation code isn't in Python at all; all you can do is render some built-in attribute inaccessible to you.) Commented Nov 10, 2019 at 17:28
  • OK, Jason, I see the point now. Coming from the strict world of C++, I hadn't shaken off its shackles. Thanks. Commented Nov 12, 2019 at 8:01

1 Answer 1

1

Question: good way of avoiding unwanted garbage collection?

It's not the question, good or not, you have to do it.
Instead of doing it over and over again you can define your own PhotoImageLabel.

For example:

import tkinter as tk from PIL import Image, ImageTk class PhotoImageLabel(tk.Label): def __init__(self, parent, **kwargs): image = Image.open(kwargs['image']) self._image = ImageTk.PhotoImage(image) kwargs['image'] = self._image super().__init__(parent, **kwargs) 

Usage:

class App(tk.Tk): def __init__(self): super().__init__() self.photo_label = PhotoImageLabel(self, image="xxxx.jpg") self.photo_label.grid() if __name__ == '__main__': App().mainloop() 

Comment: One day I guess the widget itself will fix the problem

That is unlikely, because ImageTk.PhotoImage is not part of tkinter. You can extend PhotoImage to behave it like a tkinter object, by binding the image object to the widget:

For example:

class PhotoImage(ImageTk.PhotoImage): def __init__(self, parent, **kwargs): image = kwargs['image'] # PIL.Image => .PhotoImage super().__init__(image) # Update <widget>.children with [self.__photo.name] = self self._ref = parent.children self._ref[self.__photo.name] = self def destroy(self): # This gets called on `.destroy()` the parent # Delete the reference in parent.children del self._ref[self.__photo.name] 

Usage:

class PhotoImageLabel(tk.Label): def __init__(self, parent, **kwargs): image = Image.open(kwargs['image']) # Get a PhotoImage object which is bound to 'self' kwargs['image'] = PhotoImage(self, image=image) super().__init__(parent, **kwargs) 
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.