python加载图片无法显示原因探究/python内存回收机制作祟
缘起
近来想做一款闲鱼私信的程序,目的呢是实现闲鱼私信在电脑同步、显示、回复等等功能,方便闲鱼卖家管理多个闲鱼店铺的信息,及时与买家沟通。
先用python构造了个测试界面,因为想把界面做得更形象一些,所以需要将接口返回的头像、图片等信息与图文混杂排列,类似于安卓的排版,做到尽量原生的感觉,让用户使用起来可以做到无缝对接。
但其中遇到一个问题,就是用tkinter加载图片时,明明图片已经加载上,却无法显示。令人头秃万分…
虽然很快意识到是python GC的原因,但又尝试探究了一下解决方案:
原版代码及问题
写了个Demo, 先看下原版代码:
from tkinter import *
from PIL import ImageTk, Image
def choose_pic():
img_open = Image.open('./1.jpg')
img = ImageTk.PhotoImage(img_open)
_label.configure(image=img)
if __name__ == '__main__':
root = Tk()
root.wm_minsize(900, 600)
Button(root, text='显示图片', command=choose_pic).pack()
_label = Label(root, image=None)
_label.pack()
root.mainloop()
可以看到上述代码严格按照python 及tkinter代码规范来写的,从代码上看没有任何问题。 但最终的结果却是无法显示图片,而你debug的时候,图片又会显示。头秃+1
原因分析:
究其原因,是因为python在运行结束后,会调用PhotoImage对象的__delete__()方法,然后程序中的img对象就被GC了,虽然加载完成,但当tkinter继续执行mainloop()方法时,img对象已经被GC,自然就显示不了了。 验证的方法虽然不好弄,但你只要在PhotoImage类下__delete__()方法中加个断点,便可以看到该方法被调用:
解决方法
解决这个问题的核心方法就是想办法让PhotoImage对象不被GC,而相关的方法主要有:
1. 添加global变量
2. 添加引用,使PhotoImage对象被一直引用
3. 或可综合起来以上两点,将PhotoImage对象添加到某个mainloop()方法下一直被引用的对象的属性中。
方法1:
将img变量改为全局变量
from tkinter import *
from PIL import ImageTk, Image
def choose_pic():
global img
img_open = Image.open('./1.jpg')
img = ImageTk.PhotoImage(img_open)
_label.configure(image=img)
if __name__ == '__main__':
img = None
root = Tk()
root.wm_minsize(900, 600)
Button(root, text='显示图片', command=choose_pic).pack()
_label = Label(root, image=None)
_label.pack()
root.mainloop()
方法2:
keep a reference 此方法参考:https://stackoverflow.com/questions/14291434/how-to-update-image-in-tkinter-label
from tkinter import *
from PIL import ImageTk, Image
def choose_pic():
img_open = Image.open('./1.jpg')
img = ImageTk.PhotoImage(img_open)
_label.configure(image=img)
_label.image = img # keep a reference
if __name__ == '__main__':
root = Tk()
root.wm_minsize(900, 600)
Button(root, text='显示图片', command=choose_pic).pack()
_label = Label(root, image=None)
_label.pack()
root.mainloop()
可以看出,Label对象本来没有image属性的,使用_label.image = img方法将img对象添加为_label对象的一个属性,保持img对象的持续引用,防止被GC
方法3:
综合上述方法: 将整个窗口程序写成一个类,添加一个类属性为 img对象。 并将PhotoImage对象生成后赋予这个类属性,起到持续引用的目的。
from tkinter import *
from PIL import ImageTk, Image
class Test(object):
def __init__(self, master):
self.root = master
self._label = None
self._img = None
self._page()
def _page(self):
Button(self.root, text='显示图片', command=self.choose_pic).pack()
self._label = Label(self.root, image=None)
self._label.pack()
self.root.mainloop()
def choose_pic(self):
img_open = Image.open('./1.jpg')
self._img = ImageTk.PhotoImage(img_open)
self._label.configure(image=self._img)
if __name__ == '__main__':
window = Tk()
Test(window)
window.mainloop()
|