• python加载图片无法显示原因探究/python内存回收机制作祟


    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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    可以看到上述代码严格按照python 及tkinter代码规范来写的,从代码上看没有任何问题。
    但最终的结果却是无法显示图片,而你debug的时候,图片又会显示。头秃+1

    原因分析:

    究其原因,是因为python在运行结束后,会调用PhotoImage对象的__delete__()方法,然后程序中的img对象就被GC了,虽然加载完成,但当tkinter继续执行mainloop()方法时,img对象已经被GC,自然就显示不了了。
    验证的方法虽然不好弄,但你只要在PhotoImage类下__delete__()方法中加个断点,便可以看到该方法被调用:

    PIL包中ImageTk.py下, PhotoImage类下__delete__()方法截图

    解决方法

    解决这个问题的核心方法就是想办法让PhotoImage对象不被GC,而相关的方法主要有:

    1. 添加global变量
    2. 添加引用,使PhotoImage对象被一直引用
    
    3. 或可综合起来以上两点,将PhotoImage对象添加到某个mainloop()方法下一直被引用的对象的属性中。
    
    • 1
    • 2
    • 3
    • 4
    方法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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    方法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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    可以看出,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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
  • 相关阅读:
    Redis缓存和数据库一致性、雪崩、穿透、热点key、big key
    安卓开机启动流程
    Unity环境下实现Camera高帧率RTMP推送
    hdfs和yarn的常用命令
    RabbitMQ的六种模式
    IDEA创建SparkSQL程序_大数据培训
    机器学习案例(三):未来销售预测
    禁止ie自动跳转edge
    java 工程管理系统源码+项目说明+功能描述+前后端分离 + 二次开发
    Adobe Illustrator 2021 下载及安装教程
  • 原文地址:https://blog.csdn.net/John_Lenon/article/details/127648004