Passing mulitple and varied function arguments in Python - python

Firstly I have already seen a number of similar questions, although they were not exactly my question. I am already familiar with *args and **kwargs.
Question Explanation:
I usually use positional arguments when calling a function. However, I often find myself needing to pass a plethora of arguments into a function, so using positional arguments has gotten quite burdensome. I have also found myself needing to pass a varied number of variables to a function that can accept more or other variables if needed.
How do I pass many arguments into a function that is able to take a varied number of arguments ?
I have tried to create an example that is as basic as possible. The functions just perform some arithmetic operations on some variables, and then prints them out.
a = 10
b = 20
c = 30
def firstFunction(*args):
d = a *2
e = b /2
f = c +2
x = d -10
y = e -10
z = f -10
h = 1 #or 2
secondFunction(d,e,f,h)
thirdFunction(x,y,z,h)
def secondFunction(d,e,f,h):
if h == 1:
print d
print e
print f
def thirdFunction(x,y,z,h):
if h == 2:
print x
print y
print z
firstFunction(b,c,a)
And the results produced, as expected, for h=1 and h=2 respectively, are:
20
10
32
10
0
22
Now lets say I want to combine the second and third functions together, so I need to just call one function instead of two. The function would be, in this case:
def combinedFunction(d,e,f,h,x,y,z):
if h == 1:
print d
print e
print f
if h == 2:
print x
print y
print z
and would be called by: combinedFunction(d,e,f,h,x,y,z). As you can imagine, this can get extremely annoying for much more complicated functions. Also I am passing many different arguments that are not going to be used at all, and each of them has to be first declared. For example, in the example, if h = 1, x,y and z have to still be passed into the function, and maybe the value of one of them hasn't been determined as yet(in this simple case it is). I can't use 'combinedFunction(*args)' because not every argument is globally defined.
TLDR:
Basically I want the following:
def someFunction(accepts any number of arguments **and** in any order):
# does some stuff using those arguments it received at the time it was called
# it can accept many more parameters if needed
# it won't need to do stuff to a variable that hasn't been passed through
and this function is called by:
someFunction(sends any number of arguments **and** in any order)
# can call the function again using different arguments and a
# different number of arguments if needed
Can this be easily achieved?

Using global variables from inside a function is generally a bad approach. Instead of it you can use **kwargs this way:
def firstFunction(**kwargs):
d = kwargs.get('a') * 2
secondFunction(d=d, **kwargs)
thirdFunction(e=1, **kwargs)
def secondFunction(d, **kwargs):
print d
print kwargs.get('a')
def thirdFunction(**kwargs):
print kwargs.get('e')
firstFunction(a=1, b=3, q=42) # q will not be used

You could use a dict to pass the data to the functions, but it does makes it less intuitive when programming.
Each function could the convert the dict arguments as you please.
def func_a(input_dict):
a = input_dict["a"]
b = input_dict["b"]
print(a+b)
def func_b(input_dict):
c = input_dict["c"]
d = input_dict["d"]
print(c+d)
def combined_func(input_dict):
func_a(input_dict)
func_b(input_dict)
This is quite similar to kwargs, so it might not be what you are looking for.

If I understood correctly what you are looking for:
def something(*args):
for i in args:
print(i)
some_args = range(10)
something(*some_args)
print('-----------')
something(*some_args[::-1]) # reverse order
Output:
0
1
2
3
4
5
6
7
8
9
-----------
9
8
7
6
5
4
3
2
1
0

Related

Functions as a argument

Just started learning python and came across this
def f(g):
return g(2)
def square(x):
return x ** 2
print(f(square)) # which gives 4
How does inputting square into the f function equate to 4?
When you call a function, the value of the argument is assigned to the named parameter. The call
print(f(square))
can be thought of as "expanding" to
g = square
print(g(2))
Calling f(square) is square(2) which is 2**2 so 4
In python functions are first-class values, considered objects as anything else, hence you can pass them as parameters to another function.
In your case, you are "composing" f with g, which in turn has an invocation with fixed 2 inside f. In this sense, f is a function whose purpose is to feed 2 into another function, given as argument.
Knowing that g squares a number, it's easy to see how g(2) is 4, then f(g) will return 4.

Did the swap happened?

I found this code in CodeChef twitter https://twitter.com/codechef/status/941329495046459395 . It was written in C. I did it in Python3. Here is my code:
def vegas(a,b):
temp = a
a = b
b = temp
a = 6
b = 9
print(a,b)
vegas(a,b)
print(a,b)
And this is the answer:
6 9
6 9
My question is, why my 'vegas' function is not swapping the value of variable 'a' and 'b'
It won't work the way you intend it for work. This question is answering this in full. In short: Python turns the arguments a and b into two variables which are only visible in vegas. They are initiated with the values of a and b but then have no relation to the outside a and b variables.
To make your code work, do this:
def vegas(a,b):
temp = a
a = b
b = temp
return a,b
a = 6
b = 9
print(a,b)
a,b = vegas(a,b)
print(a,b)
Also, you might be interested to know that you can swap two values with a,b = b,a
This code snippet is a joke that "what happens in vegas stays in vegas" because the function doesn't affect the values of the variables. To affects the values, the function needs to return the result of the swap. Without a return statement, the function will not affect variables since the function creates its own temporary variables to be used within the function.
Yes and no...
The function vegas do the work but never returns a and b so a and b still 6 and 9 outside. Arguments are passed by assignment in Python.
You can see more here

Deep copy index integer using lambda

func = []
for value in range(5):
func.append(lambda: print_index(value))
def print_index(index_to_print):
print(index_to_print)
for a in func:
a()
When I run the code above, the output is
4
4
4
4
4
Why is it not ?
0
1
2
3
4
What can I do to make it like the above.
I have tried importing copy and using copy.deepcopy(index). I didn't work probably since index is an integer.
Is lambda part of the reason it is not working.
Thank you for your help!
Not quite sure what you are trying to achieve but the reason that it is printing all 4s is because Python uses dynamic name resolution of variables, that is the value of value when the functions are executed (not when it is declared) is used.
If you really need a function to print the value then you need to create a closure, which means creating the function inside another function, e.g.:
def make_closure(v):
return lambda: print_index(v)
func = []
for value in range(5):
func.append(make_closure(value))
Now this outputs:
In []:
for a in func:
a()
Out[]:
0
1
2
3
4
As #AChampion said, Python calculates the value of value when it needs to and not while executing the loop.
However for me it seemed easier to do the following fix.
func = []
for value in range(5):
func.append(lambda v = value: print_index(v))
def print_index(index_to_print):
print(index_to_print)
for a in func:
a()
This is setting the default argument in the function which gets calculated during the loop execution. The code above is the same as saying :
func = []
for value in range(5):
def anonymous_function(v=value):
print_index(v)
func.append(anonymous_function)
def print_index(index_to_print):
print(index_to_print)
for a in func:
a()

Odd threading behavior in python

I have a problem where I need to pass the index of an array to a function which I define inline. The function then gets passed as a parameter to another function which will eventually call it as a callback.
The thing is, when the code gets called, the value of the index is all wrong. I eventually solved this by creating an ugly workaround but I am interested in understanding what is happening here. I created a minimal example to demonstrate the problem:
from __future__ import print_function
import threading
def works_as_expected():
for i in range(10):
run_in_thread(lambda: print('the number is: {}'.format(i)))
def not_as_expected():
for i in range(10):
run_later_in_thread(lambda: print('the number is: {}'.format(i)))
def run_in_thread(f):
threading.Thread(target=f).start()
threads_to_run_later = []
def run_later_in_thread(f):
threads_to_run_later.append(threading.Thread(target=f))
print('this works as expected:\n')
works_as_expected()
print('\nthis does not work as expected:\n')
not_as_expected()
for t in threads_to_run_later: t.start()
Here is the output:
this works as expected:
the number is: 0
the number is: 1
the number is: 2
the number is: 3
the number is: 4
the number is: 6
the number is: 7
the number is: 7
the number is: 8
the number is: 9
this does not work as expected:
the number is: 9
the number is: 9
the number is: 9
the number is: 9
the number is: 9
the number is: 9
the number is: 9
the number is: 9
the number is: 9
the number is: 9
Can someone explain what is happening here? I assume it has to do with enclosing scope or something, but an answer with a reference that explains this dark (to me) corner of python scoping would be valuable to me.
I'm running this on python 2.7.11
This is a result of how closures and scopes work in python.
What is happening is that i is bound within the scope of the not_as_expected function. So even though you're feeding a lambda function to the thread, the variable it's using is being shared between each lambda and each thread.
Consider this example:
def make_function():
i = 1
def inside_function():
print i
i = 2
return inside_function
f = make_function()
f()
What number do you think it will print? The i = 1 before the function was defined or the i = 2 after?
It's going to print the current value of i (i.e. 2). It doesn't matter what the value of i was when the function was made, it's always going to use the current value. The same thing is happening with your lambda functions.
Even in your expected results you can see it didn't always work right, it skipped 5 and displayed 7 twice. What is happening in that case is that each lambda is usually running before the loop gets to the next iteration. But in some cases (like the 5) the loop manages to get through two iterations before control is passed to one of the other threads, and i increments twice and a number is skipped. In other cases (like the 7) two threads manage to run while the loop is still in the same iteration and since i doesn't change between the two threads, the same value gets printed.
If you instead did this:
def function_maker(i):
return lambda: print('the number is: {}'.format(i))
def not_as_expected():
for i in range(10):
run_later_in_thread(function_maker(i))
The i variable gets bound inside function_maker along with the lambda function. Each lambda function will be referencing a different variable, and it will work as expected.
A closure in Python captures the free variables, not their current values at the time of the creation of the closure. For example:
def capture_test():
i = 1
def foo():
return i
def bar():
return i
print(foo(), bar()) # 1 1
i = 2
print(foo(), bar()) # 2 2
In Python you can also capture variables and write to them:
def incdec():
counter = 0
def inc(x):
nonlocal counter
counter += x
return counter
def dec(x):
nonlocal counter
counter -= x
return counter
return inc, dec
i1, d1 = incdec()
i2, d2 = incdec()
print(i1(10), i1(20), d1(3)) # 10 30 27
print(i2(100), d2(5), d2(20)) # 100 95 75
print(i1(7), d2(9)) # 34 66
As you see incdec returns a pair of two closures that captured the same variable and that are incrementing/decrementing it. The variable shared by i1/d1 is however different from the variable shared by i2/d2.
One common mistake is for example to expect that
L = []
for i in range(10):
L.append(lambda : i)
for x in L:
print(x())
will display the numbers from 0 to 9... all of the unnamed closures here captured the same variable i used to loop and all of them will return the same value when called.
The common Python idiom to solve this problem is
L.append(lambda i=i: i)
i.e. using the fact that default values for parameters are evaluated at the time the function is created. With this approach each closure will return a different value because they're returning their private local variable (a parameter that has a default).

Make function change variables in main code

I want to make a function that does the following:
def func(*args):
for arg in args:
arg+=1
a = 5
b = 6
c = 7
func(a,b,c)
print("%i,%i,%i"%(a,b,c))
I want it to return:
6,7,8
How would I do this?
You can’t! Ta-da.
Python does not support pass-by-reference in any form. Return values instead:
def func(*args):
return [arg + 1 for arg in args]
a = 5
b = 6
c = 7
a, b, c = func(a, b, c)
print("%i,%i,%i" % (a, b, c))
You can't do this, because when you pass in a variable, it takes it in as its value, not as its variable.
Instead, return the value:
def func(*args):
args = list(args)
for i in range(len(args)):
args[i]+=1
return args
a = 5
b = 6
c = 7
a, b, c = func(a,b,c)
print("%i,%i,%i"%(a,b,c))
Which outputs:
>>> print("%i,%i,%i"%(a,b,c))
6,7,8
>>>
You can't, at least not with integer values. Integers are immutable, so you can't change their values, and a function doesn't have access to the namespace of its caller, so you can't rebind the variables (i.e., assign a new value to the variable a outside the function). See this question and various others about what you can and cannot do to affect variables in functions.
If your variables are mutable types like lists, you can achieve a similar effect by mutating the list's value:
def func(*args):
for arg in args:
arg[0] += 1
a = [5]
b = [6]
c = [7]
func(a,b,c)
print("%i,%i,%i"%(a,b,c))
However, you should think about why you want to do this. It may be better to simply return the values and assign them outside the function.
3 posts to tell "You can't" But "Impossible n'est pas français".
Python is the lingua franca of programming languages.
So it's possible:
#!/usr/bin/env python
def func(args):
for i in range(len(args)):
args[i] += 1
abc = [5, 6, 7]
func(abc)
print("%i,%i,%i" % tuple(abc))
actually prints
6,7,8
You can't do it easily because Python doesn't pass immutable objects such as integers by reference. However if you pass the function the names of objects in the current scope, you can achieve your goal like this:
import sys
def func(*args):
namespace = sys._getframe(1).f_globals # caller's globals
for arg in args:
namespace[arg] += 1
a = 5
b = 6
c = 7
func('a','b','c') # note variable *names* passed to function
print("%i,%i,%i" % (a,b,c)) # -> 6,7,8

Categories

Resources