python / cpython

The Python programming language
https://www.python.org
Other
63.46k stars 30.39k forks source link

setting a locale that uses comma as decimal separator breaks tkinter.DoubleVar #84008

Open df194108-eece-4593-bf39-e3b1b0ac3325 opened 4 years ago

df194108-eece-4593-bf39-e3b1b0ac3325 commented 4 years ago
BPO 39827
Nosy @serhiy-storchaka, @thawn, @E-Paine, @akulakov
Files
  • test_doublevar.py: minimal code example
  • 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: ```python assignee = None closed_at = None created_at = labels = ['3.7', 'type-bug', 'expert-tkinter'] title = 'setting a locale that uses comma as decimal separator breaks tkinter.DoubleVar' updated_at = user = 'https://github.com/thawn' ``` bugs.python.org fields: ```python activity = actor = 'serhiy.storchaka' assignee = 'none' closed = False closed_date = None closer = None components = ['Tkinter'] creation = creator = 'thawn' dependencies = [] files = ['48943'] hgrepos = [] issue_num = 39827 keywords = [] message_count = 5.0 messages = ['363184', '363371', '371394', '400005', '400023'] nosy_count = 4.0 nosy_names = ['serhiy.storchaka', 'thawn', 'epaine', 'andrei.avk'] pr_nums = [] priority = 'normal' resolution = None stage = None status = 'open' superseder = None type = 'behavior' url = 'https://bugs.python.org/issue39827' versions = ['Python 3.7'] ```

    df194108-eece-4593-bf39-e3b1b0ac3325 commented 4 years ago

    This issue occurs when a locale is set that uses comma as decimal separator (e.g. locale.setlocale(locale.LC_NUMERIC, 'de_DE.utf8')). I have a tkinter.Spinbox with increment=0.1 connected to a tkinter.DoubleVar. When I change the value of the Spinbox using the arrow buttons and subsequently try to read out the variable with tkinter.DoubleVar.get(), my code throws the follwoing error: _tkinter.TclError: expected floating-point number but got "0,1".

    Here is a minimal code example:

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

    import tkinter
    import locale
    
    locale.setlocale(locale.LC_NUMERIC, 'de_DE.utf8')
    
    class TestDoubleVar():
        def __init__(self):
            root = tkinter.Tk()
            self.var = tkinter.DoubleVar()
            self.var.set(0.8)
            number = tkinter.Spinbox(
                root,
                from_=0, to=1, increment=0.1,
                textvariable=self.var,
                command=self.update,
                width=4
            )
            number.pack(side=tkinter.LEFT)
            root.mainloop()
    
        def update(self, *args):
            print(float(self.var.get()))
    
    if __name__ == '__main__':
        TestDoubleVar()

    Actual result: the code throws an error

    Expected result: the code should print the values of the DoubleVar even with a locale set that uses comma as the decimal separator.

    n.b. the problem also occurs with tkinter.Scale

    df194108-eece-4593-bf39-e3b1b0ac3325 commented 4 years ago

    I just found the following code in lines 314-320 of tkinter/ttk.py, which are clearly not localization-aware:

    def _to_number(x):
        if isinstance(x, str):
            if '.' in x:
                x = float(x)
            else:
                x = int(x)
        return x

    I'll keep looking for similar stuff and add a pull request once I think I have nailed down the issue I'll look for something similar in

    919475b3-36a2-4cd7-997c-9c38f05f93c7 commented 4 years ago

    Is this a problem exclusive to tkinter? I ran the following code, and it appears to be an issue with the builtin 'float' method:

    >>> import locale
    >>> locale.setlocale(locale.LC_NUMERIC, 'de_DE.utf8')
    'de_DE.utf8'
    >>> float("1,2")
    ValueError: could not convert string to float: '1,2'

    I have somehow managed to corrupt my locales so I would appreciate if you could try on your machine as well (is it just me?!).

    akulakov commented 3 years ago

    I wrote a longer explanation but BPO and Chrome ate it.

    The issue is that DoubleVar is not locale aware. I don't know what should be the actual fix but the following may be a useful workaround (It works on my system):

    import tkinter
    import locale
    import sys
    
    locale.setlocale(locale.LC_NUMERIC, 'de_DE')
    class Var(tkinter.DoubleVar):
        def get(self):
            return locale.atof(self._tk.globalgetvar(self._name))
    
    class TestDoubleVar():
        def __init__(self):
            root = tkinter.Tk()
            self.var = Var()
            self.var.set(0.8)
            number = tkinter.Spinbox(
                root,
                from_=0, to=1, increment=0.1,
                textvariable=self.var,
                command=self.update,
                width=4
            )
            number.pack(side=tkinter.LEFT)
            root.mainloop()
        def update(self, *args):
            print(self.var.get())
    if __name__ == '__main__':
        TestDoubleVar()
    serhiy-storchaka commented 3 years ago

    See also bpo-12558 which exposes the other side of the same issue.

    Las time I looked at it, it was inherently a Tcl issue. In some places it uses locale-dependent formatting of floating point numbers, but locale-unaware parsing, or vice versa.

    slawomirmarczynski commented 1 year ago

    Setting the locale results in buggy behaviour of spin boxes with non-integer content::

        locale.setlocale(locale.LC_ALL, 'pl_PL')  # any locale with the comma as a decimal separator
        variable = tk.DoubleVar(0.5)
        spinbox = ttk.Spinbox(self, from_=0, to=10, increment=0.1, format = "%.1f", textvariable = variable)

    The first click on the up/down arrows works correctly (?), any futher manipulation lock the spin box between 0,0 and 0,1 (comma as the decimal separator).

    The simple workaround:

        locale.setlocale(locale.LC_ALL, 'pl_PL')  # any locale with the comma as a decimal separator
        locale.setlocale(locale.LC_NUMERIC, 'en_US')  # the dot is the decimal separator again
    

    makes spin boxes fully useable, but... this is no solution when l18n must be used.

    The suggested subclassing DoubleVar:

    class Var(tkinter.DoubleVar):
        def get(self):
            return locale.atof(self._tk.globalgetvar(self._name))
    

    does not work in my program (CPython 3.9).