Making a variable conditionally global - python

Suppose that I'm required to run a function with a loop in it until I meet a condition. For the function to work right, I'm only allowed to return one value in the function, but once the condition clears, I want to bring some of the calculations I performed into the global scope. I'm not allowed to use the return command to do this, so I decided to globalize the variables in post. This raises a warning, but seems to work alright. Is this the best way to do things?
Here's an example:
def check_cond(x,cond):
return (x - cond,3)
def loop(x,func):
relevant_value = 0
while x > 0:
local_expensive_calculation = 1 #use your imagination
x = func(x,local_expensive_calculation)[0]
relevant_value += func(x,local_expensive_calculation)[1]
if x == 0:
global local_expensive_calculation
return relevant_value
x = 4
loop(x,check_cond)
#then do stuff with local_expensive_calculation, which is now global

This may be slightly abusing the system but you can set your variable as an attribute of the function, and then access it later through that namespace:
def check_cond(x,cond):
return (x - cond,3)
def loop(x,func):
relevant_value = 0
while x > 0:
local_expensive_calculation = 1 #use your imagination
x = func(x,local_expensive_calculation)[0]
relevant_value += func(x,local_expensive_calculation)[1]
if x == 0:
loop.local_expensive_calculation = local_expensive_calculation
return relevant_value
x = 4
loop(x,check_cond)
print loop.local_expensive_calculation

If you absolutely insist on having it as a global, one way you can do that is by changing the line:
global local_expensive_calculation
to:
globals()['local_expensive_calculation'] = local_expensive_calculation
It makes your SyntaxWarning disappear.
I don't know much about the context and it appears you want a Python 2.7 answer (Python 3.?) has nonlocal.
Bear in mind that the while statement can also have a else clause
You can write:
while x>0:
# do calculation and stuff
else:
# make calculation result global
PS: There's a typo in your assignment to x: (calcuation --> calculation)

Related

Basic Python if statements

I'm learning Python and I'm trying to understand this line if I call f(-1):
x = 0
def f(x):
if x < 0:
return g(-x)
else:
return g(x)
def g(x):
return 2*x + 3
If I call f(-1) I get 5. But I incorrectly interpret that I would get 1. This is my interpretation process:
Since x=-1 it should return g(-x). There is no def g(-x) though. However if it returns def g(x) then we should get 2*x+3, which is 1?
Don't know where I misunderstand.
Thanks
Think of g as the function and x as input to the function.
Furthermore, x is also just like any other variable name.
This means I could instead rename the x variable in the g function to anything I want.
I could also call g anything I want.
Example:
def f(x):
if x < 0:
return grumpy_function(-x)
else:
return grumpy_function(x)
def grumpy_function(cool_value):
return 2*cool_value + 3
Now try to walk through the logic using these above functions...
f(-1) causes the if statement x<0 to be true.
So we will execute the line return grumpy_function(-x)
We know that x=-1, so this means -x = -(-1) = 1.
Therefore cool_value is actually 1 not -1.
Now go to grumpy_function: 2*1+3 = 5.
when call g(-x), in your case, it equals g(-(-1)), which is g(1)
When you specify def f(x) or def g(x), you're saying that, in the following context, x is going to be the name for the actual parameter of these two methods, regardless of the x=0 defined outside.
That being said, the following lines are equivalent:
f(-1)
g(1) # because if x < 0 is True
2 * 1 + 3
5
From your code, it is not exactly clear to me which of the xes you'd like to refer to the global x=0 and which of them should refer to the function's parameter, like -1 in your example. You should make this distinction yourself and name them differently, for example, x and y. As far as I know, if you name your function parameters the same as your global variables, you lose access to the global variables from within the function body (except for globals tricks).

Using the value of an if parameter without saving it into a variable

Is it possible to "re-use" the value of a parameter of an if-clause?
I have a function that returns either True or a dictionary:
def foo():
if random.randint(0, 1) == 0:
return True
else:
return {"time": time.time()}
If foo() returns the dictionary, I also want to return it. If foo() returns True I want to just continue.
Currently I'm using:
if_value = foo()
if if_value is not True:
return if_value
My goal would be to avoid saving the return value of the function into a variable, because it makes my code really ugly (I need to do this about 20 times in a row).
When using a Python shell, it seems to work like this:
if function_that_returns_True_or_an_int() is not True:
return _
Any suggestions about this?
You can use the walrus operator (:=) to declare a variable and assign to it, then do your comparison
if (x := your_function()) == condition:
# something
else:
# something else
print(x) # x is still a named variable and in scope

Defining variable scope in function

I'm attempting to loop through a function and use a variable defined in the main scope of the program, but for some reason, it's not passed into my function. This is the first time I've used functions and variable scopes in Python, I read through the Python documentation as well as some various posts on here, but couldn't seem to figure out what I did wrong.
The function will be recursive therefore I'm unable to define them in the head of the function else it will just redefine each time. I tried doing what was done in this post in my file, but it doesn't seem to work.
I have both the main + function in one file and defined the variables I wish to use as global inside the function I want to use them in.
lv1Count = 12
lv2Count = 14
lv3Count = 18
lv4Count = 4
AL = []
def opt(target):
global lv4Count
global lv3Count
global lv2Count
global lv1Count
global AL
goal = target
if (goal <= 0 & lv4Count < 0):
pass
if (goal <= 1 & lv1Count < 0):
pass
if (goal == 2 & lv2Count < 0):
pass
if (goal == 3 & lv3Count < 0):
pass
if (goal == 4 & lv4Count < 0):
pass
opt(4)
I replaced all of the if statements with pass to avoid excessive code, but essentially whenever returning something from these statements, the comparison using the counter doesn't work as it's not successfully reading the value of this variable and the functionality doesn't occur.
Your function is working correctly: this is indeed how you use global variables, even though it is usually a bad idea. (In recursion, it is most common to pass the necessary values as arguments to the function.) If you include more details about what kind of recursion you want to do, I can help with that.
In [1]: v = 1
In [2]: def test():
...: global v
...: return v
...:
In [3]: test()
Out[3]: 1
The problem is with your if statement: you are using bitwise & instead of the normal logical operator and. Since & is evaluated first in the order of operations, you are getting problems. Consider:
In [1]: bool(1 == 1 & 2 == 2)
Out[1]: False
Why? Because this is evaluated as:
In [1]: bool(1 == (1 & 2) == 2)
Out[1]: False
Which is the same as:
In [1]: bool(1 == 0 == 2)
Out[1]: False

Accessing global function's variables in a local function

Here's my test script:
def main(): #global
n = 1
z = None
def addone(): #local
if not z:
n = n+1
addone()
print n
main()
I step into the addone() function once it hits the calling line.
At this point I can only see the variable z, but cannot see n.
Now, if n is referenced before assignment, shouldn't z be too?
Similarly, if I change n=n+1 to z='hi', I can no longer see z!
This is contrary to all my previous beliefs about local/global functions! The more you know, the more you know you don't know about Python.
Question(s):
Why can I see one but not the other?
Do I want to be prepending global to these variables I want to reassign?
The best solution is to upgrade to Python 3 and use in the inner function nonlocal n. The second-best, if you absolutely have to stick with Python 2:
def main(): #global
n = [1]
z = None
def addone(): #local
if not z:
n[0] += 1
addone()
print n[0]
main()
As usual, "there is no problem in computer science that cannot be solved with an extra level of indirection". By making n a list (and always using and assigning n[0]) you are in a sense introducing exactly that life-saving "extra level of indirection".
Okay, after some testing, I realised that it all has to do with the reassignment of variables.
for example:
def main(): #global
n = 1
z = None
def addone(): #local
if not z:
x = n+1
addone()
print n
main()
Now shows both n and z when I am inside the addone() function. This is because I am no longer trying to reassign n, makes sense to me so as to protect global variables from manipulation if one so happens to use similar names in local functions.

Why doesn't this closure modify the variable in the enclosing scope?

This bit of Python does not work:
def make_incrementer(start):
def closure():
# I know I could write 'x = start' and use x - that's not my point though (:
while True:
yield start
start += 1
return closure
x = make_incrementer(100)
iter = x()
print iter.next() # Exception: UnboundLocalError: local variable 'start' referenced before assignment
I know how to fix that error, but bear with me:
This code works fine:
def test(start):
def closure():
return start
return closure
x = test(999)
print x() # prints 999
Why can I read the start variable inside a closure but not write to it?
What language rule is causing this handling of the start variable?
Update: I found this SO post relevant (the answer more than the question): Read/Write Python Closures
Whenever you assign a variable inside of a function it will be a local variable for that function. The line start += 1 is assigning a new value to start, so start is a local variable. Since a local variable start exists the function will not attempt to look in the global scope for start when you first try to access it, hence the error you are seeing.
In 3.x your code example will work if you use the nonlocal keyword:
def make_incrementer(start):
def closure():
nonlocal start
while True:
yield start
start += 1
return closure
On 2.x you can often get around similar issues by using the global keyword, but that does not work here because start is not a global variable.
In this scenario you can either do something like what you suggested (x = start), or use a mutable variable where you modify and yield an internal value.
def make_incrementer(start):
start = [start]
def closure():
while True:
yield start[0]
start[0] += 1
return closure
There are two "better" / more Pythonic ways to do this on Python 2.x than using a container just to get around the lack of a nonlocal keyword.
One you mentioned in a comment in your code -- bind to a local variable. There is another way to do that:
Using a default argument
def make_incrementer(start):
def closure(start = start):
while True:
yield start
start += 1
return closure
x = make_incrementer(100)
iter = x()
print iter.next()
This has all the benefits of a local variable without an additional line of code. It also happens on the x = make_incrememter(100) line rather than the iter = x() line, which may or may not matter depending on the situation.
You can also use the "don't actually assign to the referenced variable" method, in a more elegant way than using a container:
Using a function attribute
def make_incrementer(start):
def closure():
# You can still do x = closure.start if you want to rebind to local scope
while True:
yield closure.start
closure.start += 1
closure.start = start
return closure
x = make_incrementer(100)
iter = x()
print iter.next()
This works in all recent versions of Python and utilizes the fact that in this situation, you already have an object you know the name of you can references attributes on -- there is no need to create a new container for just this purpose.
Example
def make_incrementer(start):
def closure():
# I know I could write 'x = start' and use x - that's not my point though (:
while True:
yield start[0]
start[0] += 1
return closure
x = make_incrementer([100])
iter = x()
print iter.next()
In Python 3.x you can use the nonlocal keyword to rebind names not in the local scope. In 2.x your only options are modifying (or mutating) the closure variables, adding instance variables to the inner function, or (as you don't want to do) creating a local variable...
# modifying --> call like x = make_incrementer([100])
def make_incrementer(start):
def closure():
# I know I could write 'x = start' and use x - that's not my point though (:
while True:
yield start[0]
start[0] += 1
return closure
# adding instance variables --> call like x = make_incrementer(100)
def make_incrementer(start):
def closure():
while True:
yield closure.start
closure.start += 1
closure.start = start
return closure
# creating local variable --> call like x = make_incrementer(100)
def make_incrementer(start):
def closure(start=start):
while True:
yield start
start += 1
return closure

Categories

Resources