Argument Unpacking while Defining Function in Python - python

I am trying to pass a list into the definition of a function in order to create new variables. The use case here is to run scipy's curve fit to find optimal parameters of the function. I want this function to be able to take any number of variables dynamically without specifically typing in all the variables I want it to optimize/solve for (b1, rate_1, etc.). Right now I have a list of the variables to include but can't seem to get the function to create them as new parameters in the function definition, which it looks like I need to do.
I'm familiar with using * in a function as seen below, but it seems that is for when the function is already defined and you're calling it. I want to do something similar but in the definition of the function so the function itself recognizes b1, rate_1, etc. as parameters that it can solve for using curve_fit.
My starter code:
def get_optimal_adstock_multivariate(x_var_names):
y = np.array(final_df['Count Of Solutions'])
# make list of coefficient variables (b1, b2, etc.) and make new variables for each rate (rate_1, rate_2, etc.)
coef_vars = []
rates = []
for i in range(0, len(x_var_names)):
coef_vars.append("b" + str(i+1))
rates.append("rate_" + str(i+1))
coef_vars_rates = coef_vars + rates
def f(final_df, b0, *coef_vars_rates): # using * to pass b1, rate_1, b2, rate_2, etc. as parameters (unpacking the list)
# need this function to recognize final_df, b0, b1, rate_1, etc. as variables

You cannot directly make the function recognize these variables unfortunately.
You can use keyword arguments with a double-* and address them from the resulting dictionary object:
def f(a, b, **kwargs):
... kwargs["b0"] ...
Posting this in hope that it might help you, though I am aware it's not a full solution to your problem.

I don't understand what you are trying to do with x_var_names.
We can define equivalent functions:
In [260]: a, b, c = 1,2,3
In [261]: def foo0(x, *args):
...: print(x, args)
...:
In [262]: def foo1(x, a,b,c):
...: print(x, a,b,c)
...:
and call them a list of variables or the actual variables:
In [263]: alist=[a,b,c]
In [264]: foo0(100,*alist)
100 (1, 2, 3)
In [265]: foo1(100,*alist)
100 1 2 3
In [266]: foo0(100,a,b,c)
100 (1, 2, 3)
In [267]: foo1(100,a,b,c)
100 1 2 3
Or if I refine the print in foo0 I get the same display in both:
In [268]: def foo2(x, *args):
...: print(x, *args)
...:
In [269]: foo2(100,a,b,c)
100 1 2 3
Look at curve_fit. It will work with either foo1 or foo2 signatures.
f(xdata, *params)
The number of params is determined by p0, though it also talks about determining them by introspection. By that I think it can deduce that foo1 expects 3 values.
Don't confuse variable names in the global environment with the variables, or tuple of values passed via curve_fit to your function. Local names can also be different. For example
def foo3(x, *args):
a,b,c = args
print(x, a,b,c)
uses local unpacking, where the a,b,c are just convenient ways of referencing the values in the function.

Related

Arguments of function?

I am learning to program, and I had a question regarding the functions.
Basically because I should use arguments in functions if I can get all the result by doing everything in the function. What are the benefits? Is it good practice?
Sorry if the question is very basic.
Here an example using python:
num1,num2 = 2,3
def sum(a,b):
z= a+b
print(z)
sum(num1,num2)
def sum():
a,b = 2,3
z= a+b
print(z)
sum()
In theory, both functions do the same, but in which cases is it advisable to use arguments or not?
If you are going to be calling the function just once, we could say there is not benefit from it. Now, let's say you want to perform the same operation multiple times, but with different input data. Then, you see the improvement:
def sum(a, b):
z = a + b
print(z)
sum(1, 2)
sum(2, 3)
sum(4, 5)
would be better than:
z = 1 + 2
print(z)
z = 2 + 3
print(z)
z = 4 + 5
print(z)
It depends on the application of use. If it needs some dynamic content like in the first case
num1,num2 = 2,3
def sum(a,b):
z= a+b
print(z)
sum(num1,num2)
but if it is some basic content,
use the second one.
def sum():
a,b = 2,3
z= a+b
print(z)
sum()
The main benefit of adding arguments to a function is that you can use the function multiple times with different arguments every time.
This can be very practical when you are building a calculator program, for example, and want to be able to find the sum of any two numbers not only of specific ones.
p.s.: If you want to use the functionality of your function only once, maybe you should consider not using a function at all (unless you need to find the sum of specific numbers a few times, in which case, as said, you wouldn't use arguments)

Pass additional variables to a function variable

I am using a function in TensorFlow which maps a set of tensors to another arrangement of tensors. For example, you might write:
data = data.map(_function)
def _function(a, b, c):
return (a + 1, b, c)
So here, you pass _function as a function variable to map, and map passes it three tensors, which are mutated in some way (here, just adding one) and returned.
My question is: Is there a way to pass in additional variables to _function?
If I want to perform a + x, and not a + 1, then how could I pass in the additional variable?
You can't do something like: data.map(_function(x)) because then you're passing the result of a function, not the function itself.
I've experimented with *arg, but I can't find a way. Any help is greatly appreciated.
You can do sth like
def extra_func(x):
def _function(a, b, c):
return (a + x, b, c)
return _function
So you can do data.map(extra_func(x))
or you can use functools.partial to fix some of a function params

Python Scipy accesses local variables in another function?

In Scipy, one-dimensional integration of a function with multiple parameters is achieved by specifying the constant parameters in the function definition through the args argument.
This example is from the Scipy Reference Guide:
>>> from scipy.integrate import quad
>>> def integrand(x, a, b):
... return a * x + b
>>> a = 2
>>> b = 1
>>> I = quad(integrand, 0, 1, args=(a,b))
>>> I = (2.0, 2.220446049250313e-14)
https://docs.scipy.org/doc/scipy/reference/tutorial/integrate.html
I always thought that local variables defined within functions are inaccessible outside the definition. Here, this seems not true since quad only requires integrad as the function argument, and it automatically knows that the variables used are (x, a, b) (and hence (a, b) are taken as parameters in the integration).
What is happening here? What am I missing?
quad doesn't know anything about what variables integrand uses. It doesn't know that the arguments are called a and b. It only sees the values of the global variables a and b, and it passes those values positionally to the integrand. In other words, the code would work the same if you did
x = 2
y = 1
I = quad(integrand, 0, 1, args=(x,y))
quad does not even know how many arguments integrand accepts, other than that it accepts at least one. quad passes the value of the variable of integration as the first argument. You, the user, have to know that, and pass the right number. If you don't (for instance if you didn't pass args, or just passed one arg), you'll get an error. quad just blindly passes along whatever arguments you give it.

passing one list of values instead of mutiple arguments to a function?

Lets say there's a function func() which takes two arguments, a and b. Is there some kind of technique in Python to pass a single list mylist which has both values to the function instead?
def myfunc(a, b):
return a+b
myfunc([1, 2])
If one was completely sure that he was always calling the same function and knew how many arguments it takes, one could do something like this:
mylist = [1, 2]
a, b = mylist
myfunc(a, b)
But what if you have lists you need to feed to certain functions, and each has different amount of arguments?
One could write separate lines of code for each of his functions to unpack the lists into variables and pass them to the corresponding functions, but if Python has something built-in to pass a single list instead of individual argument values (when being sure beforehand that list has the corresponding amount of values), then that would look a lot better and most importantly would require far less lines of code.
You can use * to unpack the list into arguments:
myfunc(*mylist)
def myfunc(a, b):
return a+b
mylist = [1, 2]
myfunc(*mylist)
Here mylist can be list, tuple, string etc of length 2.

Ignore python multiple return value

Say I have a Python function that returns multiple values in a tuple:
def func():
return 1, 2
Is there a nice way to ignore one of the results rather than just assigning to a temporary variable? Say if I was only interested in the first value, is there a better way than this:
x, temp = func()
You can use x = func()[0] to return the first value, x = func()[1] to return the second, and so on.
If you want to get multiple values at a time, use something like x, y = func()[2:4].
One common convention is to use a "_" as a variable name for the elements of the tuple you wish to ignore. For instance:
def f():
return 1, 2, 3
_, _, x = f()
If you're using Python 3, you can you use the star before a variable (on the left side of an assignment) to have it be a list in unpacking.
# Example 1: a is 1 and b is [2, 3]
a, *b = [1, 2, 3]
# Example 2: a is 1, b is [2, 3], and c is 4
a, *b, c = [1, 2, 3, 4]
# Example 3: b is [1, 2] and c is 3
*b, c = [1, 2, 3]
# Example 4: a is 1 and b is []
a, *b = [1]
The common practice is to use the dummy variable _ (single underscore), as many have indicated here before.
However, to avoid collisions with other uses of that variable name (see this response) it might be a better practice to use __ (double underscore) instead as a throwaway variable, as pointed by ncoghlan. E.g.:
x, __ = func()
Remember, when you return more than one item, you're really returning a tuple. So you can do things like this:
def func():
return 1, 2
print func()[0] # prints 1
print func()[1] # prints 2
The best solution probably is to name things instead of returning meaningless tuples (unless there is some logic behind the order of the returned items). You can for example use a dictionary:
def func():
return {'lat': 1, 'lng': 2}
latitude = func()['lat']
You could even use namedtuple if you want to add extra information about what you are returning (it's not just a dictionary, it's a pair of coordinates):
from collections import namedtuple
Coordinates = namedtuple('Coordinates', ['lat', 'lng'])
def func():
return Coordinates(lat=1, lng=2)
latitude = func().lat
If the objects within your dictionary/tuple are strongly tied together then it may be a good idea to even define a class for it. That way you'll also be able to define more complex operations. A natural question that follows is: When should I be using classes in Python?
Most recent versions of python (≥ 3.7) have dataclasses which you can use to define classes with very few lines of code:
from dataclasses import dataclass
#dataclass
class Coordinates:
lat: float = 0
lng: float = 0
def func():
return Coordinates(lat=1, lng=2)
latitude = func().lat
The primary advantage of dataclasses over namedtuple is that its easier to extend, but there are other differences. Note that by default, dataclasses are mutable, but you can use #dataclass(frozen=True) instead of #dataclass to force them being immutable.
Here is a video that might help you pick the right data class for your use case.
Three simple choices.
Obvious
x, _ = func()
x, junk = func()
Hideous
x = func()[0]
And there are ways to do this with a decorator.
def val0( aFunc ):
def pick0( *args, **kw ):
return aFunc(*args,**kw)[0]
return pick0
func0= val0(func)
This seems like the best choice to me:
val1, val2, ignored1, ignored2 = some_function()
It's not cryptic or ugly (like the func()[index] method), and clearly states your purpose.
If this is a function that you use all the time but always discard the second argument, I would argue that it is less messy to create an alias for the function without the second return value using lambda.
def func():
return 1, 2
func_ = lambda: func()[0]
func_() # Prints 1
This is not a direct answer to the question. Rather it answers this question: "How do I choose a specific function output from many possible options?".
If you are able to write the function (ie, it is not in a library you cannot modify), then add an input argument that indicates what you want out of the function. Make it a named argument with a default value so in the "common case" you don't even have to specify it.
def fancy_function( arg1, arg2, return_type=1 ):
ret_val = None
if( 1 == return_type ):
ret_val = arg1 + arg2
elif( 2 == return_type ):
ret_val = [ arg1, arg2, arg1 * arg2 ]
else:
ret_val = ( arg1, arg2, arg1 + arg2, arg1 * arg2 )
return( ret_val )
This method gives the function "advanced warning" regarding the desired output. Consequently it can skip unneeded processing and only do the work necessary to get your desired output. Also because Python does dynamic typing, the return type can change. Notice how the example returns a scalar, a list or a tuple... whatever you like!
When you have many output from a function and you don't want to call it multiple times, I think the clearest way for selecting the results would be :
results = fct()
a,b = [results[i] for i in list_of_index]
As a minimum working example, also demonstrating that the function is called only once :
def fct(a):
b=a*2
c=a+2
d=a+b
e=b*2
f=a*a
print("fct called")
return[a,b,c,d,e,f]
results=fct(3)
> fct called
x,y = [results[i] for i in [1,4]]
And the values are as expected :
results
> [3,6,5,9,12,9]
x
> 6
y
> 12
For convenience, Python list indexes can also be used :
x,y = [results[i] for i in [0,-2]]
Returns : a = 3 and b = 12
It is possible to ignore every variable except the first with less syntax if you like. If we take your example,
# The function you are calling.
def func():
return 1, 2
# You seem to only be interested in the first output.
x, temp = func()
I have found the following to works,
x, *_ = func()
This approach "unpacks" with * all other variables into a "throwaway" variable _. This has the benefit of assigning the one variable you want and ignoring all variables behind it.
However, in many cases you may want an output that is not the first output of the function. In these cases, it is probably best to indicate this by using the func()[i] where i is the index location of the output you desire. In your case,
# i == 0 because of zero-index.
x = func()[0]
As a side note, if you want to get fancy in Python 3, you could do something like this,
# This works the other way around.
*_, y = func()
Your function only outputs two potential variables, so this does not look too powerful until you have a case like this,
def func():
return 1, 2, 3, 4
# I only want the first and last.
x, *_, d = func()

Categories

Resources