Functions - Variables - Scope - python-3.x

I have the below code.
a = 10
def f(x):
return x + a
a = 3
f(1)
If we print(f1) the result is 4. It seems that the second assignment of a is used here (a=3). Could someone explain to me if there is a particular reason or rule for not using the first assignment (a=10)?
Thank you,
Dimitrios

a and x are resolved each time return x + a is executed. You reassign a before calling f. f knows that a is not a local variable so looks up "a" in its enclosing scope. That "a" has a 3 in it, and that is what is used.
As a general rule, it would be very bad for f to use the original assignment when its executed. That would mean that functions couldn't take advange of global variables changing. It would also require that global variables be assigned before the function is even defined, which is not particularly useful for a dynamic language.
You can hack it, though. If you define a parameter with a default value, it will be assigned when the function is defined and you get what you want.
a = 10
def f(x, a=a):
return x + a
a = 3
print(f(1))

You see, you've defined a before you defined f(x). But your call to f(1) was after you assigned the value 3 to "a".
f(x) will always use the current value of "a" when it its called, not when it is defined. If you want a constant value for "a" inside f(x), you should declare it inside the function, something like:
def f(x):
a = 10
return x + a

Related

equivalent of .copy() for functions

Let's assume I work with a collection of objects and I want to define one of their methods at creation. For example:
class Operator:
def __init__(self, id, operation):
self.id = id
self.operation = operation
def test(self, arg):
print(self.operation(arg))
operators = []
for i in range(4):
op = lambda x: x+i
operators.append(Operator(i, op))
operators[0].test(5)
operators[1].test(5)
operators[2].test(5)
operators[3].test(5)
Here one could naively assume that we would get "5", "6", "7" and "8", but instead we only get 8s. I suppose this is because every time op is redefined it also gets redefined inside every Operator object.
I don't want that, I want each Operator to have its own operation method. I could do:
operators = []
ops = []
for i in range(4):
ops.append(lambda x: x+i)
operators.append(Operator(i, ops.pop()))
but it feels like a dirty hack.
Is there a cleaner way to do that?
I suppose this is because every time op is redefined it also gets
redefined inside every Operator object.
I don't want that, I want each Operator to have its own operation
method.
The function objects in the operation fields of each Operation object are different. Every time you evaluate lambda, it creates a new function object. For example, operators[0].operation is operators[1].operation is false, and id(operators[0].operation), id(operators[1].operation), etc. are all different.
But Python functions capture outside variables by reference, and in this case, the four separate function objects all capture the same variable, i, and the state of that variable is shared between the functions and the outside scope it was captured from. So when i was changed in that outside scope, it can be seen by all the functions.
If you want each function to capture a different value as you iterate through the loop, there are several ways to achieve this.
One way is to use an optional parameter with a default argument, like this:
for i in range(4):
op = lambda x, i=i: x+i
operators.append(Operator(i, op))
Here, the function does not capture i in the outer scope. Rather, it has an optional parameter, also named i though it's independent of the i in the outer scope. (We name it the same so that the expression inside can use the same variable name.) The default argument of this optional parameter, is the value of the outer-scope i evaluated at the time the lambda is evaluated. Since it's the value that is remembered, and not a reference to the variable i, each function will remember a different value. When the function is called without a second argument, it will use the remembered default argument as the argument of the parameter i inside the function.
An uglier way is to put an immediately-executed lambda around the capture.
for i in range(4):
operators.append((lambda i: Operator(i, lambda x: x+i))(i))
This works because the i in the execution of the outer lambda is different from the outer-scope i, and the i in the execution of the outer lambda only exists for the duration of the execution of that lambda, in which the value of its parameter i is passed in and never changed inside the lambda.
This can also be written as a list comprehension:
operators = [(lambda i: Operator(i, lambda x: x+i))(i) for i in range(4)]
or with map():
operators = list(map(lambda i: Operator(i, lambda x: x+i), range(4)))

How to call my function in another function

I am getting an value from the user in getInteger.
I need to get the output from sqInteger in getInteger.
No matter how I set up the parameters or indent the sqInteger function, variable x is undefined.
I added a return line to try and pass the x variable, but that's definitely not helping.
Please help me understand what I'm missing!
def getInteger():
while True:
try:
x = int(input('Enter an integer: '))
except ValueError:
print()
print('That\'s not an integer. Try again.')
continue
else:
return x
print(x)
break
def sqInteger(getInteger, x):
y = x**2
print(y)
Is this the entire code? You need to call the getInteger() function at some point in the code before that loop will begin. You're also not calling function sqInteger() at any point.
Your exception handler will immediately stop evaluating the try block and move down to the except block upon a non-integer being typed into the input. Therefore, you can place a call to the sqInteger() function after the input() function. If the user types a non-integer into the terminal, it will move down to your Exception handler and prompt the user to retry. If they enter an integer, the code will continue to evaluate and run the function sqInteger.
For this, you also do not need to pass getInteger into the sqInteger() function. You are technically allowed to pass functions as parameters in Python but it's not necessary for this and probably out of the scope of this program.
So the following code would be suitable:
def getInteger():
while True:
try:
x = int(input('Enter an integer: '))
# variable 'squared' now receives the return value from the function
squared = sqInteger(x) # call to function sqInteger necessary for this function to be executed
except ValueError:
print('That\'s not an integer. Try again.')
continue
else:
print(x) # if user entered 2, prints 2, not 4
return x # this value is still only what the user input, not the result of sqInteger()
break
def sqInteger(x):
y = x**2
print(y)
return y #you need to return values from functions in order to access it from outside the function
The reason you pass a variable into a function (as a parameter) is to give that function access to that variable. Creating a function creates a local scope for that function so that variables named within that function are in a separate namespace from variables outside that function. This is useful in large programs where many variables might exist and you need to keep them separate.
Because you've separately defined a sqrt function, it does not have access to variables outside of its scope. You need to pass in variables that you'd like it to have access to.
You also need to call functions before they will run. Defining a function only serves to set up the function so that it can be called as one functional unit. It's useful for separating concerns within a program. The ability to call a function is useful because it allows you to separate your code out and only mention a single call to a function rather than having the entire functionality jumbled in with the rest of the code. It also allows for reusability of code.
You can also have access to the result of the squared integer by returning a value and assigning this value to a function call, like such:
# lets say x = 4
squared = sqInteger(x)
def sqInteger(x):
y = x**2
return y
This would NOT work:
x = input("Enter integer") #lets say you enter 3
squared = sqInteger()
print(squared)
def sqInteger():
print(x) # error: x is not defined
return x**2 # error: x is not defined
The function does not have access to outside variables like x. It must be passed these variables as parameters so that you can call this function and set the parameters at will. This is for the sake of modularity in a program. You can pass it all sorts of different integers as parameters and it allows you to have a resuable function for anytime you need to square an integer.
Edit: Sorry this was a mess, I finally fixed all the errors in my explanation though...

Function changing variables outside of the function (that is not Returned by the function)

First off really sorry for the nondescript title, I don't know how to phrase my question.
Given the code below:
x = [9]
y = [2,4,6]
def f(x, y):
if len(x) > 0:
z = x + y
x.pop(-1)
return z.pop(0)
print(f(x,y)
print(f(x,y))
The second print line gives me an UnboundLocalError: local variable 'z' referenced before assignment
I understand what this error is, as the function is skipping the if clause and going straight to the return z.pop(0), but z doesn't exist because z is defined in the if clause.
What I would like to know is why the value of x is changed by the function
The function skips the if loop because after the first call, x has been changed from x = [9] to x = []
I thought that unless it is a Return statement, then any variables changed or created within a function are local to the function?
For example, geeksforgeeks.org states that
Any variable which is changed or created inside of a function is local, if it hasn’t been declared as a global variable
So why is the value of x changing when it hasn't been returned by the function? Shouldn't the value of x always be [9]?
Thank you
Lists are mutable. When you pass one into the function then you are really passing in a pointer to the list. It's better to think of Python as pass by reference than pass by value. The x you are changing is not created in the function it is passed in as an argument. You're not changing x (the memory address pointed to by the label x), you're changing the contents of that memory address.
See this answer Python functions call by reference

Trying to understand behavior of python scope and global keyword

def func():
def nested():
global x
x = 1
x = 3
nested()
print("func:", x)
x = 2
func()
print("main:", x)
Output:
func: 3
main: 1
I'm new to programming. I want to know where I'm going wrong. As I'm new to stack exchange, please let me know if there are any issues with my question or how to improve it.
The way I am reading this code is:
x is assigned the integer 2.
the func() function is called.
x is assigned the integer 3.
the nested() function is called.
x is declared a global variable? #not really clear of the implications
x is assigned the integer 1.
print("func":x) #because x was made a global variable within nested() I expected the output to be 1.
print("main": x) #I believe this is because x was made a global variable?
I'm not clear on why the output is 3 in the first print command?
In short, there are two different identifiers, x, being referenced here: one at the model level, and a different one local to func.
Your steps should read:
The identifier x at the top level (i.e. the module level) is associated with the value 2.
the func function is called.
The completely different identifier x which is local to func is associated with the value 3.
the nested function is called.
Python is told that, within this scope of nested, the x refers to the top level (module level) x. Note global doesn't mean "next level up", it means "the top/global level" - the same one affected by step 1, not the one affected by step 3.
The global (top level) x is associated with the value 1.
etc.
General hint: Every time you think you need to use global you almost certainly don't.
The question was asked perfectly fine. The reason why is because the global x was declared in the nested. Not in the whole program, so they're 2 different variables.

How to create an array of functions which partly depend on outside parameters? (Python)

I am interested in creating a list / array of functions "G" consisting of many small functions "g". This essentially should correspond to a series of functions 'evolving' in time.
Each "g" takes-in two variables and returns the product of these variables with an outside global variable indexed at the same time-step.
Assume obs_mat (T x 1) is a pre-defined global array, and t corresponds to the time-steps
G = []
for t in range(T):
# tried declaring obs here too.
def g(current_state, observation_noise):
obs = obs_mat[t]
return current_state * observation_noise * obs
G.append(g)
Unfortunately when I test the resultant functions, they do not seem to pick up on the difference in the obs time-varying constant i.e. (Got G[0](100,100) same as G[5](100,100)). I tried playing around with the scope of obs but without much luck. Would anyone be able to help guide me in the right direction?
This is a common "gotcha" to referencing variables from an outer scope when in an inner function. The outer variable is looked up when the inner function is run, not when the inner function is defined (so all versions of the function see the variable's last value). For each function to see a different value, you either need to make sure they're looking in separate namespaces, or you need to bind the value to a default parameter of the inner function.
Here's an approach that uses an extra namespace:
def make_func(x):
def func(a, b):
return a*b*x
return func
list_of_funcs = [make_func(i) for i in range(10)]
Each inner function func has access to the x parameter in the enclosing make_func function. Since they're all created by separate calls to make_func, they each see separate namespaces with different x values.
Here's the other approach that uses a default argument (with functions created by a lambda expression):
list_of_funcs = [lambda a, b, x=i: a*b*x for i in range(10)]
In this version, the i variable from the list comprehension is bound to the default value of the x parameter in the lambda expression. This binding means that the functions wont care about the value of i changing later on. The downside to this solution is that any code that accidentally calls one of the functions with three arguments instead of two may work without an exception (perhaps with odd results).
The problem you are running into is one of scoping. Function bodies aren't evaluated until the fuction is actually called, so the functions you have there will use whatever is the current value of the variable within their scope at time of evaluation (which means they'll have the same t if you call them all after the for-loop has ended)
In order to see the value that you would like, you'd need to immediately call the function and save the result.
I'm not really sure why you're using an array of functions. Perhaps what you're trying to do is map a partial function across the time series, something like the following?
from functools import partial
def g(current_state, observation_noise, t):
obs = obs_mat[t]
return current_state * observation_noise * obs
g_maker = partial(g, current, observation)
results = list(map(g_maker, range(T)))
What's happening here is that partial creates a partially-applied function, which is merely waiting for its final value to be evaluated. That final value is dynamic (but the first two are fixed in this example), so mapping that partially-applied function over a range of values gets you answers for each value.
Honestly, this is a guess because it's hard to see what else you are trying to do with this data and it's hard to see what you're trying to achieve with the array of functions (and there are certainly other ways to do this).
The issue (assuming that your G.append call is mis-indented) is simply that the name t is mutated when you loop over the iterator returned by range(T). Since every function g you create stores returns the same name t, they wind up all returning the same value, T - 1. The fix is to de-reference the name (the simplest way to do this is by sending t into your function as a default value for an argument in g's argument list):
G = []
for t in range(T):
def g(current_state, observation_noise, t_kw=t):
obs = obs_mat[t_kw]
return current_state * observation_noise * obs
G.append(g)
This works because it creates another name that points at the value that t references during that iteration of the loop (you could still use t rather than t_kw and it would still just work because tg is bound to the value that tf is bound to - the value never changes, but tf is bound to another value on the next iteration, while tg still points at the "original" value.

Resources