Python functions and lambdas¶
Writing your own functions in Python is quite straightforward, and in fact we’ve already seen this a few times. In this section we’ll make the syntax for this explicit, explore some very special arguments, and wrap up by discussing lambda functions.
Defining your own functions¶
Functions are defined in Python using the following syntax:
def myfun(in1,in2):
# Function body
return someval
There can be any number of arguments into the function, including zero. The return statement is optional as well, and can be omitted if that makes sense for what you are writing.
Returning multiple values from a function can easily be accomplished by using a tuple
.
Consider this example function that calculates the area and circumference of a circle
with a given radius:
def circum_info(rad):
circum = 2*np.pi*rad
area = np.pi*rad**2
return circum,area
We could then call this function in a couple of ways:
>>> ca = circum_info(3)
>>> ca[0]
18.84955592153876
>>> ca[1]
28.274333882308138
>>> c,a = circum_area(3)
>>> c
18.84955592153876
>>> a
28.274333882308138
We should make some observations about our function and how we called it:
The input argument
rad
is simply assumed to be a numberThere is really only one return, a tuple containing
circum
andarea
We can capture this return into a single tuple, like
ca
in the aboveAlternatively, we can immediately unpack the tuple, like
c,a = ...
in the above - This is essentially the same as having multiple returns - This is also the most readable
Special arguments to functions: *args
and **kwargs
¶
Functions can take in any number of arguments, and in fact can take in a variable number of arguments. Additional arguments that don’t explicitly appear as part of the function signature are lumped into two special types of arguments. We can adapt our above presentation of function syntax to include these like so:
def myfun(in1,in2,*args,**kwargs):
# function body
return someval
As before, this function takes in two arguments and returns a value. It additionally
can take in an a-priori unknown number of arguments through *args
and **kwargs
.
Note
Technically these don’t need to be called *args
and **kwargs
. Rather, these
names are convention and make it a bit easier for people approaching your code to
figure out what is going on.
However, you can only have one of each of these, and *args
(or whatever you
choose to call it) must go first. You can also choose to use only one of the two.
The *args
argument¶
The *args
argument must come after all required arguments, and before **kwargs
if it is used. The interpreter will create args
as a tuple containing all arguments
after the initial set of required ones, and since tuples can be any length this can hold
any number of additional arguments. This is best seen through an example. Consider the
following function:
def myfun(in1,in2,*args):
print('in1: ',in1,type(in1))
print('in2: ',in2,type(in2))
print('Called with extra args: ',len(args))
print('args: ',args,type(args))
Try calling this function in a few different ways:
>>> myfun(2,'a string')
>>> myfun(2,'a string',3.4,5,['a','list'])
>>> myfun(2,'a string',['a','list'],('a','tuple'))
What does each call write to the terminal and why?
The **kwargs
argument¶
We’ve seen that using *args
allows you to capture any number of additional arguments
beyond those that the function explicitly requires. These arguments are called positional
arguments because their order is preserved when the tuple args
is created.
We can also capture additional arguments by name. These are called key word arguments, and can come in any order. Again, this is best shown with an example. Consider the following modification to the previous function:
def myfun(in1,in2,**kwargs):
print('in1: ',in1,type(in1))
print('in2: ',in2,type(in2))
print('Called with extra args: ',len(kwargs))
print('kwargs: ',kwargs,type(kwargs))
Try calling this function in a few different ways:
>>> myfun(2,'a string')
>>> myfun(2,'a string',my_list=['a','list'],my_tuple=('a','tuple'))
>>> myfun(2,'a string',my_tuple=('a','tuple'),my_list=['a','list'])
We can make some useful observations from this:
kwargs
is a dictionary where each argument name is used as a key - The keys are always strings, as set by the interpreter - The values are whatever each name is set toLike with
*args
we can capture as many additional arguments as we want, including none
Question: What happens if you repeat a key?
Exercise: Adapt myfun
one more time to use both *args
and **kwargs
.
What can you observe from calling it in a few different ways?
Lambda functions¶
Occasionally you only need a simple function for a one-off task. Rather than defining a whole function just for this case you can instead use a lambda function. These can consist only of a single expression that transforms a group of inputs to a desired output. The general syntax is:
lambda <arguments>: <single expression>
For example, we could write a function that doubles its input. In full form this would be:
def f(x):
return 2*x
and the corresponding lambda function is:
lambda x: 2*x
Lambda functions are anonymous functions. We can still assign them names by capturing them. For instance we could write:
>>> f = lambda x: 2*x
>>> type(f)
function
>>> f(3)
6
>>> f('a string')
'a stringa string'
Lambda functions can do more inside the body, so long as they remain a single expression:
>>> g = lambda : [x**2 for x in range(10)]
>>> g()
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Lambda functions are particularly useful for providing inputs to functions that
themselves act on functions. Consider a function func
which takes two other
functions as arguments:
>>> def func(fInner,fOuter,x,y):
return fOuter(fInner(x),fInner(y))
>>> h = lambda x,y: 2*x + y
>>> func(f,h,3,4)
20
>>> func(lambda x: x**2,h,3,4)
34