[Python-bugs-list] [ python-Bugs-632323 ] Tkinter: BitmapImage vanishes if not stored in non-local var

noreply@sourceforge.net noreply@sourceforge.net
Wed, 13 Nov 2002 04:04:26 -0800


Bugs item #632323, was opened at 2002-11-01 23:39
You can respond by visiting: 
https://sourceforge.net/tracker/?func=detail&atid=105470&aid=632323&group_id=5470

Category: Tkinter
Group: Python 2.2.2
Status: Closed
Resolution: Invalid
Priority: 5
Submitted By: L. Peter Deutsch (lpd)
Assigned to: Nobody/Anonymous (nobody)
Summary: Tkinter: BitmapImage vanishes if not stored in non-local var

Initial Comment:
The following program incorrectly produces a blank canvas:

#!/usr/bin/env python

from Tkinter import *

class Me(Canvas):

    def init1(self):
        return BitmapImage(foreground = '#ff0000',
                           background = '#00ff00',
                           data = """\
#define t_width 8
#define t_height 8
static unsigned char t_bits[] = {
   0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x3c, 0x3c};
""",

                           maskdata = """\
#define t_width 8
#define t_height 8
static unsigned char t_bits[] = {
   0x18, 0x3c, 0x7e, 0xff, 0xff, 0x7e, 0x3c, 0x18};
""")

    def init2(self, image):
        self.create_image(32, 32, anchor = NW, image =
image)

self = Me()
self.pack(expand = 1, fill = BOTH)
self.init2(self.init1())
#img = self.init1()
#self.init2(img)
self.mainloop()

----------------

However, the following program correctly draws a small
red-and-green icon:

#!/usr/bin/env python

from Tkinter import *

class Me(Canvas):

    def init1(self):
        return BitmapImage(foreground = '#ff0000',
                           background = '#00ff00',
                           data = """\
#define t_width 8
#define t_height 8
static unsigned char t_bits[] = {
   0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x3c, 0x3c};
""",

                           maskdata = """\
#define t_width 8
#define t_height 8
static unsigned char t_bits[] = {
   0x18, 0x3c, 0x7e, 0xff, 0xff, 0x7e, 0x3c, 0x18};
""")

    def init2(self, image):
        self.create_image(32, 32, anchor = NW, image =
image)

self = Me()
self.pack(expand = 1, fill = BOTH)
#self.init2(self.init1())
img = self.init1()
self.init2(img)
self.mainloop()

----------------

The two programs are identical except that one of them
assigns the BitmapImage to a variable rather than
passing it directly as an argument.

Python 2.2.1, OS = Linux version 2.4.18-3
(bhcompile@daffy.perf.redhat.com) (gcc version 2.96
20000731 (Red Hat Linux 7.3 2.96-110)) #1 Thu Apr 18
07:37:53 EDT 2002


----------------------------------------------------------------------

>Comment By: Martin v. Löwis (loewis)
Date: 2002-11-13 13:04

Message:
Logged In: YES 
user_id=21627

Peter, I have considered these reasons, and believe they
don't apply.

1) Dropping the last reference to a Variable (IntVar,
StringVar) also causes the variable to be unset. More
importantly, changing the behaviour now would cause
backwards incompatibilities: Existing applications rely on
not having to release images explicitly.

2) While this may be true in general and for the Python
language, it is not the case for CPython and Tkinter.Image
specifically. If there are no references to the image
anymore, it is released immediately, in CPython, withiyt
waiting for garbage collection to be invoked.

If you need a way to appeal this decision, you will have to
write a PEP. Describe the proposed change and implementation
strategy (explaining in detail how your approach solves the
backwards compatibility issue), then discuss this proposed
change with the Python community.

----------------------------------------------------------------------

Comment By: L. Peter Deutsch (lpd)
Date: 2002-11-12 22:58

Message:
Logged In: YES 
user_id=8861

I disagree strongly with the resolution, for two reasons.

1) No other commonly used entity in the Tkinter API has this
counter-intuitive behavior. Widgets do not disappear from
the screen if Python holds no references to them. Text
strings do not disappear from the screen if Python holds no
references to the string object or the font object. (I don't
know how this is accomplished.)

2) Python does not, and cannot, guarantee that an object
will be finalized and freed immediately when there are no
longer any references to it: if the object must be reclaimed
by garbage collection, an unpredictable amount of time may
elapse. Therefore, in my opinion, it is very undesirable to
use object finalization in a way that directly affects the
user interface.

Please consider changing Tkinter so that it handles Image
objects the same way that it apparently handles widgets,
fonts, strings, etc.


----------------------------------------------------------------------

Comment By: Martin v. Löwis (loewis)
Date: 2002-11-07 11:11

Message:
Logged In: YES 
user_id=21627

It is now in the library section. If you think it also ought
to be in the Image class, please contribute a patch.
However, I think anybody looking at the Image class code
could not fail to notice the image delete.

I agree that the behaviour is counter-intuitive, but I
disagree that automatic addition of a reference would be a
solution:
1. It would break backwards-compatibility. A number of text
books explicitly
    mention this issue, and applications make use of this
property, relying on
    the fact that you can drop the last reference to the
image and thus release
    the associated memory.

2. Python cannot possibly track all uses of the command. For
example, you
    could put the image into a StrVar, and then expect to
use the StrVar as the
    value for an image= attribute.

So in short, I think educating users is the best we can do,
until Tk provides better mechanisms.

----------------------------------------------------------------------

Comment By: Internet Discovery (idiscovery)
Date: 2002-11-07 10:22

Message:
Logged In: YES 
user_id=33229

> When the BitmapImage object is no longer referenced, it is
> finalized; finalization causes "image delete" to be
> invoked.

Martin, thanks for the explanation. The behaviour is
counterintuitive for a Tk wrapper, because in Tk images are
eternal unless explicitly deleted. They are not GC'd when
they are unreferenced, and that's their documented behaviour.

IThe documentation I was refering to was the Image class 
documentation in Tkinter.py - it makes no mention of this,
and as a rule in Python, I didn't think you had to keep a
global reference to everything you pass to any function if
you want it to be still alive when the function uses it.
Perhaps the Label/Canvas/Button instances should keep a
reference to it, which would be deleted when the  instance
is deleted?

I know it's not in the Library Reference, as I contributed
the Tkinter section, but I think it should be clear in
Tkinter.py.

----------------------------------------------------------------------

Comment By: Martin v. Löwis (loewis)
Date: 2002-11-05 23:13

Message:
Logged In: YES 
user_id=21627

Mentioning "the docs" is somewhat funny when it comes to
Tkinter: there was no documentation on images in the first
place. I've added a new section on this in tkinter.tex 1.17.

I've also added a Tk wishlist item at

http://sourceforge.net/tracker/?func=detail&aid=633300&group_id=12997&atid=362997

If they ever act on this, we can improve Tkinter in this
respect.

----------------------------------------------------------------------

Comment By: Guido van Rossum (gvanrossum)
Date: 2002-11-05 22:23

Message:
Logged In: YES 
user_id=6380

Let's close this as Not A Bug.

Maybe it needs to be fixed in the docs?

----------------------------------------------------------------------

Comment By: Martin v. Löwis (loewis)
Date: 2002-11-04 08:26

Message:
Logged In: YES 
user_id=21627

When the BitmapImage object is no longer referenced, it is
finalized; finalization causes "image delete" to be invoked.

Tcl does not keep a reference to the image object; if you
don't yourself, nobody does. In turn, the image object goes
away right after being created.

The right approach would be for Tcl to somehow keep a
reference to the Python object, but I think this is
unimplementable.

----------------------------------------------------------------------

Comment By: L. Peter Deutsch (lpd)
Date: 2002-11-03 20:42

Message:
Logged In: YES 
user_id=8861

The bug does *not* manifest with the following definition
for init():

    def init(self):
        self.img = self.init1()
        self.init2(self.img)

I think this is even stronger evidence that we're seeing a
reference counting / garbage collection / finalization (?)
problem.


----------------------------------------------------------------------

Comment By: Internet Discovery (idiscovery)
Date: 2002-11-03 06:14

Message:
Logged In: YES 
user_id=33229

This may be the same as what I'm seeing in Demo/tix/tixwidgets.py
Look for image1 in tixwidgets.py: I put the following comment in the code:

    # This image is not showing up under Python unless it is set to a
    # global variable - no problem under Tcl. I assume it is being garbage
    # collected some how, even though the tcl command 'image names' shows
    # that as far as Tcl is concerned, the image exists and is called pyimage1.

Can anyone explain to me what's going on? IMHO, either this is a Tkinter bug, 
or a documentation bug because the documentation does not explain to me why
this image1 has to be global, or am I missing something here.


----------------------------------------------------------------------

Comment By: L. Peter Deutsch (lpd)
Date: 2002-11-02 05:32

Message:
Logged In: YES 
user_id=8861

Comment #1: This bug is still there in Python 2.2.2.

Comment #2: Using a variable isn't the whole story. The bug
also manifests if I define:

    def init(self):
        img = self.init1()
        self.init2(img)

and then have the calling program invoke self.init().


----------------------------------------------------------------------

You can respond by visiting: 
https://sourceforge.net/tracker/?func=detail&atid=105470&aid=632323&group_id=5470