What are the arguments to Tkinter variable trace method callbacks? - python

Python has classes for Tkinter variables StringVar(), BooleanVar(), etc. These all share the methods get(), set(string), and trace(mode, callback). The callback function passed as the second argument to trace(mode, callback) is passed four arguments, self, n, m, x.
For an example of a BooleanVar() these appear to be '', 'PYVAR0', 'w'.
The third argument x appears to be the mode that triggered the trace, in my case the variable was changed. However, what is the first variable that appears to be an empty string? What is the second, if I had to guess I'd say some internal name for the variable?

The first argument is the internal variable name. You can use this name as an argument to the tkinter getvar and setvar methods. If you give your variable a name (eg: StringVar(name='foo')) this will be the given name, otherwise it will be a name generated for you by tkinter (eg: PYVAR0)
If the first argument represents a list variable (highly unlikely in tkinter), the second argument will be an index into that list. If it is a scalar variable, the second argument will be the empty string.
The third argument is the operation, useful if you are using the same method for reading, writing and/or deleting the variable. This argument tells you which operation triggered the callback. It will be one of "read", "write", or "unset".
Tkinter is a python wrapper around a tcl/tk interpreter. The definitive documentation for variable traces can be found here: http://tcl.tk/man/tcl8.5/TclCmd/trace.htm#M14. Though, this only documents how the internal trace works, the tkinter wrapper sometimes massages the data.

The first argument is the name of the variable, but is not "useless" since you can set it when you declare the variable, e.g.:
someVar = IntVar(name="Name of someVar")
When you check the first argument in the trace callback it will equal "Name of someVar". Using the name to distinguish between variables, you can then bind the same handler to trace changes to any number of variables, rather than needing a separate handler for each variable.

Related

Why does button.clicked.connect gives unexpected output when i set callback for it in pyqt6? [duplicate]

Im trying to build a calculator with PyQt4 and connecting the 'clicked()' signals from the buttons doesn't work as expected.
Im creating my buttons for the numbers inside a for loop where i try to connect them afterwards.
def __init__(self):
for i in range(0,10):
self._numberButtons += [QPushButton(str(i), self)]
self.connect(self._numberButtons[i], SIGNAL('clicked()'), lambda : self._number(i))
def _number(self, x):
print(x)
When I click on the buttons all of them print out '9'.
Why is that so and how can i fix this?
This is just, how scoping, name lookup and closures are defined in Python.
Python only introduces new bindings in namespace through assignment and through parameter lists of functions. i is therefore not actually defined in the namespace of the lambda, but in the namespace of __init__(). The name lookup for i in the lambda consequently ends up in the namespace of __init__(), where i is eventually bound to 9. This is called "closure".
You can work around these admittedly not really intuitive (but well-defined) semantics by passing i as a keyword argument with default value. As said, names in parameter lists introduce new bindings in the local namespace, so i inside the lambda then becomes independent from i in .__init__():
self._numberButtons[i].clicked.connect(lambda checked, i=i: self._number(i))
UPDATE: clicked has a default checked argument that would override the value of i, so it must be added to the argument list before the keyword value.
A more readable, less magic alternative is functools.partial:
self._numberButtons[i].clicked.connect(partial(self._number, i))
I'm using new-style signal and slot syntax here simply for convenience, old style syntax works just the same.
You are creating closures. Closures really capture a variable, not the value of a variable. At the end of __init__, i is the last element of range(0, 10), i.e. 9. All the lambdas you created in this scope refer to this i and only when they are invoked, they get the value of i at the time they are at invoked (however, seperate invocations of __init__ create lambdas referring to seperate variables!).
There are two popular ways to avoid this:
Using a default parameter: lambda i=i: self._number(i). This work because default parameters bind a value at function definition time.
Defining a helper function helper = lambda i: (lambda: self._number(i)) and use helper(i) in the loop. This works because the "outer" i is evaluated at the time i is bound, and - as mentioned before - the next closure created in the next invokation of helper will refer to a different variable.
Use the Qt way, use QSignalMapper instead.

What is `mode` argument of `trace_add` method in tkinter

The documentation states:
Mode: It is one of “array”, “read”, “write”, “unset”, or a list or tuple of such strings. but I'm not sure I understand what it means.
I'm using trace_add to call a function whenever my object tk.StringVar changes value and I'm using the mode write without really understanding why.
EDIT: what I find online is only about the deprecated method trace; nothing about the method trace_add.
Using the value "write" means you want the callback to be called whenever the variable is written (ie: changed; when the set method is called).
Using the value "read" means you want the callback to be called whenever the variable is read (ie: the get method is called).
Using the value "unset" means you want the callback to be called whenever the variable is unset. In the case of tkinter, this happens when the variable is deleted.
The canonical tcl documentation for "array" says: "Invoke commandPrefix whenever the variable is accessed or modified via the array command". Tkinter doesn't directly give access to tcl's array command, so this one isn't very useful in tkinter.

I don’t understand why this won’t print, it says that title is not defined on line 5 even though I defined it in the parenthesis

I defined the problem in the title oops
I have tried so many things I can’t even write them all.
def document(title="cool", genre="fiction"):
print(title+genre)
document(title = "once upon a time ")
document(“awesome”)
document(title+genre)
I expect it to print, once upon a time awesome, cool fiction.
You defined a function which takes two arguments named title and genre. These two arguments are only accessible within your function as local variable. Since these variables aren't declared outside the function, they can not be accessed.
def document(title="cool", genre="fiction"):
print(title+genre)
#declaration of variables
title="foo"
genre="bar"
document(title, genre)
def document(title="cool", genre="fiction"):
This means that the function has two arguments, named title and genre. Each of these has a default value provided, that will be filled in if the caller does not provide them. This is not "defining title" in the way that you seem to be thinking of it. Each function has its own entirely separate set of names for things, as well as the global set of names for things.
print(title+genre)
This means that whatever values were provided, whether they were from the caller or the default values, will be concatenated and printed.
document(title = "once upon a time ")
This says to call the function and use "once upon a time " as the value for title. The value for genre is not provided, so the default value of "fiction" is used. Thus,once upon a time fiction` is printed.
document("awesome")
This says to call the function and use "awesome" as the value for the first parameter. That parameter is the title parameter, so "awesome" is used as the value for title. As before, the value for genre is still "fiction", so awesomefiction is printed.
Note that when the function runs, title is the name that is being used by the function for the string "awesome", even though you didn't say anything about title when you called the function.
document(title+genre)
This says to use whatever are the values of title and genre in the calling context, as the value for the first parameter. But there are no such defined names outside the function. The function's parameters are completely separate and have no meaning whatsoever here. You get a NameError because the names in question are not defined.
You're confusing defining a variable (e.g. title = "once upon a time") with specifying a function argument document(title="whatever"). The latter only specifies the argument passed into document() function, it doesn't define a local variable called title.
And by "inside the parenthesis" you mean "in my function call to document()"
One solution is to do the following:
title = "once upon a time " # <-- actually define a local variable
genre = "Mongolian puppetry"
document(title) # now you can use that variable in your function call
document(“awesome”)
document(title+genre) # ...and reuse that variable again

Trying to create a mediocre cookie clicker in python with tkinter, doesn't print?

So i decided to do this for a school project and am completely new to tkinter (but not new to python). But in the parts where the "cookie" values should be printed - they dont get printed.
I dont know how to properly paste code here so here's github: https://gist.github.com/avaque/a5fd47b896c8ff4e9e52d206994187ce
It looks like a mess, but thats not what matters (i think). The problem is that it doesn't print integers defined as "a" representing the cookie amount.
I started from this code: https://www.python-course.eu/tkinter_buttons.php
...and in that one the string gets printed. Is the value type a problem here?
And also is it possible for the cookie amount to be displayed in a button's text? (look at row 70-74)
The problem is that it doesn't print integers defined as "a" representing the cookie amount.
If I understand correctly, your buttonzy is supposed to print the amount when clicked, and it doing just that. The main problem is that you're not updating your a variable correctly.
slogan = tk.Button(frame, text="Cookie click!", command=clicc(a,b))
First thing: you can't directly send arguments to functions used as commands to your tkinter buttons. One approach is to use a lambda operator. So you could rewrite the line above (and all the other ones that try to send arguments to the command functions) as follows:
slogan = tk.Button(frame, text="Cookie click!", command=lambda: clicc(a,b))
Just a reminder that if you don't need to send any arguments, there's no need to use lambda.
The second problem you are having is with the clicc() function. When you call it from the slogan button, you're sending the a and b variables, and, supposedly updating the value from a. What actually happens is that, when you send the values from a and b to the function, these values are allocated to local variables, that shadow the names from your global variables. So any alteration that you might do inside the function, as a=a+b, will only change the value from you local variables, and leave your global variables unaltered. The same happens in the boostie() function.
You can read more about the local/global variables dynamics here.
The most direct solution to you problem, although far from the most elegant, is to add the global keyword with the variable names at the start of the functions that do try to modify the values from global variables. For example, your clicc() function could look like:
def clicc(x, y):
global a
a=x+y
Notice how I changed the first argument from a to x, so as to not use the same variable names.
Another solution would be to work with other types of objects, like lists or even classes, to avoid the need of global variables, so you might want to look into that.
And also is it possible for the cookie amount to be displayed in a button's text?
In the link you've provided, they show an example with a label, but it is exactly the same with buttons. You can do something like buttonzy.config(text=str(a)) after updating the value from a.

How do I send multiple arguments when binding a widget to a function in python 2.7.2

Just wondering, How do I bind an entry field to (with the return key) a function that requires 2 arguments counting event as one of them without lambda or classes, just one function and 2 arguments:
def function(event,entry):#entry is the widget to be focused. also, entry is just a place holder NOT A WIDGET. entry MUST be specified. it cannot be removed.
entry.focus()
entry1.bind("<Return>",function(None,entry2))
When entry1 is binded, the function that it is bound to executes right when it is binded and then it ignores all other input. It lets me put characters into the field, but when I hit return, it does not go through and focus the second entry. If I remove None as one of the arguments it gives me an error that only one of two required arguments are defined, it doesn't matter what I put in place of None, it still doesn't work. How do I make it work without classes or an anonymous function?
When you write function(None,entry2) you /are/ calling it right away -- the function probably returns None, so essentially what you are doing is:
function(None, entry2)
entry1.bind("<Return>", None)
What you are probably looking for is this:
entry1.bind("<Return>", lambda e: function(entry2))
This generates a function (note: generates the function, but doesn't call it) that takes one parameter (the event, "e") and ignores it
When you then hit the return key, this generated function will be called, and in turn it will call function(entry2)
The answer is to use lambda. For a good explanation see Tkinter Callbacks on effbot.org
Without a full example, it's difficult to provide a full answer. However there is one common error that is easily fixed: the second argument to bind should be a function name, not a function call.
If you change the bind statement to:
entry1.bind("<Return>", function)
the function will be called when the Return event is triggered instead of when you execute the bind statement.

Categories