This question already has answers here:
Why can a function modify some arguments as perceived by the caller, but not others?
(13 answers)
Closed 4 years ago.
Sorry if the title is not accurate, not sure how to name it properly.
the question is. If I do this code:
num = 1
def test_func2(arg):
arg = 10
test_func2(num)
print(num)
The num will obviously stay 1
But if I do similar thing with objects
class TestClass:
def __init__(self):
self.one = 1
self.two = 2
test_obj = TestClass()
def test_func(arg):
arg.one = 10
test_func(test_obj)
print(test_obj.one)
The value of test_obj.one will change to 10. Why are integers passed by value and user defined objects by reference?
In the first case, num refers to the integer object of value 1. Passing it to a function assigns arg to also refer to the same integer object of value 1, but then arg is reassigned to a new integer object of value 10. num is still referring to the original integer object of value 1.
In the second case, test_obj as assigned the value of a new TestClass instance. Passing it to a function assigns arg to the same TestClass instance. The object itself is altered, and both arg and test_obj still refer to the same object, so even after the function returns, test_obj "sees" the change.
This happens because -
In first case, after passing a variable to a function test_func2(num), that value like will be caught in a local variable arg, whose scope is local to the function. After we return from function that variable is destroyed as the function has ended its execution.
num = 1
def test_func2(arg): // arg catches value 1 here
arg = 10 // arg variable changed to 10 but this value is not reflected back to num variable
// so num is still 1
test_func2(num) // function called with variable
print(num) // that's why 1 gets printed here
And when you pass object to the function test_func(test_obj), the arg in function will be a reference to the object it catches i.e., test_obj. That means after modifying arg.one = 10 you are actually modifying the original objects value i.e., test_obj.one = 10.
def test_func(arg): // arg points to test_obj
arg.one = 10 // actually changing test_obj.one
test_func(test_obj) // function called with object
print(test_obj.one) //that's why 10 gets printed here
Related
I've created a class called "Test" with attributes "hp" and "max_hp". Now I want to pass these attributes as arguments to an outside function (it is outside the class since I want to use it with other instances of other classes), and add 1 to the attribute if it is not equal to the max attribute. My problem is that even though the function is called and test.hp and test.max_hp passed as arguments, they don't change.
Here's the code:
class Test:
def __init__(self):
self.hp = 10
self.max_hp = self.hp
def recovery_action(attribute, max_attribute):
if attribute == max_attribute:
pass
else:
attribute += 1
test = Test()
test.hp -= 5
print(f'test.hp - {test.hp}')
recovery_action(test.hp, test.max_hp)
print(f'test.hp after recovery - {test.hp}')
The problem is that the output looks like this:
test.hp - 5
test.hp after recovery - 5
I passed the test.hp as an argument and it wasn't equal to test.max_hp, so 1 should have been added to it - but it stayed the same. What am I doing wrong here?
The problem is you are not really changing the attribute in your function.
Basically what you do is call recovery_action(5,10).
If you want to actually change the attribute, you can return the result and assign it, like that:
def recovery_action(attribute, max_attribute):
if attribute != max_attribute:
return attribute + 1
return attribute
Then you can run:
test.hp = recovery_action(test.hp, test.max_hp)
The variable attribute lives in the scope of recovery_action. However, what you would want is to actually reference the class, so that changes are saved outside the scope of the function. A solution could be:
def recovery_action(test: Test):
if test.hp == test.max_hp:
pass
else:
test.hp += 1
This function passes an instance of Test to the function (hence the : Test part). The rest of the function is straightforward. Now, you address the variable in the object you pass to the function and thus remains changed after the function ends.
There is no object representing the attribute itself; test.max_hp is just an expression that evaluates to the value of the attribute. Since an int is immutable, you can't change the value by passing it as an argument. You would need to pass the object and the name of the attribute as two separate arguments, and have the function operate on the object directly.
def recovery_action(obj, attribute, max_):
if getattr(obj, attribute) == max_:
pass
else:
setattr(obj, attribute, attribute + 1)
recovery_action(test, 'hp', test.max_hp)
Note that your function doesn't care that the maximum value comes from an object attribute; it only cares that it is some integer.
Changing the argument of a function will not change the variable you have passed in to that function. You can return the changed variable and reassign the class attribute.
Also maybe creating getter and setter of the attributes is a good idea to access and change attributes of a class.
In general terms, I cannot get any function to return a value inside a variable (an integer, back to the main program. I have included the variable name as one of 2 arguments in the function (in this case mod_stone_value), and am using the return command to hopefully return that integer value to the main program, where that value is then added to a total value.
The key lines of code in the function would be:
def calc_real_stone_value(base_stone_value, mod_stone_value):
return mod_stone_value
and then, back in the main program:
total_stone_value = total_stone_value + mod_stone_value
The variable total_stone_value ends up being 0, yet non-zero values of mod_stone_value do print inside the function. I know I am doing something fundamentally wrong, but have no idea what it is.
If I understand correctly, you want to use the number value returned by the function calc_real_stone_value and are confused as to why it is not changing after you call your function which supposedly "updates it's value".
The fundamental thing you are misunderstanding is that primitive data types (double, int, char, etc.) are passed by value, not by reference. Therefore, if you make changes to the local variable mod_stone_value within the function, the changes will not show in the variable that exists outside the function. If you want to use the value you are returning with the line return mod_stone_value you need to call the function with the variables you want to be used in the calculations and then assign that value to a variable (mod_stone_value in this case).
Example:
base_stone_value = 1
mod_stone_value = 4
def calc_real_stone_value(base_stone_value, mod_stone_value):
# calculations
# e.g. add the two integer arguments
mod_stone_value += base_stone_value
return mod_stone_value
# print the values for mod_stone_value
print(mod_stone_value)
# call function and assign value to mod_stone_value
mod_stone_value = calc_real_stone_value(base_stone_value, mod_stone_value)
print(mod_stone_value)
Output:
4
5
Conclusion:
Primitive variables are passed by value and in order to retrieve the value from a function that returns an integer for example, it needs to be re-assigned.
To reduce confusion, you can avoid using variable names that shadow variable names from an outer scope.
Using your code as a template, here is an example that illustrates passing a value from "inside" the function to the outer level (here, the outermost level of the program). I would recommend reading this part of the official Python tutorial (or from the beginning if it does not make sense) as it describes concepts (e.g. global vs. local variables) that are relevant to your problem.
c = 0
d = 5
e = 2
def foo(a, b):
# insert whatever code here
d = 10
print(f"Inside foo(), d has value {d}")
# to match your example
return b
# c has the value 0, d has the value 5
print(f"(c, d) has the value ({c}, {d})")
# call function and pass return value to c
c = foo(d, e)
# c has the value 2, d has the value 5
print(f"(c, d) has the value ({c}, {d})")
By the way, regarding your code, statements inside the body of the function should be indented. This includes the return statement.
Suppose I have a Python function foo which takes a default argument, where that default is set to some global variable. If I now change that global variable before calling the function, the default argument is still set to the original value of that global variable.
For example:
x = 1
def foo(a=x):
print a
x = 2
foo()
This prints 1, instead of 2.
How should I be writing my code, so that I can change this global variable, and have it update this default argument?
A default variable is only evaluated and set once. So Python makes a copy of the reference and from then on, it always passes that reference as default value. No re-evaluation is done.
You can however solve this by using another object as default object, and then use an if statement to substitute it accordingly. Something like:
the_default = object()
x = 1
def foo(a = the_default):
if a is the_default:
a = x
print a
x = 2
foo()
Note that we use is to perform reference equality. So we check if it is indeed the default_object. You should not use the the_default object somewhere else in your code.
In many Python code, they use None as a default (and thus reduce the number of objects the construct). For instance:
def foo(a = None):
if a is None:
a = x
print a
Note however that if you do that, your program cannot make a distinction between a user calling foo() and foo(None) whereas using something as the_object makes it harder for a user to obtain a reference to that object. This can be useful if None would be a valid candidate as well: if you want foo(None) to print 'None' and not x.
I am learning python 3 and programming in general for the first time, but I can't seem to distinguish a parameter and a variable?
A variable is just something that refers/points to some data you have.
x = 5
Here x is a variable. Variables can point to more kinds of data than just numbers, though. They can point to strings, functions, etc.
A parameter is something that is passed into a function
def my_function(y):
print(y)
Here y is a parameter. It doesn't contain a value yet. But if I want to call the function, I need to provide an argument to the function.
An argument is the actual value you provide to the function that replaces the parameter.
my_function(5)
Here, 5 is the argument. Of course, since x points to the value "5", I can do this too:
my_function(x)
which also prints 5
A parameter is a variable that was received as an argument to a function. Once the function has begun executing, the parameter is just like any other variable; it can be reassigned (and stops being tied to the caller's argument) like anything else.
global_variable = ... still a variable, just defined globally ...
def foo(parameter):
function_local_variable = ... something defined locally ...
foo(... argument that is bound to parameter in foo ...)
A parameter is a type of variable that serves as the input to a method.
A variable is a name (identifier) that refers to some value. Values can be either immutable types, that is, types that cannot be changed such as strings, bytes, integers and floating point numbers:
x = 5 # immutable type
y = x # y points to what x currently points to, namely 5
x = 9 # x points to a new value, 9
print(x, y) # 9 5
A variable can also name a mutable type:
class MyType:
pass
t1 = MyType()
t1.x = 5 # set member x to 5
t2 = t1 # t2 points to the same object as t1
t1.x = 9
print(t1.x, t2.x) # 9 9
A parameter (or argument) is a value passed to a function:
def foo(x):
print(x)
foo(some_value)
x is an argument to function foo. foo(some_value) invokes function foo with the value some_value as the actual value. The Python interpreter implicitly assigns x = some_value at the entry to function foo. Note that x is a variable is every sense of the word but that it is defined in the scope of foo so that it hides any other definition of x that may exist outside of foo.
This question already has answers here:
"Least Astonishment" and the Mutable Default Argument
(33 answers)
Closed 8 years ago.
#! /usr/bin/python
class my_class:
# 1. __init__
def __init__(self):
self.my_set = set()
# 2. __init__
#def __init__(self, arg_set = set()):
# self.my_set = arg_set
c1 = my_class()
c1.my_set.add('a')
print c1.my_set
c2 = my_class()
c2.my_set.add('b')
print c1.my_set
my_class has 2 ways of defining __init__:
If I use 1st way, output is as expected:
set(['a'])
set(['a'])
If I use 2nd way, output is unexpected:
set(['a'])
set(['a', 'b'])
What is wrong with 2nd way? How can modification of C2 (a separate object), result in modification of c1?
Edit: Updated the question title to reflect specific area of concern
From http://docs.python.org/2/reference/compound_stmts.html#function-definitions
Default parameter values are evaluated when the function definition is
executed. This means that the expression is evaluated once, when the
function is defined, and that the same “pre-computed” value is used
for each call. This is especially important to understand when a
default parameter is a mutable object, such as a list or a dictionary:
if the function modifies the object (e.g. by appending an item to a
list), the default value is in effect modified.
Thats the reason why your second method appends values everytime.
Moreover, modify the second __init__ like this
def __init__(self, arg_set = set()):
print id(arg_set)
self.my_set = arg_set
Now, when you run the code, you ll always get the same address (id function in CPython returns the address of the object in memory). So, default arguments are not created everytime the function is invoked, but when evaluated the first time.
thefourtheye is completely right on the cause of this behavior. If you want to use the second version of your constructor, it should be like this:
def __init__(self, arg_set=None):
if arg_set is None:
self.my_set = set()
else:
self.my_set = arg_set