Is there a way to give Python functions repeating sets of arguments? - python

I know about *args and **kwargs for repeating arguments, but is there a way to create a function with a variable number of sets of arguments?
E.g.
def move(dir1, num1, dir2, num2, dir3, num3, ... dirN, numN):
for a function that would allow you to tell something to move something any number of directions by any number of spaces.

You could accept a list of tuples specifying a sequence of moves instead.
For example:
def move(moves):
for direction, num in moves:
print(direction, num)
move([('N', 1), ('E', 2)])

You could use *params in the function definition, which each parameter is a tuple of a direction and a number:
In [1]: def move(*dir_nums):
...: for direction, number in dir_nums:
...: print(f"{direction=}, {number=}")
...:
...:
...: move(('N',1),('E',2),('W',3))
# direction='N', number=1
# direction='E', number=2
# direction='W', number=3

Related

Send different number of arguments to a function in a Pythonic way [duplicate]

This question already has answers here:
Can a variable number of arguments be passed to a function?
(6 answers)
Closed 3 months ago.
I'm trying to make a small function which calls another function from a library I import, I have 8 similar use cases but I don't want the code to be long and repeating.
each time I send the exact same function and with the same arguments but with different number of them.
Let me show an example of what I mean:
This is my function
def num_pack(num, 8_bytes):
return struct.Struct(">Q Q Q Q Q Q Q Q").pack(num, num, num, num, num, num, num, num)
num is some generic counter, 8_bytes is a variable that runs from 1 to 8.
there are 8 possible options for the function that I use, it depends on the 8_bytes value.
The number of Q in the string should be equal to the number of 8_bytes and the same goes for num.
The naive way to do it is :
def num_pack(num, 8_bytes):
if 8_bytes == 8:
return struct.Struct(">Q Q Q Q Q Q Q Q").pack(num, num, num, num, num, num, num, num)
if 8_bytes == 7:
return struct.Struct(">Q Q Q Q Q Q Q").pack(num, num, num, num, num, num, num)
if 8_bytes == 6:
return struct.Struct(">Q Q Q Q Q Q").pack(num, num, num, num, num, num)
.
.
.
if 8_bytes == 1:
return struct.Struct(">Q").pack(num)
I know how to modify the ">Q" string at each time by I don't know how to change the pack function's number of arguments.
I also know how to do this with eval, but this is bad practice and I don't want to use this method.
I'm sure there is some Pythonic way of doing so,
Thanks in advance !
You can create a function that accepts any amount of arguments using the unpack operater *.
Here is an example of a function that takes any amount of arguments:
def my_awesome_function(*args):
# do awesome job!
print(args)
# depending on len of args do something...
Or if you always have a one argument, but an unknown number of following arguments, you can formulate it like this:
def my_awesome_function(num, *all_other_args):
# my number
print(num)
# do awesome job!
print(all_other_args)
# depending on len of args do something...
Here is full explanation of how to deal with unknown number of args, and more usefull operators: https://www.scaler.com/topics/python/packing-and-unpacking-in-python/
If you are looking to do it the other way around, such that a function deeds all the items in a list as separate arguments. This could be done like this with the * operator as well.
def foo(a, b, c):
print(a, b, c)
values = ['adam', 'dave', 'elon']
# a valid funciton call would be
foo(*values)
# values -> ['adam', 'dave', 'elon']
# *values -> 'adam', 'dave', 'elon'
Hopefully this helps. Please leave a comment if I misunderstood.
Good Luck!

What is the point of using *args when a list of arguments can be used?

Would passing in a list or dictionary of variables be more concise than passing in *args in Python methods?
For example,
def function(a, *argv):
print('First variable:', a)
for k in argv:
print('Additional variable:',k)
is the same as
def function(a, list):
print('First variable:', a)
for k in list:
print('Additional variable:',k)
except a list is passed in the second argument. What I think using *args would often do is to cause additional bugs in the program because the argument length only needs to be longer than the mandatory argument length. Would any please explain situations where *args would be really helpful? Thanks
The first function accepts:
function('hello', 'I', 'am', 'a', 'function')
The second one won't. For the second you'd need:
function('hello', ['I', 'am', 'a', 'function'])
In principle, the first one is used when your function can have an arbitrary number of parameters (think: print), while the second one specifies that there's always a second parameter, which is an iterable (not necessarily a list, despite the name)
Passing *args is useful when you have to extract only some (or none) arguments in first level function and then pass others to other inner function without knowing about the details. e.g.
def inner_func(x, y, z, *args, **kwargs):
# do something with x, y, and z
return output
def outer_func(a, b, *args, **kwargs):
# do something with a and b
# pass the rest arguments to inner function without caring about details
output = inner_func(*args, **kwargs)
# do something with output
return
That is a fair ask as to why *args (or **kwargs) is essentially required when a list (or dict) could do the same task. The key reason to that is when a ** caller of a function does not know the number of arguments beforehand**. I'll try to explain this with reference to the particular scenario you have shared.
Lets suppose that we have the below function which finds the sum of all integers passed in. (I'm giving up sum builtin function for demonstration purpose, please bear with me :) )
def do_add(*int_args):
total = 0
for num in int_args:
total += num
return total
And you want to call this for an unknown number of arguments with an unknown number of times.
If in case you need to send a list argument, the do_add function might look like below:
def do_add(int_list):
total = 0
for num in int_list:
total += 0
return total
l1 = [1, 2, 3, 4, ... n] # memory loaded with n int objects
do_add(l1)
l2 = [10, 20, ... n]
do_add(l2)
Firstly, you are loading the memory with an additional list object created just for the sake of function call. Secondly, if you have to add some more items to the list we may need to call another list method such as append or extend.
But if you follow the *args approach, you can avoid creating an extra list and focus only on the function call. If you need to add more arguments you can just add another argument separated by a comma rather than calling append or extend methods.
Assume that you want to call this function n times with 1000 arguments. It will result in n * 1000 new list objects to be created every time. But with the variable arguments approach, you can just call it directly.
do_add(1, 2, 3) # call 1
do_add(10.0, 20.0, 30.0) # call 2
...
do_add(x, y, z, ..., m) # call n

python multiple output variables as input

def function_1(a,b,c,d):
print('{}{}{}{}'.format(a,b,c,d))
return
def function_2():
t=y=u=i= 5
return t,y,u,i
function_1(function_2())
I expect that python would execute function 2 first, and return each t, y, u and i as inputs to function1, but instead I get:
TypeError: function_1() missing 3 required positional arguments: 'b', 'c', and 'd'
I understand that either the output of function2 is in a single object, or it is treating function2 as an input function, instead of executing.
how do I change my code to execute as expected? (each of the output variables from function2 treated as input variables to function1)
You need a splat operator.
function_1(*function_2())
function_2() returns a tuple (5, 5, 5, 5). To pass that as a set of four parameters (rather than one four-element tuple as one parameter), you use the splat operator *
Closely related is this question
This might be a slightly awkward way of doing it, but you can actually pss the function as a parameter itself, and then decompose it!
def function_1(func):
print('{}{}{}{}'.format(*func))
return
def function_2():
t=y=u=i= 5
return t,y,u,i
function_1(function_2())
output:
5555

Most pythonic way to write a function to either pass in arguments or a tuple of arguments

What is a most pythonic way to write a function to either pass in arguments or a tuple/list of arguments?
For example, a function add could either take in an argument of add(1, 2) or add((1, 2)) and both output 3.
What I have so far: (it works, but does not look nice)
def add(*args):
if len(args) == 1:
return (args[0][0] + args[0][1])
if len(args) == 2:
return args[0] + args[1]
else:
print "error: add takes in one or two arguments"
What I don't like about it is:
I have to print the error about passing in one or two arguments
The args[0][0] looks very unreadable
This way, it is hard to tell what the arguments passed in represent (they don't have names)
I dont know if this is the most "pythonic" way but it will do what you want:
def add(a, b=None):
return a+b if b is not None else sum(a)
If your function takes a specific number of arguments, then the most pythonic way to do this is to not do it. Rather if the user has a tuple with the arguments, you make them unpack them when they call the function. E.g.
def add(a, b):
return a + b
Then the caller can do
add(1,2)
or
t = (1,2)
add(*t)
The only time you want to accept either a sequence of params or individual params is when you can have any arbitrary (non-zero) number of arguments (e.g. the max and min builtin functions) in which case you'd just use *args
If you can only take a finite number of arguments, it makes more sense to ask for those specifically. If you can accept an arbitrary number of arguments, then the *args paradigm works well if you loop through it. Mixing and matching those two aren't very elegant.
def add(*args):
total = 0
for i in args:
total += i
return total
>>> add(1, 2, 3)
6
(I know we could just use sum() there, but I'm trying to make it look a bit more general)
In the spirit of python duck typing, if you see 1 argument, assume its something that expands to 2 arguments. If its then 2, assume its two things that add together. If it violates your rules, raise an exception like python would do on a function call.
def add(*args):
if len(args) == 1:
args = args[0]
if len(args) != 2:
raise TypeError("add takes 2 arguments or a tuple of 2 arguments")
return args[0] + args[1]
A decorator would be best suited for this job.
from functools import wraps
def tupled_arguments(f):
#wraps(f) # keeps name, docstring etc. of f
def accepts_tuple(tup, *args):
if not args: # only one argument given
return f(*tup)
return f(tup, *args)
return accepts_tuple
#tupled_arguments
def add(a, b):
return a + b

optional arguments function

I am searching how I could use optional arguments in python.
I have read this question but it is not clear to me.
Lets say I have a function f that can take 1 or more arguments to understand time series. Am i obliged to specify the number of arguments and set default values for each argument?
What I aim to do is being able to write a function this way:
simple function:
def f(x,y):
return x + y
#f(1,2) returns 3
What i want is also f(1,2,3) to return me 6 and f(7) returning me 7
Is it possible to write it without setting a predefined number of mandatory/optional parameters?
Is it possible to write it without having to set default values to 0 ?
How to write this function?
Its a simple example with numbers but the function i need to write is comparing a set of successive objects. After comparison is done, the data set will feed a neural network.
Thanks for reading.
EDIT:
Objects I am feeding my function with are tuples like this (float,float,float,bool,string)
You can put *args in your function and then take arbitrary (non-keyword) arguments. *args is a tuple, so you can iterate over it like any Python tuple/list/iterable. IE:
def f(*args):
theSum = 0
for arg in args:
theSum += arg
return theSum
print f(1,2,3,4)
def f(*args):
"""
>>> f(1, 2)
3
>>> f(7)
7
>>> f(1, 2, 3)
6
>>> f(1, 2, 3, 4, 5, 6)
21
"""
return sum(args)
If you need to do something more complicated than sum you could just iterate over args like this:
def f(*args):
r = 0
for arg in args:
r += arg
return r
See this question for more information on *args and **kwargs
Also see this sections on the Python tutorial: Arbitray Argument List
You can use the follow syntax:
def f(*args):
return sum(args)
The * before args tells it to "swallow up" all arguments, makng args a tuple. You can also mix this form with standard arguments, as long as the *args goes last. For example:
def g(a,b,*args):
return a * b * sum(args)
The first example uses the built-in sum function to total up the arguments. sum takes a sequence as adds it up for you:
>>> sum([1,3,5])
9
>>> sum(range(100))
4950
The args name is not mandatory but is used by convention so best to stick with it. There is also **kwargs for undefined keyword arguments.

Categories