Why does Tkinter image not show up if created in a function?

This code works:

import tkinter


root = tkinter.Tk()
canvas = tkinter.Canvas(root)
canvas.grid(row = 0, column = 0)
photo = tkinter.PhotoImage(file = './test.gif')
canvas.create_image(0, 0, image=photo)
root.mainloop()

It shows me the image.

Now, this code compiles but it doesn't show me the image, and I don't know why, because it's the same code, in a class:

import tkinter


class Test:
def __init__(self, master):
canvas = tkinter.Canvas(master)
canvas.grid(row = 0, column = 0)
photo = tkinter.PhotoImage(file = './test.gif')
canvas.create_image(0, 0, image=photo)


root = tkinter.Tk()
test = Test(root)
root.mainloop()
84150 次浏览

The variable photo is a local variable which gets garbage collected after the class is instantiated. Save a reference to the photo, for example:

self.photo = tkinter.PhotoImage(...)

If you do a Google search on "tkinter image doesn't display", the first result is this:

Why do my Tkinter images not appear? (The FAQ answer is currently not outdated)

Just add global photo as the first line inside the function.

from tkinter import *
from PIL import ImageTk, Image


root = Tk()


def open_img():
global img
path = r"C:\.....\\"
img = ImageTk.PhotoImage(Image.open(path))
panel = Label(root, image=img)
panel.pack(side="bottom", fill="both")
but1 = Button(root, text="click to get the image", command=open_img)
but1.pack()
root.mainloop()

Just add global to the img definition and it will work

The problem is Python automatically deletes the references to the variable by a process known as Garbage Collection. The solution is to save the reference or to create a new reference.

The following are the ways:

  1. Using self to increase the reference count and to save the reference.
import tkinter


class Test:
def __init__(self, master):
canvas = tkinter.Canvas(master)
canvas.grid(row = 0, column = 0)
self.photo = tkinter.PhotoImage(file = './test.gif') # Changes here
canvas.create_image(0, 0, image=self.photo) # Changes here


root = tkinter.Tk()
test = Test(root)
root.mainloop()
  1. Saving it to a list to increase the reference count and to save the reference.
import tkinter
l=[]
class Test:


def __init__(self, master):
canvas = tkinter.Canvas(master)
canvas.grid(row = 0, column = 0)
photo = tkinter.PhotoImage(file = './test.gif')
l.append(photo)
canvas.create_image(0, 0, image=photo)


root = tkinter.Tk()
test = Test(root)
root.mainloop()

While using method 2, you can either make a global list as i did or use list inside the class. Both would work.

Some useful links:

As a rule of thumb, whenever you create your image in an indented block of code you need to safe a reference to that image. This is because of the python's automated garbage collection and it collects everything with a refcount of 0 when it destroys/leaves that frame/page/indented block of code.


The canonical way to deal with it is to have a list of images somewhere in the global namespace and add your image-references to that list. This is convenient but not very efficient and should be used for small applications.

import tkinter as tk


global_image_list = []
global_image_list.append(tk.PhotoImage(file = 'test.png'))

An more efficient way is to bound an attribute to your widget or class that holds that reference for you, as Bryan proposed in his answer. It doesn't make a difference if you do self.image or widget.image that was assigned widget = tk.Widget(.. before. But this also might not the right approach if you want to use that image further even when the widget is destroyed and garbage collected.

import tkinter as tk


root = tk.Tk()
label = tk.Label(root, text='test')
label.image = tk.PhotoImage(file = 'test.png')
label.configure(image=label.image)