Can inner functions always be safely defined outside of the function? - python

For example:
def increment(number):
... def inner_increment():
... return number + 1
... return inner_increment()
The inner_increment() can be taken out and defined in the same scope as increment(). Is that true?
EDIT:
def increment(number):
return inner_increment(number)
def inner_increment(number):
return number+1

"Always" and "safely" are very sweeping words, involving a variety of assumptions and perhaps a value judgement or two.
As you have done, yes, it's possible to extract a nested function -- provided that you move all of its required outer scope collateral into the parameter list. Anything that inner assumed from the outer scope, now must be passed as an argument.
You also need to ensure that you don't collide with any other inner_increment symbol in the outer scope.

Related

Why is my variable is unbound in one inner function but not the other?

In the code below, why does the first version of say work but the second version throws "local variable 'running_high' referenced before assignment"?
def announce_highest(who, last_score=0, running_high=0):
assert who == 0 or who == 1, 'The who argument should indicate a player.'
''' this one works
def say(*scores):
assert len(scores) == 2
gain = scores[who] - last_score
if gain > running_high:
print(gain, "point(s)! That's the biggest gain yet for Player", who)
return announce_highest(who, scores[who], gain)
return announce_highest(who, scores[who], running_high)
return say
'''
# this one errors "local variable 'running_high' referenced
# before assignment"
def say(*scores):
gain = scores[who] - last_score
if gain > running_high:
running_high = scores[who]-last_score
print(gain,"point(s)! That's the biggest gain yet for Player",who)
return announce_highest(who, scores[who], gain)
return announce_highest(who,scores[who],running_high)
return say
There's one key difference between your two solutions which is causing them to behave differently: In the second solution, you assign a value to running_high on the third line:
def say(*scores):
gain = scores[who] - last_score
if gain > running_high:
# here
running_high = scores[who]-last_score
Python variable scoping is a little bit different than other languages. The order of name resolution is as follows:
Local (function) scope. There is no block scope in python, in contrast to many other languages, so e.g. the following code is valid:
if True:
foo = "foo"
else:
foo = "bar"
print(foo)
Even though foo is defined inside either the if block or the else block, it can be used outside of those blocks because it's still in the local scope.
Enclosing (nonlocal) scope. Inside say, running_high is in this scope, because it is a parameter to announce_highest, the enclosing function.
Global (module) scope. The top-most scope accessible to the programmer. If you define something in a script outside of a function, it's in the global scope.
Built-in scope. This contains all of python's built-in functions, keywords, etc.
Coming back to the question at hand, when you make an assignment to running_high in your second solution, you redefine it in the local scope. This is known as shadowing. We say the function-local running_high defined in say shadows the enclosing function running_high defined as a parameter of announce_highest.
However, the assignment on line 3 is not the first thing you do with your new local variable. On line 2, you try to use it in a comparison (if gain > running_high:). The interpreter hasn't yet seen running_high at this point in the local scope and as such has no value for it, hence the error.
The solution to this is to use the special keywords global and nonlocal to tell the interpreter that running_high is not a local variable. In this specific case, running_high is in the enclosing or nonlocal scope, so you need to rewrite your second solution as follows:
def say(*scores):
nonlocal running_high
...
To recap, here is your first solution:
def say(*scores):
assert len(scores) == 2
gain = scores[who] - last_score
# running_high isn't in the local scope, so the interpreter
# automatically looks for it in the enclosing scope. it exists
# as a parameter of announce_highest, so this is valid code
if gain > running_high:
print(gain, "point(s)! That's the biggest gain yet for Player", who)
return announce_highest(who, scores[who], gain)
return announce_highest(who, scores[who], running_high)
And here is the second:
def say(*scores):
gain = scores[who] - last_score
if gain > running_high:
# you didn't tell the interpreter where to look for the name
# running_high, so it assumes you want to create a new local
# variable. however, you tried to access running_high on the
# previous line before it had a value, hence the error
running_high = scores[who]-last_score
print(gain,"point(s)! That's the biggest gain yet for Player",who)
return announce_highest(who, scores[who], gain)
return announce_highest(who,scores[who],running_high)

Given a multi-nested Python function, how can I access a closure variable all the way down in some arbitrarily nested function?

I understand that in most cases nonlocal and global keywords allow inner functions to have access to either the outer function's variables or overall global variables. But the typical closure examples I've seen usually only nest to 2 levels (e.g. 1 outer and 1 inner function). I'm curious how we can access a variable in the first function scope all the way deep in some nested function scope. I know this type of programming doesn't appear often but I'm curious what the answer is
Please see example below
def pseudo_global(): # layer 1 -- first outer function
a = "global"
def block(): # layer 2
def show_a(): # layer 3
nonlocal a # doesn't do anything here because "a" isn't defined in block() scope
# global a # commented out; nothing prints anyway because refers to global scope
print(a) # free variable error; how can I access layer 1's variable here in layer 3?
show_a()
a = "block"
show_a()
block()
pseudo_global()
nonlocal a is precisely what you need. The issue is that you need two of them. You need to tell both block() and show_a() that a is nonlocal.
The definition of nonlocal is to cause the variable to refer to the previously bound variable in the nearest enclosing scope, excluding globals.
def pseudo_global(): # layer 1 -- first outer function
a = "global"
def block(): # layer 2
nonlocal a
def show_a(): # layer 3
nonlocal a
print(a)
a = "block"
show_a()
block()
pseudo_global()
global
block

When defining a lambda function with a variable inside, how to prevent changes in function when I change the variable?

For example: in this code, function changes when the variable changes. I would like to learn how to prevent the change in function behaviour when i change the variable. Is there some way to only get the value of the variable instead of the variable itself? Also are there any sources where I can learn more about problems like this?
a = 5
adder = lambda x: x + a
print(adder(5)) # prints 10
a = 50
print(adder(5)) # prints 55
Just like the equivalent function defined by a def statement (which is what you should be using, rather than assigning the result of a lambda expression to a name explicitly)
def adder(x):
return x + a
the name a isn't looked up until the function is called.
One way to make a function that specifically computes x + 5 when a == 5 at definition time is use a default argument value:
def adder(x, a=a):
return x + a
where the left-hand a is a new parameter (which isn't intended to be set explicitly), and the right-hand a is the value of the a in the current scope.
A better idea, though, is to define a closure, so that your function doesn't have a "hidden" parameter that can be abused.
# This is where a lambda expression does make sense: you want
# a function, but don't need to give it a name.
def make_adder(a):
return lambda x: x + a
adder = make_adder(5)
a in adder is still a free variable, but now it refers to a variable in a scope which you don't "outside" access to after make_adder returns.

Global variables in embedded functions

I have the following snippet:
def test ():
num_sum = 0
def inner_test ():
global num_sum
num_sum += 1
inner_test()
return num_sum
When I run test() I get:
NameError: name 'num_sum' is not defined
I was expecting that the inner function would change the value of the num_sum variable defined in the outer function. Basically, I need a global variable to increment in an inner function which I may call recursively.
I noticed that this pattern works well with collections (lists, dictionaries) even if I don't define the variable as global (but pass it as a parameter to the inner function).
However, with scalar values like int this pattern seems to break. Neither defining the variable as global (as is here) nor passing it as a parameter to the inner function works as intended. Basically, the scalar variable stays unchanged. What do I need to do to get the desired behaviour with such scalar values?
you need nonlocal instead of global. your num_sum is not a global variable (will not be found in globals()). nonlocal will instruct python not to search for it in the global namespace, but in the nearest namespace. the order is LEGB: Local, Enclosed, Global, Built-in.

Python - using a shared variable in a recursive function

I'm using a recursive function to sort a list in Python, and I want to keep track of the number of sorts/merges as the function continues. However, when I declare/initialize the variable inside the function, it becomes a local variable inside each successive call of the function. If I declare the variable outside the function, the function thinks it doesn't exist (i.e. has no access to it). How can I share this value across different calls of the function?
I tried to use the "global" variable tag inside and outside the function like this:
global invcount ## I tried here, with and without the global tag
def inv_sort (listIn):
global invcount ## and here, with and without the global tag
if (invcount == undefined): ## can't figure this part out
invcount = 0
#do stuff
But I cannot figure out how to check for the undefined status of the global variable and give it a value on the first recursion call (because on all successive recursions it should have a value and be defined).
My first thought was to return the variable out of each call of the function, but I can't figure out how to pass two objects out of the function, and I already have to pass the list out for the recursion sort to work. My second attempt to resolve this issue involved me adding the variable invcount to the list I'm passing as the last element with an identifier, like "i27". Then I could just check for the presence of the identifier (the letter i in this example) in the last element and if present pop() it off at the beginning of the function call and re-add it during the recursion. In practice this is becoming really convoluted and while it may work eventually, I'm wondering if there is a more practical or easier solution.
Is there a way to share a variable without directly passing/returning it?
There's couple of things you can do. Taking your example you should modify it like this:
invcount = 0
def inv_sort (listIn):
global invcount
invcount += 1
# do stuff
But this approach means that you should zero invcount before each call to inv_sort.
So actually its better to return invcount as a part of result. For example using tuples like this:
def inv_sort(listIn):
#somewhere in your code recursive call
recursive_result, recursive_invcount = inv_sort(argument)
# this_call_invcount includes recursive_invcount
return this_call_result, this_call_invcount
There's no such thing as an "undefined" variable in Python, and you don't need one.
Outside the function, set the variable to 0. Inside the loop, use the global keyword, then increment.
invcount = 0
def inv_sort (listIn):
global invcount
... do stuff ...
invcount += 1
An alternative might be using a default argument, e.g.:
def inv_sort(listIn, invcount=0):
...
invcount += 1
...
listIn, invcount = inv_sort(listIn, invcount)
...
return listIn, invcount
The downside of this is that your calls get slightly less neat:
l, _ = inv_sort(l) # i.e. ignore the second returned parameter
But this does mean that invcount automatically gets reset each time the function is called with a single argument (and also provides the opportunity to inject a value of invcount if necessary for testing: assert result, 6 == inv_sort(test, 5)).
Assuming that you don't need to know the count inside the function, I would approach this using a decorator function:
import functools
def count_calls(f):
#functools.wraps(f)
def func(*args):
func.count += 1
return f(*args)
func.count = 0
return func
You can now decorate your recursive function:
#count_calls
def inv_sort(...):
...
And check or reset the count before or after calling it:
inv_sort.count = 0
l = inv_sort(l)
print(inv_sort.count)

Categories