This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Tkinter: BitmapImage vanishes if not stored in non-local var
Type: Stage:
Components: Tkinter Versions: Python 2.3
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: effbot, facundobatista, gvanrossum, idiscovery, loewis, lpd
Priority: normal Keywords:

Created on 2002-11-01 22:39 by lpd, last changed 2022-04-10 16:05 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
image.txt loewis, 2002-12-08 12:37
Messages (23)
msg13020 - (view) Author: L. Peter Deutsch (lpd) Date: 2002-11-01 22:39
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
msg13021 - (view) Author: L. Peter Deutsch (lpd) Date: 2002-11-02 04:32
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().
msg13022 - (view) Author: Internet Discovery (idiscovery) Date: 2002-11-03 05:14
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.
msg13023 - (view) Author: L. Peter Deutsch (lpd) Date: 2002-11-03 19:42
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.
msg13024 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2002-11-04 07:26
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.
msg13025 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2002-11-05 21:23
Logged In: YES 
user_id=6380

Let's close this as Not A Bug.

Maybe it needs to be fixed in the docs?
msg13026 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2002-11-05 22:13
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.
msg13027 - (view) Author: Internet Discovery (idiscovery) Date: 2002-11-07 09:22
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.
msg13028 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2002-11-07 10:11
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.
msg13029 - (view) Author: L. Peter Deutsch (lpd) Date: 2002-11-12 21:58
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.
msg13030 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2002-11-13 12:04
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.
msg13031 - (view) Author: L. Peter Deutsch (lpd) Date: 2002-12-07 18:36
Logged In: YES 
user_id=8861

Sorry, I don't agree entirely with either of the responses.

1) I should have said no commonly used *on-screen* entity
has this behavior. Backward compatibility can be handled
easily by creating new subclasses of Image that have the
proposed behavior. (I think I also disagree with the
behavior for Variables, since unsetting a variable can also
cause the screen appearance to change, but I haven't thought
about it carefully enough to have a strong opinion.)

2) This is true only if the last reference to the image is
the one being held by CPython. If a circular structure
refers to an image, the last reference will be deleted by
the garbage collector, not by CPython, so the image will not
disappear until garbage collection occurs.

I understand the reasoning, and it's not worth it to me to
go through the PEP process; I just don't agree.
msg13032 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2002-12-07 19:40
Logged In: YES 
user_id=21627

Yes, backwards compatibility can be preserved by new
classes, or by an additional constructor argument (I'd
personally prefer the latter). However, I doubt the
usefulness of such a new construct: nobody would use it
unless they are aware of the issue, and if they are aware of
the issue, they can easily solve the problem in a different way.

Circular references are a red herring here; no image will
ever exist in a circle, since image objects don't refer to
other objects.
msg13033 - (view) Author: L. Peter Deutsch (lpd) Date: 2002-12-07 19:57
Logged In: YES 
user_id=8861

Sorry, my previous comment regarding garbage collection
wasn't clear. Consider the following situation: object X
refers to object Y and to an image, and object Y refers to
object X. The image will not be freed until the garbage
collector frees objects X and Y.

We worked through almost precisely this set of issues in
Smalltalk systems at Xerox PARC, and later at ParcPlace
Systems, starting in the early 1970s. Smalltalk, like
Python, started out using only reference counting, and added
garbage collection much later. However, Smalltalk wasn't
trying to interact with a foreign memory management system
at arm's length, as Python is with Tk: in my opinion, it's
very unfortunate that Tkinter makes the existence of the two
separate memory management systems visible to the Python
programmer, with consequences like the ones discussed here.
However, I have to agree with Martin Loewis that it's too
late to change this situation, at least without extending
the existing API.
msg13034 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2002-12-08 12:37
Logged In: YES 
user_id=21627

As there appears to be agreement that the current behaviour
cannot be changed, I'd like to propose the attached
image.txt as a patch.

With this patch, people who want to explicitly delete images
need to pass a delete=False option to the image ctor, i.e.

i = Tkinter.PhotoImage(file="fn", delete=0)

Then, when they want to delete the image, they need to call

i.delete()

If that is an acceptable solution, I'll apply it to Python
2.3. In the case of the original report, the image would
become garbage eventually, since the only reference to
delete it was a local variable, which is lost.
msg13035 - (view) Author: Fredrik Lundh (effbot) * (Python committer) Date: 2002-12-08 13:30
Logged In: YES 
user_id=38376

MvL wrote: As there appears to be agreement that the
current behaviour cannot be changed, I'd like to propose
the attached image.txt as a patch.

+1 from here.

I should probably write an essay some day on why the
current behaviour is the only one that makes sense if
you consider all common use cases -- if you don't do it
this way, you either end up with leaks, code that works
only by accident, or code that is a lot more convoluted
than the simple assignment that is necessary to work
around this problem in real life:

     photo = PhotoImage(...)
     widget = Label(master, photo=photo)
     widget._photo = photo # keep a reference

martin has already covered most other issues.

</F>
msg13036 - (view) Author: Internet Discovery (idiscovery) Date: 2002-12-11 09:01
Logged In: YES 
user_id=33229

What's wrong with simply wrapping what Tk does with an image_create
method that returns a string and an image_delete method that accepts a string?
It's consistent with Tk usage. You can document the proper usage of image_delete
and leave the Image class for backwards compatability. Something like:

    def image_create(self, imgtype, cnf={}, master=None, **kw):
        if not master:
            master = Tkinter._default_root
            if not master:
                raise RuntimeError, 'Too early to create image'
        if kw and cnf: cnf = _cnfmerge((cnf, kw))
        elif kw: cnf = kw
        options = ()
        for k, v in cnf.items():
            if callable(v):
                v = self._register(v)
            options = options + ('-'+k, v)
        return master.tk.call(('image', 'create', imgtype,) + options)
    def image_delete(self, imgname):
        try:
            self.tk.call('image', 'delete', imgname)
        except TclError:
            # May happen if the root was destroyed
            pass

msg13037 - (view) Author: Fredrik Lundh (effbot) * (Python committer) Date: 2002-12-11 09:33
Logged In: YES 
user_id=38376

"..consider all common use cases -- if you don't do it
this way, you either end up with leaks, code that works
only by accident, or code that is a lot more convoluted
than the simple assignment that is necessary to work
around this problem in real life."
msg13038 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2002-12-11 11:59
Logged In: YES 
user_id=21627

It's inconsistent with the rest of Tkinter. Everything in
Tkinter is an object, and so are images. If you want
strings, use plain _tkinter.
msg13039 - (view) Author: Facundo Batista (facundobatista) * (Python committer) Date: 2004-12-28 02:02
Logged In: YES 
user_id=752496

Please, could you verify if this problem persists in Python 2.3.4
or 2.4?

If yes, in which version? Can you provide a test case?

If the problem is solved, from which version?

Note that if you fail to answer in one month, I'll close this bug
as "Won't fix".

Thank you! 

.    Facundo
msg13040 - (view) Author: L. Peter Deutsch (lpd) Date: 2005-01-28 03:01
Logged In: YES 
user_id=8861

SourceForge apparently failed to e-mail me a notification of
your follow-up, so I didn't find out about it until today
(exactly 1 month from your original posting), by accident.

The problem is definitely still there in 2.3.3, which is
what I currently have installed.  I'll download and test
2.3.4 and 2.4 tomorrow.  (I hope you'll give me an extra day!)

lpd
msg13041 - (view) Author: Facundo Batista (facundobatista) * (Python committer) Date: 2005-05-30 20:19
Logged In: YES 
user_id=752496

OP says the problem persists, changing it to Py2.3.

I think that or we change the resolution from "Invalid" or
close the bug...
msg60156 - (view) Author: Facundo Batista (facundobatista) * (Python committer) Date: 2008-01-19 13:05
Martin: 

I'll close this as it's marked as invalid, and nobody answered since two
years ago.

If you think that your first patch is a good solution (Fredrik agreed,
good enough for me).

If you want, reopen the bug and assign it to me, and I'll apply it and
add a few lines and an example in the docs.
History
Date User Action Args
2022-04-10 16:05:48adminsetgithub: 37398
2008-01-19 13:05:12facundobatistasetstatus: open -> closed
messages: + msg60156
2002-11-01 22:39:32lpdcreate