Bugs item #632323, was opened at 2002-11-01 19:39 Message generated for change (Comment added) made by facundobatista 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: Open 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 ([EMAIL PROTECTED]) (gcc version 2.96 20000731 (Red Hat Linux 7.3 2.96-110)) #1 Thu Apr 18 07:37:53 EDT 2002 ---------------------------------------------------------------------- Comment By: Facundo Batista (facundobatista) Date: 2004-12-27 23:02 Message: 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 ---------------------------------------------------------------------- Comment By: Martin v. Löwis (loewis) Date: 2002-12-11 08:59 Message: 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. ---------------------------------------------------------------------- Comment By: Fredrik Lundh (effbot) Date: 2002-12-11 06:33 Message: 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." ---------------------------------------------------------------------- Comment By: Internet Discovery (idiscovery) Date: 2002-12-11 06:01 Message: 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 ---------------------------------------------------------------------- Comment By: Fredrik Lundh (effbot) Date: 2002-12-08 10:30 Message: 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> ---------------------------------------------------------------------- Comment By: Martin v. Löwis (loewis) Date: 2002-12-08 09:37 Message: 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. ---------------------------------------------------------------------- Comment By: L. Peter Deutsch (lpd) Date: 2002-12-07 16:57 Message: 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. ---------------------------------------------------------------------- Comment By: Martin v. Löwis (loewis) Date: 2002-12-07 16:40 Message: 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. ---------------------------------------------------------------------- Comment By: L. Peter Deutsch (lpd) Date: 2002-12-07 15:36 Message: 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. ---------------------------------------------------------------------- Comment By: Martin v. Löwis (loewis) Date: 2002-11-13 09: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 18: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 07: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 06: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 19: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 18: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 04: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 16: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 02: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 01: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 _______________________________________________ Python-bugs-list mailing list Unsubscribe: http://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com