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: :slight_smile:](https://emoji.discourse-cdn.com/apple/slight_smile.png?v=12)
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