The simpler version of my problem looks like the following:
def function_1(x, function):
print(x)
function()
def function_2(y):
print(2*y)
def function_3(z, n):
print(3*z)
print(5 * n)
function_1(5, function_2)
function_1(3, function_3)
My question is when calling function_1, how can i give it an argument which will be added to the called function (function_2 or function_3) also as an argument?
Beginner here. Thanks in advance.
You can use functools.partial:
from functools import partial
...
function_1(5, partial(function_2, 5))
function_1(3, partial(function_3, 2, 3))
partial lets you pass a object to function_1 that can be called with no passed parameters.
As Kacper suggested, Python offers the possibility of using arbitrary positional arguments lists (args) and keyword arguments dicts (kwargs):
def fun_x(x, a_callable, *args, **kwargs):
print(x, a_callable, args, kwargs)
a_callable(*args, **kwargs)
def fun_y(a,b,kwarg1):
print(a,b,kwarg1)
fun_x(3,fun_y, "first", "second", kwarg1="third")
3 <function fun_y at 0x000001D1E0E02378> ('first', 'second') {'kwarg1': 'third'}
first second third
Do you mean by adding x to the parameters?
def function_1(x, function):
print(x)
function(x)
...
If you want to use the same argument x to function_1 as an argument to called function inside then you can use:
def function_1(x, function):
print(x)
function(x)
or you want to use another argument y for function which is called inside then you can use like below:
def function_1(x,y,function):
print(x)
function(y)
Related
As per manual, functools partial() is 'used for partial function application which “freezes” some portion of a function’s arguments and/or keywords resulting in a new object with a simplified signature.'
What's the best way to specify the positions of the arguments that one wishes to evaluate?
EDIT
Note as per comments, the function to be partially evaluated may contain named and unnamed arguments (these functions should be completely arbitrary and may be preexisting)
END EDIT
For example, consider:
def f(x,y,z):
return x + 2*y + 3*z
Then, using
from functools import partial
both
partial(f,4)(5,6)
and
partial(f,4,5)(6)
give 32.
But what if one wants to evaluate, say the third argument z or the first and third arguments x, and z?
Is there a convenient way to pass the position information to partial, using a decorator or a dict whose keys are the desired arg positions and the respective values are the arg values? eg to pass the x and z positions something like like this:
partial_dict(f,{0:4,2:6})(5)
No, partial is not designed to freeze positional arguments at non-sequential positions.
To achieve the desired behavior outlined in your question, you would have to come up with a wrapper function of your own like this:
def partial_positionals(func, positionals, **keywords):
def wrapper(*args, **kwargs):
arg = iter(args)
return func(*(positionals[i] if i in positionals else next(arg)
for i in range(len(args) + len(positionals))), **{**keywords, **kwargs})
return wrapper
so that:
def f(x, y, z):
return x + 2 * y + 3 * z
print(partial_positionals(f, {0: 4, 2: 6})(5))
outputs:
32
Simply use keyword arguments. Using your definition of f above,
>>> g = partial(f, z=10)
>>> g(2, 4)
40
>>> h = partial(f, y=4, z=10)
>>> h(2)
40
Note that once you use a keyword argument for a given parameter, you must use keyword arguments for all remaining arguments. For example, the following would not be valid:
>>> j = partial(f, x=2, z=10)
>>> j(4)
TypeError: f() got multiple values for argument 'x'
But continuing to use keyword arguments is:
>>> j = partial(f, x=2, z=10)
>>> j(y=4)
40
When you use functools.partial, you store the values of *args and **kwargs for later interpolation. When you later call the "partially applied" function, the implementation of functools.partial effectively adds the previously provided *args and **kwargs to the argument list at the front and end, respectively, as though you had inserted these argument-unpackings yourself. I.e., calling
h = partial(1, z=10)
f(4)
is roughly equivalent to writing
args = [1]
kwargs = {'z': 10}
f(*args, 4, **kwargs)
As such, the semantics of how you provide arguments to functools.partial is the same as how you would need to store arguments in the args and kwargs variables above such that the final call to f is sensible. For more information, take a look at the pseduo-implementation of functools.partial given in the functools module documentation
For easier usage, you can create a new object specifically to specify a positional argument that is to be skipped when sequentially listing values for positional arguments to be frozen with partial:
SKIP = object()
def partial_positionals(func, *positionals, **keywords):
def wrapper(*args, **kwargs):
arg = iter(args)
return func(*(*(next(arg) if i is SKIP else i for i in positionals), *arg),
**{**keywords, **kwargs})
return wrapper
so that:
def f(x, y, z):
return x + 2 * y + 3 * z
print(partial_positionals(f, 4, SKIP, 6)(5))
outputs:
32
I was trying to define a simple sum function in python.
My code is:
def sum_all_num(*args):
return sum(*args)
But I got error. I understand the *args will gather all arguments in a tuple, but why I can not use sum function to sum it?
sum() takes an iterable. Just remove the * and pass the args tuple directly.
Example:
x = 1, 2, 3
print(sum(x))
Results:
6
sum() takes only one argument (an iterable).
So, instead of unpacking args with *, just pass the args variable to it:
def sum_all_num(*args):
return sum(args)
Test:
>>> sum_all_num(1,2,3)
6
I'm newbie in Python, but the second time I encouter this problem.
Problem:
In some libraries there are functions with arguments. Sometimes there is argument as function, like this:
def somefun(fun):
x = [1,2,3]
z = fun(x)
return z
And I want to pass there some other function like this:
def func(x,y):
return x*y
which have more than one argument. I want to make one argument static, so somefun except func as argument.
Finally I want to make some kind of cycle where I can change static arg.
Something like this:
for i in xrange(1,9):
somefun(func(i,*))
Please do not offer me to change any functions. They are from library and it's not very comfortable to change them.
Thanks a lot!
You can use lambda statement:
somefun(lambda x: func(i, x))
It sure sounds like you are looking for functools.partial. From the docs:
functools.partial(func, *args, **keywords)
Return a new partial object which when called will behave like func called with the positional arguments args and keyword arguments keywords.
In your example, you could pass partial(func, 10) as the argument to somefun. Or you could create the partial objects and use them in a loop:
for i in xrange(1,9):
somefun(partial(func, i))
My solution with decorator
from functools import wraps
import numpy as np
def p_decorate(f):
#wraps(f)
def wrapped(*args):
z = f(*args)
return z
return wrapped
#p_decorate
def myfunc(a,b):
"""My new function"""
z = np.dot(a,b)
return z
x = [1,2,3]
y = [4,2,0]
r = myfunc(x,y)
print (r)
print (myfunc.__name__)
print (myfunc.__doc__)
You can change myfunc as you wish.You can also insert more function layers.Without the use of this decorator factory,you would lose the name of myfunc and the docstring.
why this code is printing once "potato" instead than 5 times ?
def print_word(word):
print word
return
def do_n(function , n):
for i in range(n):
function
return
do_n( print_word("potato") , 5 )
Your code is actually not passing print_word("potato") ("the 'call' to the print_word") to do_n, but instead it's passing None since print_word returns None. Which means that the only time that print_word ran was at do_n( print_word("potato") , 5 ). What you can do instead is use functools.partial, which returns a function with the args applied to it:
from functools import partial
def print_word(word):
print(word)
return # side note: the "return" isn't necessary
def do_n(function , n):
for i in range(n):
function() # call the function
return
do_n( partial(print_word,"potato") , 5)
functools.partial:
Return a new partial object which when called will behave like func
called with the positional arguments args and keyword arguments
keywords. If more arguments are supplied to the call, they are
appended to args.
Another way is to use a lambda statement or pass the argument separately:
def print_word(word):
print(word)
return # side note: the "return" isn't necessary
def do_n(function , n):
for i in range(n):
function() # call the function
return
do_n(lambda: print_word("potato"), 5) # use the lambda
Or:
def print_word(word):
print(word)
return # side note: the "return" isn't necessary
def do_n(function , n, *args):
for i in range(n):
function(*args) # call the function
return
do_n(print_word, 5, "potato") # pass the argument of print_word as a separate arg
To pass a function with arguments, you can either pass the arguments separately, or you can do a 'partial' application of the function, where you lock a number of variables. Here is such as solution to your problem, where I have 'partially' applied all variables. But, the function is still not called until the function() statement.
from functools import partial
def print_word(word):
print word
return
def do_n(function, n):
for i in range(n):
function()
return
print_potato = partial(print_word, potato)
do_n(print_potato, 5)
I'm trying to make a function designed to call another function multiple times:
def iterator(iterations, function, *args):
#called as:
iterator(5, my_function, arg1, arg2, arg3)
Note that the number of arguments here is variable: could 1, could be 2, could be 10.
fill them in based on the function that is being called.
def iterator(iterations, function, *args):
for i in range(iteration):
temp = function(args)
return temp
The problem here is:
TypeError: my_function() takes exactly 4 arguments (1 given)
And this is because (arg1, arg2, arg3, arg4) are being treated as a single argument.
How do I get around this?
By using the same syntax when applying the args sequence:
temp = function(*args)
The *args syntax here is closely related to the *args function parameter syntax; instead of capturing an arbitrary number of arguments, using *args in a call expands the sequence to separate arguments.
You may be interested to know that there is a **kwargs syntax too, to capture and apply keyword arguments:
def iterator(iterations, function, *args, **kwargs):
for i in range(iteration):
temp = function(*args, **kwargs)
return temp
Try this, unpacking the argument list (a.k.a. splatting it):
function(*args)
From the example in the documentation, you'll see that this is what you need:
range(3, 6) # ok
range([3, 6]) # won't work
range(*[3, 6]) # it works!