Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why can't I change the variables using kwargs inside a function? [duplicate]

Python beginner's question. I'm trying to change the value of some variables inside a function, and I don't understand why sometimes it works and sometimes it doesn't. So I would like to know what's happening behind the scenes. If I write:

def assign(self, **kwargs):
    
    kwargs['test'] = 3
    kwargs['steps'] += [1]

t = 1
s = []

assign(test=t, steps=s)

print(t)
print(s)

This still prints

1
[]

Now, if I change the function assign to

def assign(self, **kwargs):
    
    kwargs['test'] += 3
    kwargs['steps'] += [1, 2, 3]

it changes the list but not the integer. So I guess this has to do with the fact that integer are immutable and a list is mutable. So then I thought to use a dictionary instead, to make sure that my variables are changed. So then:

dict = {'test':1, 'steps': []}
assign(**dict)
print(dict)

still prints

{'test': 1, 'steps': [1, 2, 3]}

with exactly the same behavior, so now I'm really puzzled. It seems that when unpacking the dictionary, I am not passing references to the dictionary variables anymore so that these unpacked variables are being copied by value? What's the best way to achieve what I try to do then?

UPDATE

Thanks to the discussion with @6502, since

In Python there is no way to change a parameter that has been passed to a function because you cannot have "pointers" or "references".

The most pythonic way is not doing it. A function receive parameters and provide results. If the parameter is a mutable it can mutate its state but changing the call parameter itself was considered not needed.

Then I decided to return a dictionary with the results instead:

def assign(self, **kwargs):
    
    kwargs['test'] += 3
    kwargs['steps'] += [1, 2, 3]
    return kwargs

dict = {'test':1, 'steps': []}
dict = assign(**dict)
print(dict)

This works of course, but I wonder the implications on large data, as it seems to me that (coming from a C++ world), there's a lot of copying around.

like image 571
aaragon Avatar asked Nov 19 '25 04:11

aaragon


2 Answers

The first example is wrong, you get s=[1]:

That's because the list s is a parameter, steps and you change the contents of this list. Much simpler:

def assign(step):
    step += [1] # change the contents of the list

s = []
assign(step=s)
print(s) # gives [1]

That as nothing to do with keyword arguments or dict expansion. If you use ** or give the key words directly as parameters is absolutely the same.

Don't try to pass variables «by reference». That's not possible with python. Use return values instead.

def assign(test, steps):
    return 3, steps + [1]

t, s = assign(3, [])
like image 196
Daniel Avatar answered Nov 21 '25 19:11

Daniel


A simple way to rationalize the semantic is to consider that all Python values are indeed pointers to objects and that those pointers are always passed by value.

If you change the object pointed to the caller can see the mutation, but if you assign the variable you're just changing what it's pointing to and the caller won't notice.

In Python there is no way to change a parameter that has been passed to a function because you cannot have "pointers" or "references". The only way to mutate a variable is using its name.

A work-around (in Python 3) is to pass a closure that can access the local, for example:

def foo(x, y, set_result):
    set_result(x + y)

def bar():
    res = None
    def set_res(x):
        nonlocal res
        res = x
    foo(10, 20, set_res)
    print(res)

This kind of trickery is however rarely needed because in languages with reference parameter passing the most common use is to return multiple values, e.g. (C++)

bool read_xy(double& x, double& y);

where the function needs to return three values, x, y and a success flag.

In Python however this is not needed as you can just write

return x, y

and use it as

x, y = read_xy()
like image 38
6502 Avatar answered Nov 21 '25 18:11

6502



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!