Python function argument passing sequence - python

Following code is incorrect:
def add(a, b, c):
return a + b + c
args = (2, 3)
add(a = 1, *args)
TypeError: add() got multiple values for keyword argument 'a'
I've seen some example in python docs, but I still don't know why there's an error, can anybody explain in detail?

When applying the arguments, Python first fills in the positional arguments, then the keyword arguments.
In your specific case, *args is then applied firsts, so the first positional argument is passed 2, the second is passed 3. The first argument is a here.
Then the a = 1 is applied, and Python finds that you already applied a value to it.
In other words, Python cannot and will not take positional arguments out of consideration when you use one as a keyword argument. Just because you used a as keyword argument does not make it ineligible as a positional argument.

Related

Python - parenthesis used in syntax

Im VERY new to programming. Its my second day. In functions like file.read(), empty parenthesis are part of the syntax. Are they always supposed to be empty, or is there an option to fill them? My script works fine, It's just a question I've always had.
When you define a function, you specify the function's arguments. A function can have zero or more arguments. When a function has zero arguments, or all of its arguments have default values, then you can call the function without passing any arguments.
It depends whether you what them empty (no arguments ) or not (with arguments)
Here is an example of a function
#Let's create a function that can add two values :)
def add(x, y): # the function have positional arguments 'x' 'y'
z = x + y
return z # the function output the value of z
addition = add(5, 33) # call the add() function with arrguments x=5, y=33
print(addition)
#this time variables as arguments
a = 5
b = 33
additon = add(a ,b)
print(addition)
As you can see above that function takes input as arguments and returns the output

Are keyword arguments useful only to increase flexibility over order of arguments?

Arguments in Python are of two types: Keyword arguments and positional arguments.
Consider the following definition of a square_1_sum function. It takes two numbers $a, b$ as input and returns the number $a^2 + b$
def square_1_sum(operand_1, operand_2):
sum_result = operand_1 ** 2 + operand_2
return sum_result
The result of calling function using keyword arguments gives flexibility over order of arguments
square_1_sum(operand_2 = 3, operand_1 = 4)
square_1_sum(operand_1 = 4, operand_2 = 3)
gives the same result of 19.
If we use the positional arguments, they will give different results
square_1_sum(3, 4)
gives 13
square_1_sum(4, 3)
gives 19
so, I am guessing that the only purpose of introducing keyword arguments is to increase flexibility in passing arguments to a function remembering the keywords.
Am I true? Are there any other uses for keyword arguments (compared to positional arguments in which the position of an operand is important)?
They're much more useful for optional arguments.
def my_print(value, sep=' ', end='\n', file=sys.stdout, flush=False):
The keywords allow you to provide only the options you need. If you call the function with positional arguments, you need to supply all the intermediate arguments.
And the real print() takes a variable number of positional parameters. You have to use keyword arguments so it knows where the end of the positional arguments are.

How to pass arguments when using apply in DataFrame

I want to apply a function along a column, eg coco.convert, and pass to it, along with the value in the row, the argument to = 'ISO3'.
df['alpha-3'] = df['Country'].apply(coco.convert('ISO3'))
misses 1 positional argument:
TypeError: convert() missing 1 required positional argument: 'names'
but
df['alpha-3'] = df['Country'].apply(coco.convert)
works fine (I assume it uses default values).
How do I pass the positional argument here?
Also, what exactly is happening here - can someone explain a little how the function is passed to apply?
Probably the most normal way to solve this would be to use the **kwds argument for .apply which are covered in the apply documentation. Basically you can just pass any other named arguments to .apply() and it uses them with the passed function.
df['alpha-3'] = df['Country'].apply(coco.convert, to='ISO3')
An alternative way to do it would be to define your own new function with the arguments pre-passed such as below.
def my_fun(x):
return coco.convert(names = x, to = 'ISO3')
df['alpha-3'] = df['Country'].apply(my_fun)
To your other about how .apply works...
Take the column of data you give it, and loop through each element
For each element, feed its value into the supplied function as the *first
For each return of that function, convert it into another Pandas series.

tuples as function arguments

a tuple in python (in a code block) is defined by the commas; the parentheses are not mandatory (in the cases below). so these three are all equivalent:
a, b = 1, 2
a, b = (1, 2)
(a, b) = 1, 2
if i define a function
def f(a, b):
print(a, b)
calling it this way will work:
f(2, 3)
this will not:
f((2, 3))
# TypeError: f() missing 1 required positional argument: 'b'
how does python treat tuples differently when they are function arguments? here the parentheses are necessary (i understand why this is the case and i am happy python works this way!).
my question is: how does python treat tuples differently when they are function arguments.
For convenience, Python constructs a temporary tuple as needed for an assignment statement. Thus, all three of your assignment statements are exactly the same once they reach data movement.
A function call is not an assignment statement; it's a reference mapping. Therefore, the semantics are different.
If you want Python to unpack your tuple into two separate arguments, use the * operator:
f(*(2, 3))
A tuple behaves like an immutable list. The fact that you notate them with parentheses is perhaps confusing, but it's more or less a coincidence - a result of the fact that parentheses are used for grouping things together and reducing ambiguity otherwise.
When you call a function, you're not providing a tuple. You're providing arguments. A tuple can be an argument, but only one - it's just a variable of type tuple.
What you can do is expand a tuple (or a list) into a a series of arguments with this notation:
tup = (2, 3)
f(*tup)
# expand the tuple (2,3) into a series of arguments 2, 3
You can do that with dictionaries as well, except with ** instead of *:
my_dict = {"arg1": 1, "arg2": 2}
f(arg1=my_dict["arg1"], arg2=my_dict["arg2"])
f(**my_dict) # these are equivalent
On the other hand, functions can take arbitrary numbers of arguments (similar to how other languages do for printf() calls). For example:
def g(*args):
print("I got this many arguments:", len(args))
Here, if you do type(args), you get tuple, and if you do type(*args), you get an error. This is because, in function headers, the * does the exact opposite: it packs the arguments that were given to the function into a single tuple, so that you can work with them. Consider the following:
g(2, 3) # 2 arguments, both integers
g((2, 3)) # 1 argument, a tuple
g(*(2, 3)) # 2 arguments, both integers
In short,
functions are built in such a way that they take an arbitrary number of arguments
The * and ** operators are able to unpack tuples/lists/dicts into arguments on one end, and pack them on the other end
individual tuples/lists/dicts are otherwise just individual variables.
The thing is that parens are used for several different things in Python -- for calling functions, for making tuples (it's not just the commas that matter, see the empty tuple ()), for changing evaluation priority in expressions.
In cases where interpreting them is ambiguous (e.g. your example f(2, 3) could be either a function call with two arguments, or a function call with one argument that is a tuple) the language has to make a choice.
If the Python parser was implemented so that it parsed this as a tuple, it would be impossible to have functions with more than one argument. If the Python parser was implemented so that it parsed this as two arguments, it's impossible to pass a literal tuple without the parens.
Clearly the first is a much bigger limitation, so the second was chosen.
Another example is with tuples with one element -- is (1+2) an expression yielding the number 3, or a tuple with one element, 3? Here if it was the second, then it would be impossible to use parens for expressing priority in an expression ((3+4)*5 vs 3+(4*5)). So it was decided to require the comma after the first element for 1-element tuples ((3,)).

Python - list of function/argument tuples

def f1(n): #accepts one argument
pass
def f2(): #accepts no arguments
pass
FUNCTION_LIST = [(f1,(2)), #each list entry is a tuple containing a function object and a tuple of arguments
(f1,(6)),
(f2,())]
for f, arg in FUNCTION_LIST:
f(arg)
The third time round in the loop, it attempts to pass an empty tuple of arguments to a function that accepts no arguments. It gives the error TypeError: f2() takes no arguments (1 given). The first two function calls work correctly - the content of the tuple gets passed, not the tuple itself.
Getting rid of the empty tuple of arguments in the offending list entry doesn't solve the problem:
FUNCTION_LIST[2] = (f2,)
for f,arg in FUNCTION_LIST:
f(arg)
results in ValueError: need more than 1 value to unpack.
I've also tried iterating over the index rather then the list elements.
for n in range(len(FUNCTION_LIST)):
FUNCTION_LIST[n][0](FUNCTION_LIST[n][1])
This gives the same TypeError in the first case, and IndexError: tuple index out of range when the third entry of the list is (f2,).
Finally, asterisk notation doesn't work either. This time it errors on the call to f1:
for f,args in FUNCTION_LIST:
f(*args)
gives TypeError: f1() argument after * must be a sequence, not int.
I've run out of things to try. I still think the first one ought to work. Can anyone point me in the right direction?
Your comment in this code snippet shows a misconception relevant in this context:
FUNCTION_LIST = [(f1,(2)), #each list entry is a tuple containing a function object and a tuple of arguments
(f1,(6)),
(f2,())]
The expressions (2) and (6) are not tuples – they are integers. You should use (2,) and (6,) to denote the single-element tuples you want. After fixing this, your loop code should look thus:
for f, args in FUNCTION_LIST:
f(*args)
See Unpacking Argument Lists in the Python tutorial for an explanation of the *args syntax.
The problem is that such notation:
(6)
evaluates to integer value and you need tuple, so write this way:
(6, )
and your asterisk notation will succeed.
Try passing *() instead of (). The * symbol tells python to unpack the iterable that follows it, so it unpacks the empty tuple and passes nothing to the function, since the tuple was empty.
For the record, a nice alternative I have since discovered is the use of functools.partial. The following code does what I was trying to do:
from functools import partial
def f1(n): #accepts one argument
pass
def f2(): #accepts no arguments
pass
FUNCTION_LIST = [partial(f1,2), #each list entry is a callable with the argument pre-ordained
partial(f1,6),
partial(f2)] #the call to partial is not really necessary for the entry with no arguments.
for f in FUNCTION_LIST: f()

Categories

Resources