Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tkinter.BooleanVar.get() behavior and docstring disagree #59338

Closed
mark-summerfield mannequin opened this issue Jun 22, 2012 · 18 comments
Closed

tkinter.BooleanVar.get() behavior and docstring disagree #59338

mark-summerfield mannequin opened this issue Jun 22, 2012 · 18 comments
Assignees
Labels
topic-tkinter type-bug An unexpected behavior, bug, or error

Comments

@mark-summerfield
Copy link
Mannequin

mark-summerfield mannequin commented Jun 22, 2012

BPO 15133
Nosy @terryjreedy, @mark-summerfield, @serwy, @asvetlov, @vadmium, @zware, @serhiy-storchaka
Files
  • tkinter_getboolean.patch
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = 'https://github.com/serhiy-storchaka'
    closed_at = <Date 2015-04-04.19:21:22.228>
    created_at = <Date 2012-06-22.09:27:46.087>
    labels = ['type-bug', 'expert-tkinter']
    title = 'tkinter.BooleanVar.get() behavior and docstring disagree'
    updated_at = <Date 2015-04-04.19:21:22.227>
    user = 'https://github.com/mark-summerfield'

    bugs.python.org fields:

    activity = <Date 2015-04-04.19:21:22.227>
    actor = 'serhiy.storchaka'
    assignee = 'serhiy.storchaka'
    closed = True
    closed_date = <Date 2015-04-04.19:21:22.228>
    closer = 'serhiy.storchaka'
    components = ['Tkinter']
    creation = <Date 2012-06-22.09:27:46.087>
    creator = 'mark'
    dependencies = []
    files = ['35897']
    hgrepos = []
    issue_num = 15133
    keywords = ['patch']
    message_count = 18.0
    messages = ['163387', '163553', '163644', '163686', '163747', '163748', '163770', '163824', '163905', '163923', '163924', '166494', '175197', '222482', '222520', '222549', '239310', '240056']
    nosy_count = 11.0
    nosy_names = ['terry.reedy', 'klappnase', 'mark', 'gpolo', 'roger.serwy', 'asvetlov', 'python-dev', 'martin.panter', 'zach.ware', 'serhiy.storchaka', 'crickert']
    pr_nums = []
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue15133'
    versions = ['Python 2.7', 'Python 3.4', 'Python 3.5']

    @mark-summerfield
    Copy link
    Mannequin Author

    mark-summerfield mannequin commented Jun 22, 2012

    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.)

    @terryjreedy
    Copy link
    Member

    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.

    @terryjreedy terryjreedy changed the title tkinter.BooleanVar.get() docstring is wrong tkinter.BooleanVar.get() behavior and docstring disagree Jun 23, 2012
    @terryjreedy terryjreedy added the type-bug An unexpected behavior, bug, or error label Jun 23, 2012
    @mark-summerfield
    Copy link
    Mannequin Author

    mark-summerfield mannequin commented Jun 23, 2012

    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'>

    @terryjreedy
    Copy link
    Member

    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.
    1. 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?).

    2. change BooleanVar.get to convert 0/1 (or all returns?) to False/True.

    @mark-summerfield
    Copy link
    Mannequin Author

    mark-summerfield mannequin commented Jun 24, 2012

    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.

    @mark-summerfield
    Copy link
    Mannequin Author

    mark-summerfield mannequin commented Jun 24, 2012

    Oh, and I forgot to say that I think StringVar.set(x) should do str(x) and StringVar.get() should return a str.

    @klappnase
    Copy link
    Mannequin

    klappnase mannequin commented Jun 24, 2012

    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))

    @terryjreedy
    Copy link
    Member

    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.

    @mark-summerfield
    Copy link
    Mannequin Author

    mark-summerfield mannequin commented Jun 25, 2012

    How about a compromise? Deprecate (but keep BooleanVar) and add BoolVar with proper True/False behavior to match the other *Vars?

    @klappnase
    Copy link
    Mannequin

    klappnase mannequin commented Jun 25, 2012

    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.

    @klappnase
    Copy link
    Mannequin

    klappnase mannequin commented Jun 25, 2012

    "...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.

    @crickert
    Copy link
    Mannequin

    crickert mannequin commented Jul 26, 2012

    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

    @zware
    Copy link
    Member

    zware commented Nov 8, 2012

    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.

    @terryjreedy
    Copy link
    Member

    Serhiy, what do you think is the proper disposition of this issue *now*?

    @serhiy-storchaka
    Copy link
    Member

    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.

    @serhiy-storchaka
    Copy link
    Member

    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().

    @serhiy-storchaka
    Copy link
    Member

    What are your thoughts about the patch Terry?

    @serhiy-storchaka serhiy-storchaka self-assigned this Mar 26, 2015
    @python-dev
    Copy link
    Mannequin

    python-dev mannequin commented Apr 4, 2015

    New changeset dedf481ec2be by Serhiy Storchaka in branch '2.7':
    Issue bpo-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 bpo-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 bpo-15133: _tkinter.tkapp.getboolean() now supports Tcl_Obj and always
    https://hg.python.org/cpython/rev/38747f32fa7b

    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    topic-tkinter type-bug An unexpected behavior, bug, or error
    Projects
    None yet
    Development

    No branches or pull requests

    3 participants