Function parameters and naming

Do I understand well that def function parameters allow the communication of values between the function scope and the rest of the code? So, for example, if I have:

def my_function():
    print("foo")

I don’t need to set the my_function() param because I don’t need any parameter within print(). While if I define something like this:

def greeting(name):
  print(f"Hi, my name is {name}.")
  
greeting("Peter")

{name} within the print() cannot retrieve value outside that function scope so I do have to set greeting parameter?

And why do the function parameter placeholder sometimes differ from variables in or outside the scope? For example, I define:

def outer_function(whatever):
  def inner_function():
    print("Button pressed.")
    print(input_string.get())
  return inner_function

...

input_string = tk.StringVar(value = "test")
entry = ttk.Entry(window, textvariable = input_string)
entry.pack()

...
button = ttk.Button(window, text = "Button 1", command = outer_function(input_string)) 
button.pack()

The first run of the outer_function does not need to retrieve any values from outside the function scope, and on the second run, just the inner_function is performed. So why is there a whatever parameter? Do we somehow use whatever?

Hmm, did you intend to use a closure? Otherwise this function only works if input_string is a global variable.

def outer_function(input_string):
  def inner_function():
    print("Button pressed.")
    print(input_string.get())
  return inner_function

def main():
    input_string = tk.StringVar(value = "test")
    entry = ttk.Entry(window, textvariable = input_string)
    entry.pack()
    ...
    button = ttk.Button(window, text = "Button 1", command = outer_function(input_string)) 
    button.pack()

main()

The inner function can use the parameters of the outer function.

The way you use the terminology is a bit strange, but your understanding seems fine.

Because that’s the point. When you write a function that has a parameter, you are consciously expecting the calling code to give you the value that the code will work with. Because of this, you don’t need to know what the outside code calls it, because you won’t go looking for it - it’s given to you. You can call it whatever makes sense locally, and use that name to write the code for the function. That also means that multiple other places can use your function, and give it differently named - or even unnamed things:

def my_function(a_value):
    print("I was given", a_value)

print("In one place, I use a different name")
dead_parrot = "a Norwegian Blue"
my_function(dead_parrot)

print("in another place, I use no name")
my_function("lovely spam, wonderful spam")

This example is a lot more complicated. But first you should understand that there is no “second run” of outer_function. Instead, calling outer_function creates a new inner_function; and when the button is pressed, Tkinter will call that separately created inner_function.

Because someone made a mistake when writing the code. :wink: When the inner_function runs, it needs to use input_string, which is a global. It will always use that name, no matter how we called outer_function in the first place; and it will look in the global namespace for that name.

The intended technique looks like this instead:

def outer_function(whatever):
  def inner_function():
    print("Button pressed.")
    print(whatever.get()) # notice this is changed
  return inner_function

Now, the inner_function will look up the whatever name when it runs. But when it was compiled, Python knew “oh, this function is nested, and the other function that it’s nested inside, has its own whatever name”. So Python will compile this a little differently. When the inner_function runs, it will look for a closure for whatever - a special storage area that remembers the locals from the outer_function call, even after the outer_function has finished running. (And it only creates this storage because it saw the nested function during compilation, and it only creates storage for whatever.)

The idea is, when we write

, now the outer_function can create a button callback that works with any tk.StringVar, not just the input_string global. You just have to pass it to outer_function, and it sets up this special “binding” for the inner_function, that lets it remember which tk.StringVar to use when the button is clicked.

So this means we can make multiple tk.StringVars and multiple ttk.Buttons, and use the same outer_function logic to explain how the buttons should work. When we call outer_function, we say which StringVar to use for which Button:

string_a = tk.StringVar(value="one")
string_b = tk.StringVar(value="two")
button_a = ttk.Button(window, command=outer_function(string_a))
button_a.pack()
button_b = ttk.Button(window, command=outer_function(string_b))
button_b.pack()

When we click each button, it shows the message from the corresponding entry.

3 Likes

Do I understand well that def function parameters allow the
communication of values between the function scope and the rest of
the code?

Yes. In particular, you write the code for the function to work with
some fixed variable name, eg name, and the parameters assign a value
to name for a particular call:

 def greeting(name):
     print("Hello", name)

 my_name = "Cameron"
 your_name = "Jan"

 greeting(my_name)
 greeting(your_name)

In this way the function does not need to know anything about the code
which called it or what variable names that code might be using.

def greeting(name):
 print(f"Hi, my name is {name}.")

greeting("Peter")

{name} within the print() cannot retrieve value outside that function scope so I do have to set greeting parameter?

Well…

Broadly, we do not access variables from outside the function directly.
We try to write “pure functions” which receive values only through
their parameters and produce results only by the return statement.
These functions do not have side effects, so they are easy to reason
able and use safely and correctly.

However, a function has access to its outer scopes. When you use a
variable name such as name in your greeting() function Python looks
for the variable in the local scope i.e. the function’s local variables,
which include the parameters, and then in outer scopes. Typically that
outer scope is the module namespace, which is where what you think of as
“global variables” live.

Example:

 DEFAULT_VALUE = 10

 def foo(p=None):
     if p is None:
         p = DEFAULT_VALUE
     print("foo:", p)

Here we have a function foo() with a parameter p. If you don’t
supply the parameter is defaults to None and the function recognises
that and sets p=DEFAULT_VALUE in that case. This kind of this is quite
common. So: foo(3) would print foo: 3 and foo() would print foo: 10.

Let’s look at the variables. When you access a variable Python looks for
its name in the local variables and then in the outer scopes (which are
just the module.global scope here). For p it finds it in the local
variables because it is a parameter. For DEFAULT_VALUE it does not
find it in the local variables but does find it in the outer scope.

You’ll have noticed that we don’t “declare” variables in Python. When
you define a function Python scans the code for assignment statements.
If you assign to the name anywhere in a function, that name is a local
variable. The parameters are assigned to. So Python knows p is local
because it’s a parameter. DEFAULT_VALUE is not assigned to, so it must
be found elsewhere. Note that the assignment does not need to actually
be run:

 def func2():
     print(x)
     x = 2

The variable x is a local variable because there’s an assignment
statement in the function code. When you run the above you’ll get a
UnboundLocalError because x has not yet been set, but Python still
knows that it is a local variable, even if the outer scope also had an
x variable.

And why do the function parameter placeholder sometimes differ from
variables in or outside the scope? For example, I define:

def outer_function(whatever):
 def inner_function():
   print("Button pressed.")
   print(input_string.get())
 return inner_function

...

input_string = tk.StringVar(value = "test")
entry = ttk.Entry(window, textvariable = input_string)
entry.pack()

...
button = ttk.Button(window, text = "Button 1", command = outer_function(input_string))
button.pack()

The first run of the outer_function does not need to retrieve any values from outside the function scope, and on the second run, just the inner_function is performed. So why is there a whatever parameter? Do we somehow use whatever?

This is more complex than you might think. And, I think, more complex
than it needs to be.

To answer your question: you’re not using the whatever variable. I
don’t think you need it at all. As written above. But I think the code
above isn’t what might ordinarily be done.

I was thinking that Nice Zombies had missed the point, but now I think
they’re spot on.

Do I gather you’ve adapted this code from some example?

Let’s examine this code in detail. Starting at the bottom, of course :slight_smile:
Because the bottom code is the objective. Then we work back to how it is
done.

So, you’re defining a button:

 button = ttk.Button(window, text = "Button 1", command = outer_function(input_string))

Here we make a button with the label Button 1 and arrange that when it
is pushed it calls a function - that function is passed as the command
parameter to the button setup. The idea is that pressing the button
prints the current value of the input string, so the function you pass
must access the object holding the input string.

Now, you’ve also defined an Entry widget which stores its stae, the
input string, in a StringVar object which we refer to with the
input_string variable:

 input_string = tk.StringVar(value = "test")
 entry = ttk.Entry(window, textvariable = input_string)

So your button wants a callback function (the command parameter) which
accesses the StringVar to get the current input string value. And that
is when you call outer_function(): outer_function is a function
which returns a function. The function it returns is what accesses and
prints the value. Let’s look at how it is defined:

 def outer_function(whatever):
   def inner_function():
     print("Button pressed.")
     print(input_string.get())
   return inner_function

What does it do? It defines inner_function(), which runs a couple of
print() calls. The second print directly accesses input_string to
obtain the value - that’s the StringVar object. The whatever
parameter is not used.

This works, but only because you’ve got exactly one Entry widget, so
you get to name it directly in the function. What if you had more, for
example two entries and two buttons:

 input_string = tk.StringVar(value = "test 1")
 entry1 = ttk.Entry(window, textvariable = input_string)
 button = ttk.Button(window, text = "Button 1", command = outer_function(input_string))

 input_string2 = tk.StringVar(value = "test 2")
 entry2 = ttk.Entry(window, textvariable = input_string2)
 button = ttk.Button(window, text = "Button 2", command = outer_function(input_string2))

As written, the call to outer_function(input_string) or outer_function(input_string2)
ignores the parameter (whatever) and returns a function which always
accesses input_string, the first one. Not so good: both buttons report
on the same StringVar object.

I’d be naming these functions differently:

 def get_report_on_var(whatever):
   def report():
     print("Button pressed.")
     print(input_string.get())
   return report

so that it is more clear that you have a function report() which
reports on the value of some StringVar, and a function
get_report_on_var() which returns a report function. So we would set
up the buttons like this:

 button = ttk.Button(window, text = "Button 1", command = get_report_on_var(input_string))
 button = ttk.Button(window, text = "Button 2", command = get_report_on_var(input_string2))

But the report() function still reaches for the hardwired
input_string object. The intent of the button setups above is more
clear: we want to report on input_string from button 1 and report on
input_string2 from button 2. But the get_report_on_var() function
ignores its argument!

What we now want is for get_report_on_var() to return a function which
reports on whatever StringVar we hand it. So it needs a further change:

 def get_report_on_var(some_var):
   def report():
     print("Button pressed.")
     print(some_var.get())
   return report

Now it reports on the particular StringVar object you passed in when
you set up the button, and the parameter (formerly whatever) is not ignored!

So: how does this work?

What you have above is called a “closure”, where the inner function
report() has access to the variables in its immediate outer scope:
those local variables of get_report_on_var(). When it accesses
some_var, that is not a local variable of report(). So Python looks
in the next outer scope, which is the local variables of
get_report_on_var() at the time you called it. That’s important.

When you set up the buttons, you call get_report_on_var() to obtain
the callback function for the button. The get_report_on_var() function
defines a new function called report():

 def report():
   print("Button pressed.")
   print(some_var.get())

Functions are just another type of object in Python, so this is a kind
of assignment statement: the name report is bound to the function as a
local variable in get_report_on_var(). And the new function you just
made has access to get_report_on_var() local variables, particularly
some_var, which is a local variable because it came from the
parameters.

When you run a function, the local variables are new, unrelated to the
ones some any earlier or later call: you get a fresh set every time.

So when you define the first button, you call get_report_on_var()
once, and it returns a function which accesses some_var, which on that
call is input_string.

When you define the second button, you call get_report_on_var() once
again, and it returns another function which accesses some_var,
which on that call is input_string2, the other StringVar object`.

These “callback unctions” are not run until you press one of the
buttons.

I think someone’s trying to teach your about closures, which is why the
functions had the names outer_function and inner_function, but
they’re poorly named for the programme as a whole.

Finally, returning your first question about scopes: the inner function
report() (which gets defines anew on every call to get_report_on_var())
has 3 scopes to access names:

  • its local variables
  • the local variables of get_report_on_var()
  • the module/global variables
1 Like

A function parameter is a local variable, but one that can be defined in a special way. For example,

def foo(x):
    y = 5
    return x + y

Both x and y are local variables, defined in the scope of foo. But while y is defined explicitly by an assignment statement, x is defined implicitly via a function call. That is,

foo(3)

basically performs an assignment x = 3 before entering the body of the function.

1 Like

Yes, I made a mistake when retyping this segment from the video tutorial; the variable name of the second print should be whatever, so print(whatever.get()).

I recommend to put the main code in a function, that doesn’t hide these errors.

So, as I understand it now, the outer_function parameter placeholder can differ if I am sending into the function just one value because it is clear that the function could receive just one parameter, and there is no uncertainty about which one which could be a case of more complex functions with more parameters.

So for example:

def caller(name):
    print(f"Hello {name}.")

dog = Murphy
caller(dog)

Would result in Hello Murphy. since I am sending just one parameter to the named function.