Changing a local variable in a function from another function - python

First, here's my example code:
EDIT: I should have specified, in my real code, that_func() is already returning another value, so I want it to return one value, and change c in addition
EDIT 2: Code edited to show what I mean
def this_func():
c=1 # I want to change this c
d=that_func()
print(c, d)
def that_func():
this_func.c=2 #Into this c, from this function
return(1000) #that_func should also return a value
this_func()
What I want to do is change the local variable c in this_func() to the value I assign it in that_func(), so that it prints 2 instead of 1.
From what I've gathered online, this_func.c=2 should do just that, but it doesn't work. Am I doing something wrong, or have I misunderstood?
Thanks for any and all help.

Yes, you misunderstood.
functions are not class. You can't access variables of a function like that.
Obviously, it's not the smartest of code that can be written, but this code should give an idea about how to use variables of a function.
def this_func():
c=1 # I want to change this c
c=that_func(c) # pass c as parameter and receive return value in c later
print(c)
def that_func(b): # receiving value of c from this_func()
b=2 # manipulating the value
return b #returning back to this_func()
this_func()

Wrap it in an object and pass it to that_func:
def this_func():
vars = {'c': 1}
d = that_func(vars)
print vars['c'], d
def that_func(vars):
vars['c'] = 2
return 1000
Alternatively, you can pass it in as a regular variable and that_func can return multiple values:
def this_func():
c = 1
c, d = that_func(c)
print c, d
def that_func(c):
c = 2
return c, 1000

Related

Remember Array value after Function call

If I write this:
c = []
def cf(n):
c = range (5)
print c
if any((i>3) for i in c) is True:
print 'hello'
cf(1)
print c
Then I get:
[1, 2, 3, 4]
hello
[]
I'm really new to programming, so please explain it really simply, but how do I stop Python from forgetting what c is after the function has ended? I thought I could fix it by defining c before the function, but obviously that c is different to the one created just for the function loop.
In my example, I could obviously just write:
c = range (5)
def cf(n)
But the program I'm trying to write is more like this:
b = [blah]
c = []
def cf(n):
c = [transformation of b]
if (blah) is True:
'loop' cf
else:
cf(1)
g = [transformation of c that produces errors if c is empty or if c = b]
So I can't define c outside the function.
In python you can read global variables in functions, but you cant assigned to them by default. the reason is that whenever python finds c = it will create a local variable. Thus to assign to global one, you need explicitly specify that you are assigning to global variable.
So this will work, e.g.:
c = [1,2,3]
def cf():
print(c) # it prints [1,2,3], it reads global c
However, this does not as you would expect:
c = [1,2,3]
def cf():
c = 1 # c is local here.
print(c) # it prints 1
cf()
print(c) # it prints [1,2,3], as its value not changed inside cf()
So to make c be same, you need:
c = [1,2,3]
def cf():
global c
c = 1 # c is global here. it overwrites [1,2,3]
print(c) # prints 1
cf()
print(c) # prints 1. c value was changed inside cf()
To summarise a few of these answers, you have 3 basic options:
Declare the variable as global at the top of your function
Return the local instance of the variable at the end of your function
Pass the variable as an argument to your function
You can also pass the array c into the function after declaring it. As the array is a function argument the c passed in will be modified as long as we don't use an = statement. This can be achieved like this:
def cf(n, c):
c.extend(range(5))
print c
if any((i>3) for i in c) is True:
print 'hello'
if __name__ == '__main__':
c = []
cf(1, c)
print c
For an explanation of this see this
This is preferable to introducing global variables into your code (which is generally considered bad practice). ref
Try this
c = []
def cf(n):
global c
c = range (5)
print c
if any((i>3) for i in c) is True:
print 'hello'
cf(1)
print c
If you want your function to modify c then make it explicit, i.e. your function should return the new value of c. This way you avoid unwanted side effects:
def cf(n, b):
"""Given b loops n times ...
Returns
------
c: The modified value
"""
c = [transformation of b]
...
return c # <<<<------- This
c = cf(1)

Python: variables not cumulative passing through functions

I am writing a function for a text adventure I'm making that acts as a progress bar when the player receives experience (which then levels the player up upon reaching 100). For some reason altering the values of my variables inside the function does not change their values outside the function even though I've returned all three. This becomes apparent when I try calling the function twice, each time adding 85 to the variable a.
Within the function, the objective is to pass the value of a to b, check if b is greater than or equal to 100, if so add 1 to c and remove 100 from b, then reset a to 0, print the result, and return the new values.
a = new experience b = current experience c = player level
a = 0
b = 0
c = 1
def func_1(a, b, c):
b = b + a
loop1 = 0
while loop1 < 1:
if b >= 100:
print(" ")
print("Add 1!")
print(" ")
c = c + 1
b = b - 100
else:
loop1 = loop1 + 1
a = a - a
print("c is " + str(c))
print("b is " + str(b))
print("a is " + str(a))
return a
return b
return c
a = a + 85
func_1(a, b, c)
a = a + 85
func_1(a, b, c)
print a, b, c
I'm really new to programming so I apologize for any inefficiencies. Thank you for any help and let me know if something doesn't make sense/is unclear.
Couple of things I see here:
First, out of the three return statements at the end of your code, only the first, return a, is executed. A return statement immediately stops execution of the function; return b and return c are never touched.
Second, you're having some confusion with the scope of the variables. a, b, and c defined outside of the function are global variables, while the a, b, and c passed into the function are local to the scope of the function. Thus, any modifications to the variables in your function won't affect the global variables.
You can do two things. First, have a global declaration at the beginning of the function:
def func_1():
global a
global b
global c
# Function body
This indicates to the function that a, b, and c are global variables that exist outside the function. Thus, passing in arguments is no longer needed; the function will search for variables outside the function. However, this is bad programming practice. The better option is to have func_1 return the modified values and reassign the global values to these new values, like so:
def func_1(a, b, c):
# Function body
return (a, b, c)
Then, to call the function and modify the variables outside the function:
a, b, c = func_1(a, b, c)
A couple suggestions:
You have a lot of incrementing going on, and python has specialized syntax for it: b = b + a can be shortened to b += a, c = c + 1 to c += 1, b = b - 100 to b -= 100. Also, simply reset a to 0 with a = 0 instead of subtracting a - a. Also, you don't need print(" "); print() will suffice.
Next, your while loop is unnecessary. Your function only needs to check once whether b >= 100, so there's no need to set up a loop. Increment b and then use a simple if statement to level up if necessary:
def func_1(a, b, c):
b += a
if b >= 100:
print("\nAdd 1!\n")
c += 1
b -= 100
a = 0
# Rest of the function
Inside func_1() the names a,b,c are local. When you change them nothing happens to the external a,b,c. You return the values correctly in the function, but then when calling the function you need to assign the values to the variables like this: a,b,c=func_1(a,b,c).
Returning a value doesn't set it unless you explicitly assign it in the calling function:
a, b, c = func_1(a, b, c)
Assigning inside the function doesn't affect the outer ones because they are considered "local scope". To counter that declare them global to affect the outer variables:
def func_1():
global a
global b
global c
Only one of these should be implemented
It is generally preferred not to declare globals. Ideally you should make a class for all of this, but these two options would require the least refactoring of your existing code
Note that a global variable (the variables outside of the function) are completely separate from the local variables (the variables inside the function).
This doesn't work because you can only return once. When you returned a, the function immediately stopped.
Also, since you didn't set any variable to the returned value, the global variables outside of the loop were unaffected. What you can do is return the a, b, and c values as a tuple, and then set the a, b and c global variables to that tuple.
return (a, b, c)
a, b, c = func_1(a, b, c)

How to update local variables in function with a dictionary?

A problem meeted in rewriting a python program.
I move some previous global variables into a dictionay, so I have to rewrite the functions which have used those variables.
For example,
#old one
a, b = 1, 2
def func(c):
print a+b+c
#new one
d = dict(a=1,b=2)
def func(c,d):
a = d['a']
b = d['b']
print a+b+c
As the dictionary d is large, so I'm seeking a something like this
d = dict(a=1,b=2)
def func(c,d):
locals().update(d)
print a+b+c
I have tried __dict__.update(d), however __dict__ can't be accessed directly.
Or for key,value in d.items():
setattr(obj,key,value)
is possible? If yes, how to set the obj to the function itself?
You can't create local variables programmatically in that way.
Instead, just use the dictionary directly by accessing its keys.
What you could do instead is to evaluate your expression using the dictionary as the local variables:
def func(c,d):
print eval('a+b+c',globals(),dict(d,c=c))
func(3,dict(a=1,b=2))
You could use in clause of an exec statement. In the in clause you may provide namespace(s) for Python to search when executing a string. Documentation
For example:
>>> d = dict(a=1, b=2)
>>> def func(c,d):
exec 'print a+b+c' in d, locals()
>>> func(5,d)
8

Can anyone help me understand Python variable scoping?

I wrote a test program that looked like this:
#!/usr/bin/python
def incrementc():
c = c + 1
def main():
c = 5
incrementc()
main()
print c
I'd think that since I called incrementc within the body of main, all variables from main would pass to incrementc. But when I run this program I get
Traceback (most recent call last):
File "test.py", line 10, in <module>
main()
File "test.py", line 8, in main
incrementc()
File "test.py", line 4, in incrementc
c = c + 1
UnboundLocalError: local variable 'c' referenced before assignment
Why isn't c passing through? And if I want a variable to be referenced by multiple functions, do I have to declare it globally? I read somewhere that global variables are bad.
Thanks!
You're thinking of dynamic scoping. The problem with dynamic scoping is that the behavior of incrementc would depend on previous function calls, which makes it very difficult to reason about the code. Instead most programming languages (also Python) use static scoping: c is visible only within main.
To accomplish what you want, you'd either use a global variable, or, better, pass c as a parameter. Now, because the primitives in Python are immutable, passing an integer can't be changed (it's effectively passed by value), so you'd have to pack it into a container, like a list. Like this:
def increment(l):
l[0] = l[0] + 1
def main():
c = [5]
increment(c)
print c[0]
main()
Or, even simpler:
def increment(l):
return l + 1
def main():
c = 5
print increment(c)
main()
Generally, global variables are bad because they make it very easy to write code that's hard to understand. If you only have these two functions, you can go ahead and make c global because it's still obvious what the code does. If you have more code, it's better to pass the variables as a parameter instead; this way you can more easily see who depends on the global variable.
When a variable is assigned to in a scope, Python assumes it's local for the whole scope unless you tell it otherwise.
So, to get this to work as you think it will, you need to use two global statements:
#!/usr/bin/python
def incrementc():
global c
c = c + 1
def main():
global c
c = 5
incrementc()
main()
print c
Otherwise, you're talking about a local variable named c in both situations.
The normal way to solve this, however, does not involve globals.
#!/usr/bin/python
def incrementc(c):
c = c + 1
return c
def main():
c = 5
c = incrementc(c)
return c
c = main()
print c
Here, in each function and in the global scope, c refers to a different variable, which you are passing around as an argument and with return values. If you wanted only one c, use a class:
class Foo:
def __init__(self, c):
self.c = c
self.incrementc()
def incrementc(self):
self.c = self.c + 1
foo = Foo(5)
print foo.c
The variable c isn't passing through because you do not hand any reference to c to the function incrementc.
What you're looking at here are 3 scopes, the global scope and those within the functions main and incrementc. In main you've properly defined a variable c, but increment c has no knowledge of this - so attempting to increment it is going to fail. Even if those two functions succeeded, trying to print c would fail in the global scope because it has no knowledge of the c you've defined in main.
You have a few options. One way to do this:
def incrementc(c):
c = c + 1
return c
def main():
c = 5
c = incrementc(c)
return c
c = main()
print c
Notice how c is being handed around. Of course, the name doesn't have to be preserved, you very well could write it like this:
def increment(z):
z = z + 1
return z
def main():
bar = 5
bar = increment(bar)
return bar
foo = main()
print foo
Another option that many would probably dislike (for good reason) is to use globals. In that case:
def incrementc():
global c # indicate intention to write to this global, not just read it
c = c + 1
def main():
global c # declares c in global space
c = 5
incrementc()
main()
print c
Any function in which you hope to MODIFY the GLOBAL instance of c, you need to inform the function. So you state, 'global c'. You can READ from the global without doing so. This would ensure (to some extent) that you don't make a mistake and overwrite a value in the global space unintentionally with a similar name, should you decide to use one in the local space of a function.
Hopefully that's clear enough, but feel free to ask for clarification on any point (I'm also open to being corrected if I've mistakenly described any part of this).
Global variables are bad.
Just like friends and enemys. Keep your friends close but keep your enemys even closer.
The function main last a local variable c, assignment the value 5
You then call the function inc..C. The c from main is now out of scope so you are trying to use a value of c that is not in scope - hence the error.

Pythonic way to select first variable that evaluates to True

I have some variables and I want to select the first one that evaluates to True, or else return a default value.
For instance I have a, b, and c. My existing code:
result = a if a else (b if b else (c if c else default))
Another approach I was considering:
result = ([v for v in (a, b, c) if v] + [default])[0]
But they both feel messy, so is there a more Pythonic way?
Did you mean returning first value for what bool(value)==True? Then you can just rely on the fact that boolean operators return last evaluated argument:
result = a or b or c or default
If one variable is not "defined", you can't access its name. So any reference to 'a' raises a NameError Exception.
In the other hand, if you have something like:
a = None
b = None
c = 3
you can do
default = 1
r = a or b or c or default
# r value is 3
So long as default evaluates to True:
result = next((x for x in (a, b, c, d , e, default) if x))
You could do something like this (in contrast to the other answers this is a solution where you don't have to define the 'missing' values as being either None or False):
b = 6
c = 8
def first_defined(items):
for x in items:
try:
return globals()[x]
break
except KeyError:
continue
print first_defined(["a", "b", "c"])
In order to avoid NameErrors when a, b or c isn't defined: give the function a list of strings instead of variable references (you can't pass non-existing references). If you are using variables outside the 'globals()' scope, you could use getattr with its default argument.
--
If a, b and c are defined, I'd go for something like this (considering the fact that an empty string, None or False evaluate to a boolean False):
a = None
b = 6
c = 8
def firstitem(items):
for x in items:
if x:
return x
break
else:
continue
print firstitem([a, b, c])
Don't know if this works in every case, but this works for this case.
a = False
b = "b"
c = False
default = "default"
print a or b or c or default # b
How about this ?
a=None
b=None
c=None
val= reduce(lambda x,y:x or y,(a,b,c,"default"))
print val
The above prints "default". If any of the inputs is defined, val would contain the first defined input.
If by defined you mean ever assigned any value whatsoever to in any scope accessible from here, then trying to access an "undefined" variable will raise a NameError exception (or some subclass thereof, but catching NameError will catch the subclass too). So, the simplest way to perform, literally, the absolutely weird task you ask about, is:
for varname in ('a', 'b', 'c'):
try: return eval(varname)
except NameError: pass
return default
Any alleged solution lacking a try/except won't work under the above meaning for "defined". Approaches based on exploring specific scopes will either miss other scopes, or be quite complex by trying to replicate the scope-ordering logic that eval does for you so simply.
If by "defined" you actually mean "assigned a value that evaluates to true (as opposed to false)", i.e., all values are actually defined (but might happen to be false, and you want the first true value instead), then the already-proposed a or b or c or default becomes the simplest approach. But that's a totally different (and even weirder!) meaning for the word "defined"!-)

Categories

Resources