classification
Title: tkinter.BooleanVar.get() behavior and docstring disagree
Type: behavior Stage: resolved
Components: Tkinter Versions: Python 3.5, Python 3.4, Python 2.7
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: serhiy.storchaka Nosy List: asvetlov, crickert, gpolo, klappnase, mark, martin.panter, python-dev, roger.serwy, serhiy.storchaka, terry.reedy, zach.ware
Priority: normal Keywords: patch

Created on 2012-06-22 09:27 by mark, last changed 2015-04-04 19:21 by serhiy.storchaka. This issue is now closed.

Files
File name Uploaded Description Edit
tkinter_getboolean.patch serhiy.storchaka, 2014-07-08 07:54 review
Messages (18)
msg163387 - (view) Author: Mark Summerfield (mark) * Date: 2012-06-22 09:27
Python 3.2.2 (default, Jun  4 2012, 11:15:16) 
[GCC 4.4.5] on linux2
Type "copyright", "credits" or "license()" for more information.
>>> from tkinter import *
>>> help(BooleanVar.get)
Help on function get in module tkinter:

get(self)
    Return the value of the variable as a bool.

On my system it actually returns an int. (I wish it did return a bool though.)
msg163553 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2012-06-23 08:51
The bug is the mismatch between doc and behavior. Unless someone can explain why the seemingly reasonable docstring is wrong, I would consider changing the behavior a possible fix. Can you add minimal test code that gives you an int? I should check windows and someone should check 2.7, doc and behavior.
msg163644 - (view) Author: Mark Summerfield (mark) * Date: 2012-06-23 16:55
Did you mean formal test code? Or just an example like this:

from tkinter import *
tk = Tk()
bv = BooleanVar()
print(bv.get(), type(bv.get()))
bv.set(True)
print(bv.get(), type(bv.get()))
bv.set(False)
print(bv.get(), type(bv.get()))

### output ###
0 <class 'int'>
1 <class 'int'>
0 <class 'int'>
msg163686 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2012-06-23 22:06
Just that, which I used to verify with 2.7.3 and 3.3.0a4 in Win7 and do some more experiments:
bv.set/get happily accept *and return* 2, -1, 
bv.set(2.2) 'works'
bv.get() then raises TypeError: must be str, not float

This will annoy you ;-) bv.set('1'); bv.get() returns True, '0'=>False
most other string inputs raise ValueError: invalid literal for getboolean() (but see below).

Doc: "There are many useful subclasses of Variable already defined: StringVar, IntVar, DoubleVar, and BooleanVar."

Looking into tkinter.__init__, *all* subclasses just inherit Variable.set(self, value): return self._tk.globalsetvar(self._name, value). So there is no input validation or conversion. I wonder is here should be?

It appears that anything can be set to anything. I was thinking that everything was converted to a string, and the error message for 2.2 above suggests that, but the (surprising to me) different behavior of 1 and '1' says that is not right. So does iv=IntVar(); iv.set(2.2); iv.get() returning 2.

Variable.get(self) returns self._tk.globalgetvar(self._name). String/Int/Double/Var call str/int/float as appropriate. BooleanVar calls tk.getboolean which is supposed to "Convert true and false to integer values 1 and 0.". Well, it should not do that and does not do that for tcl false and true, as with '0' and '1'. Further checking shows that 'no', 'false', 'yes', 'true' or any prefix thereof return False or True as desired.

So getboolean was partially changed after the introduction of bool to return bool instead int for tcl booleans, but it was not changed to convert ints to bool. And if one inputs a Python bool, it somehow gets converted to an int instead of being returned as is. I see three possible fixes, but I do not know enough of tk/inter usage to choose.

1) add BooleanVar.set to convert Python bool to tcl bool. Other python objects could be converted, except that some people might be using python strings that become tcl bools (as above) which come back as python bools.

2. change getboolean to not convert python bool (if that is where is happens) and do convert int to bool (just 0,1 or all ints?).

3. change BooleanVar.get to convert 0/1 (or all returns?) to False/True.
msg163747 - (view) Author: Mark Summerfield (mark) * Date: 2012-06-24 07:36
I think that BooleanVar.set(x) should do bool(x) on its argument (and raise an exception if this isn't valid) and BooleanVar.get() should return a bool. Similarly I think that IntVar.set(x) should do int(x) and IntVar.get() should return an int, and that DoubleVar.set(x) should do float(x) and should return a float.

I will mention this issue on tkinter-discuss and encourage people to comment.
msg163748 - (view) Author: Mark Summerfield (mark) * Date: 2012-06-24 07:41
Oh, and I forgot to say that I think StringVar.set(x) should do str(x) and StringVar.get() should return a str.
msg163770 - (view) Author: klappnase (klappnase) * Date: 2012-06-24 11:19
The behavior of Tkinter when handling boolean values appears to be a bit erratic at times, not only BooleanVar.get() returns 0/1 instead of True/False (e.g. see the thread at tkinter-discuss: http://mail.python.org/pipermail/tkinter-discuss/2012-June/003176.html ). This is apparently because tk itself uses the strings '0' and '1' as boolean values, so behind the scenes Tkinter has to convert any bool into a string when setting a value and the string back into a bool when retrieving it, and it seems like most of the Tkinter code (and docstrings) was written in the good ol' days when Python used 0 / 1 itself.
Now when it comes to change Tkinter's behavior I'd tend to be careful. For example BooleanVar.get() could easily be changed to always return True/False. But imho this change would only make sense if you add strict input value checking for set() either, otherwise we just change one incoherency with another. But this would mean that we might end up breaking a lot of working code, for what seems to me a primarily aesthetic reason.

And of course, I don't see much use in isolated changes of the behavior of one function here and another method there. I feel that if the behavior of Tkinter concerning bool values was to be changed, there should at least be a thorough change of *any* Tkinter method that gets / sets boolean values so we at least end up with an overall coherent solution.
But again, this looks like quite a lot of work, will break running code and add no significant benefit besides a cosmetic improvement.
So my vote goes to accepting that 0 and 1 are ok boolean values in Tkinter and changing no more than misleading docstrings, as in this example of BooleanVar.get():

    def get(self):
        """Return the value of the variable as 1 (True) or 0 (False)."""
        return self._tk.getboolean(self._tk.globalgetvar(self._name))
msg163824 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2012-06-24 19:25
Mark: "Variable.get(self) returns self._tk.globalgetvar(self._name). String/Int/Double/Var call str/int/float as appropriate." was meant to say that String/Int/Double/Var.get(self) already call str/int/float on the result of self._tk.globalgetvar(self._name) before returning it. BooleanVar.get does not call bool, but instead calls self._tk.getboolean, which fails to always return a boolean.

Klappnase: I disagree, somehow. bool is a subclass of int, so that False, True are arithmetically indistinguishable from 0, 1. The main difference is on display and 'type(x) = int/bool' comparisons (which should be isinstance()). BooleanVar.get already returns False, True for the tcl boolean values '0', '1' set by tk rather than the user (which I expect should be the usual case for retrieval). So a Python/tkinter program has to be prepared to get proper booleans anyway. Since the purpose of Variables is to synchronize values between user code and tk, TypeVar().set(x).get() should be x when has the proper type. That is now true for everything but bool/Boolean.

I do wonder whether not converting or rejecting bad inputs to .set could cause problems with tk, but maybe some of that is handled later (and silently? if so bad) within _tkinter. I could be persuaded that a behavior fix should only be applied to 3.3.
msg163905 - (view) Author: Mark Summerfield (mark) * Date: 2012-06-25 06:36
How about a compromise? Deprecate (but keep BooleanVar) and add BoolVar with proper True/False behavior to match the other *Vars?
msg163923 - (view) Author: klappnase (klappnase) * Date: 2012-06-25 10:16
I agree that get() should better return a boolean in any case, however the bug then is not one of BooleanVar but rather one of tk.getboolean(). I did some experimenting with get() and it seems that there are three possibilities: getboolean() returns True/False as it should when the variable value was previously set() to anything Tcl accepts as boolean value (like 'yes'/'no', '0'/'1', 'on'/'off', 'true'/'false'). If something which is not a proper Tcl-bool was passed to set() before, getboolean will produce a TclError or TypeError.
If set() got 0/1 (as integers) or True/False before, getboolean() will return 0/1 and this appears to me to be the only bug here. But this is not at all a sole BooleanVar bug, e.g.:

>>> root=Tk()
>>> root.tk_strictMotif()
0
>>> root.tk_strictMotif(1)
1

You will find this everywhere in Tkinter where getboolean() is used, so I think that rather this should be fixed in _tkinter.c than applying a workaround to BooleanVar.get() and leaving all other uses of getboolean() as they are; even worse, if BooleanVar.set() was changed to accept only True/False and getboolean() left as it is, you could not pass the return value of getboolean() any longer to BooleanVar.get() which would be really bad.

As far as set() is concerned, it should at least allow everything that Tcl accepts as boolean, according to http://wiki.tcl.tk/16235 these are at least the above mentioned 'yes'/'no', '0'/'1', 'on'/'off', 'true'/'false' (btw. all of these seem to be ok even case insensitive on the tcl side) plus of course 0/1 and True/False. Otherwise it might break existing code. 

Terry:
"Since the purpose of Variables is to synchronize values between user code and tk, TypeVar().set(x).get() should be x when has the proper type. That is now true for everything but bool/Boolean."
But when the input type is not proper you can do e.g.:

>>> d = DoubleVar()
>>> d.set('1.1')
>>> d.get()
1.1
>>> i = IntVar()
>>> i.set(True)
>>> i.get()
1
>>> 

I think the Variable classes can also be considered convenience objects that save the application programmer a lot of headaches when they have to convert Python objects into Tcl which usually expects strings, so I think there is nothing wrong with set() accepting "improper" values.
msg163924 - (view) Author: klappnase (klappnase) * Date: 2012-06-25 10:19
"...you could not pass the return value of getboolean() any longer to BooleanVar.get() which would be really bad."

Ooops, typo, should be BooleanVar.set() of course.
msg166494 - (view) Author: Christian Rickert (crickert) Date: 2012-07-26 16:53
Noticed this odd behaviour with BooleanVar.get() as well:

class MyCheckbutton(Checkbutton):
	def __init__(self, parent, **options):
		Checkbutton.__init__(self, parent, **options)
		self.var = BooleanVar()
		self.configure(indicatoron=False, command=self.cb, variable=self.var)
		print(self.var.get)	# "<bound method BooleanVar.get of <tkinter.BooleanVar object at 0x245c310>>"
		print(self.var.get())	# "0"
	def cb(self, *events): # button callback (manual toggle)
		print(self.var.get)	# <bound method BooleanVar.get of <tkinter.BooleanVar object at 0x245c310>>
		print(self.var.get())	# True

Python 3.2.3 (default, May  3 2012, 15:51:42) 
[GCC 4.6.3] on linux2
msg175197 - (view) Author: Zachary Ware (zach.ware) * (Python committer) Date: 2012-11-08 22:10
I don't think 2.7 and 3.3 were meant to be removed from this one and we're now in 3.4 time.
msg222482 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2014-07-07 17:31
Serhiy, what do you think is the proper disposition of this issue *now*?
msg222520 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2014-07-07 21:14
I have no strong opinion. Definitely getboolean() (and other getXXX) is not consistent. The question is what should getboolean (and BooleanVar.get) always return, bool or int? Note that since 8.6 Tcl doesn't use special boolean type internally and use integers 0 and 1 to represent boolean values. So any boolean values returned from Tk will be converted by Tkinter to 0/1.
msg222549 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2014-07-08 07:54
Here is a patch.

1) getboolean() and BooleanVar.get() now always return bool. Wrapping the result of getboolean() into bool() is not more needed.

2) getboolean() and BooleanVar.get() now works with Tcl_Obj. This makes Tkinter more robust against future Tcl/Tk changes.

3) An exception is raised if an argument to BooleanVar.set() couldn't be considered as boolean value. An argument is converted to canonized 0/1 values. This makes errors to be exposed earlier.

Similar changes should be made for getint() and getdouble().
msg239310 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-03-26 06:59
What are your thoughts about the patch Terry?
msg240056 - (view) Author: Roundup Robot (python-dev) Date: 2015-04-04 09:49
New changeset dedf481ec2be by Serhiy Storchaka in branch '2.7':
Issue #15133: _tkinter.tkapp.getboolean() now supports long and Tcl_Obj and
https://hg.python.org/cpython/rev/dedf481ec2be

New changeset 117f45749359 by Serhiy Storchaka in branch '3.4':
Issue #15133: _tkinter.tkapp.getboolean() now supports Tcl_Obj and always
https://hg.python.org/cpython/rev/117f45749359

New changeset 38747f32fa7b by Serhiy Storchaka in branch 'default':
Issue #15133: _tkinter.tkapp.getboolean() now supports Tcl_Obj and always
https://hg.python.org/cpython/rev/38747f32fa7b
History
Date User Action Args
2015-04-04 19:21:22serhiy.storchakasetstatus: open -> closed
resolution: fixed
stage: patch review -> resolved
2015-04-04 09:49:19python-devsetnosy: + python-dev
messages: + msg240056
2015-03-26 06:59:32serhiy.storchakasetassignee: serhiy.storchaka
2015-03-26 06:59:20serhiy.storchakasetmessages: + msg239310
2015-03-20 03:00:40martin.pantersetnosy: + martin.panter
2014-07-08 07:54:39serhiy.storchakasetfiles: + tkinter_getboolean.patch
keywords: + patch
messages: + msg222549

stage: patch review
2014-07-07 21:14:40serhiy.storchakasetmessages: + msg222520
versions: + Python 3.5
2014-07-07 17:31:40terry.reedysetnosy: + serhiy.storchaka

messages: + msg222482
versions: - Python 3.2, Python 3.3
2012-11-08 22:10:22zach.waresetnosy: + zach.ware

messages: + msg175197
versions: + Python 2.7, Python 3.3, Python 3.4
2012-07-26 16:53:25crickertsetnosy: + crickert

messages: + msg166494
versions: - Python 2.7, Python 3.3
2012-07-22 11:37:52asvetlovsetnosy: + asvetlov
2012-06-25 10:19:12klappnasesetmessages: + msg163924
2012-06-25 10:16:50klappnasesetmessages: + msg163923
2012-06-25 06:36:05marksetmessages: + msg163905
2012-06-24 19:25:35terry.reedysetmessages: + msg163824
2012-06-24 11:19:09klappnasesetnosy: + klappnase
messages: + msg163770
2012-06-24 07:41:26marksetmessages: + msg163748
2012-06-24 07:36:42marksetmessages: + msg163747
2012-06-23 22:06:45terry.reedysetmessages: + msg163686
2012-06-23 16:55:19marksetmessages: + msg163644
2012-06-23 08:51:53terry.reedysetnosy: + gpolo, terry.reedy, roger.serwy
title: tkinter.BooleanVar.get() docstring is wrong -> tkinter.BooleanVar.get() behavior and docstring disagree
messages: + msg163553

versions: + Python 2.7, Python 3.3
type: behavior
2012-06-22 09:27:46markcreate