Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Views: 720
Image: ubuntu2004
Kernel: Python 3

nuwc_nps_banner.png

Lesson 1.5: Functions

One of the basic building blocks of computing in Python is the function.

  • a function takes zero or more input values (called parameters) and returns a result

  • it also executes commands that can have an effect (sometimes also called a procedure)

The basic structure of a function in Python is the following:

def some_function(arg1, arg2, ...): """documentation""" # optional doc string statements return expression # returns the value of the expression return # returns None

A function in Python always returns a single value.

  • it can be None (similar to null), this is the default

  • or it can return the value of some specified expression

  • or it can return a comma separated sequence of values combined as a tuple

The Components of A Function (1 of 2)

Note: figure courtesy of Professor Thomas Otani, NPS.

The Components of A Function (2 of 2)

Note: figure courtesy of Professor Thomas Otani, NPS.

Passing Data into a Function: Specifying Arguments

Python has two conventions for specifying arguments to a function:

  • Positional - based on the ordered position in the argument list (we have seen this before)

  • Keyword - using 'key = value' specification, which ignores positional order (new material)

def myfunc(a,b,c): print('a: %s, b: %s, c: %s' % (a,b,c))
# this is the standard positional specification myfunc(1,2,3)
a: 1, b: 2, c: 3
# however, we can also use keyword specification myfunc(a=2,b=3,c=4)
a: 2, b: 3, c: 4
# keyword specification ignores the order in which they are used myfunc(c=4,a=2,b=3)
a: 2, b: 3, c: 4

Mixing Styles

# you can mix these styles (but be careful) myfunc(5, c=4, b=3)
a: 5, b: 3, c: 4
# you can't specify more than one value for an argument myfunc(5, b=4, a=3)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-20-60e22f32f115> in <module> 1 # you can't specify more than one value for an argument ----> 2 myfunc(5, b=4, a=3) TypeError: myfunc() got multiple values for argument 'a'
# positional arguments cannot follow keyword arguments myfunc(a=2,3,4)
File "<ipython-input-21-87063e8c3423>", line 2 myfunc(a=2,3,4) ^ SyntaxError: positional argument follows keyword argument

Using keyword specification to define default values for parameters

You can define default values using keyword style specification when writing a function.

def doit(x,y,z=0): print('x: %s, y: %s, z: %s' % (x,y,z))
# things work as normal with positional arguments... doit(1,2,3)
# ...but we only need to provide two of them doit(1,2)
# anything less than 2 args produces an error here doit(1)
# we can use keyword arguments here too doit(y=3,x=2)
# but we still have to provide values for x and y in this case doit(z=2,x=1)

Providing default values for arguments (when appropriate) can create a lot of flexibility in how you call your functions

def doit2(x=1,y=2,z=3): print('x: %s, y: %s, z: %s' % (x,y,z))
doit2()
doit2(5)
doit2(5,4)
doit(5,4,2)
doit2(z=1)

Functions can accept arbitrary positional arguments with *args

def unlimited(x,y,*args): print('x: %s, y: %s' % (x,y)) if args: print(args)
unlimited(1,2)
unlimited(1,2,3)
unlimited(1,2,3,4,5,6)
# positional arguments cannot follow keyword arguments unlimited(y=3,x=5,7)

Activity - Create a my_sum function that:

  • Accepts any number of inputs and sums them together

def my_sum(*args): total = 0 for i in args: total = i + total return total
my_sum(1,2,3,4)
10

Functions can accept arbitrary keyword arguments with **kwargs

Any additional key-value arguments are parsed and stored in a dictionary called kwargs.

def flexible(x,y,**kwargs): '''requires two positional arguments, then arbitrary key-value arguments''' print('x: %s, y: %s' % (x,y)) for key,value in kwargs.items(): print('also got, %s : %s' % (key,value))
flexible(5,7,a=1,b=2,c=3)
# this function takes arbitrary arguments def generic(*args, **kwargs): print('positional arguments: ', args) print('keyword arguments: ', kwargs)
generic(4,3,2,1,a=1, b=2, c=3)

Argument expansions

# recall our original function myfunc... def myfunc(a,b,c): print('a: %s, b: %s, c: %s' % (a,b,c))
myfunc(1,2,3)
a: 1, b: 2, c: 3
# what if we had a tuple that we wanted to pass in x = 1,3,5 x
(1, 3, 5)
myfunc(x)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-4-c15edb3ac2c7> in <module> ----> 1 myfunc(x) TypeError: myfunc() missing 2 required positional arguments: 'b' and 'c'
# we can 'unpack' the tuple and pass it in as positional arguments... # (revisit the old lecture on tuples to see more details on this!) myfunc(*x)
a: 1, b: 3, c: 5
# we can also 'unpack' a list and pass it in as arguments mylist = [1,2,3] myfunc(*mylist)
a: 1, b: 2, c: 3
# similarly, suppose we had a dictionary of values mydict = {'a': 2, 'b': 4, 'c': 6} mydict
{'a': 2, 'b': 4, 'c': 6}
myfunc(mydict)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-7-497782732eab> in <module> ----> 1 myfunc(mydict) TypeError: myfunc() missing 2 required positional arguments: 'b' and 'c'
# we can 'unpack' the dictionary and pass it in as keyword arguments... myfunc(**mydict)
a: 2, b: 4, c: 6

Activity - Create a function that:

  • Unpacks a tuple, list, or dictionary of length 4 and prints the results

def sum_four(a,b,c,d): return a+b+c+d
x = 1,2,3,4 y = [2,3,4,5] z = {'a':1 , 'b':2, 'c':3 , 'd':5} sum_four(**z)
11

Control Flow (and implications)

  • When a function is called, a program control flows into the function.

  • After the function is executed, the control flows back to the calling side.

  • Only the value of return stament passes back with control flow.

  • Function parameters are separate copies of arguments (variables):

    • Parameters are temporary variables that exist in memory only during the execution of the function

    • Parameters and other local variables are erased from memory after the execution of the function

    • Any changes you make to parameters during the execution of the function (even if they have the same name as variable in your program) are lost after the function terminates

  • A function can return only once.

    • If there is more than one return statement, the first one that is reached is executed

# consider this function def reset(a, b): a = 33 b = 44
# what is printed ? x = 11 y = 22 reset(x, y) print(x, y)
# consider another function def reset(x, y) : x = 33 y = 44
# what is printed here ? x = 11 y = 22 reset(x, y) print(x, y)

Bottom line: variables that are assigned within a function are local and live only within the scope of that function. It does not matter if they share a name with another variable.

If you want to pass values back to where the function was called, you should return these values.

# this is how you normally pass values back from a function def reset(a, b) : a = 33 b = 44 return a , b
x = 11 y = 22 x,y = reset(x, y) print(x, y)
x = 11 y = 22 x = reset(x, y) print(x, y)

What happens when you pass a list into a function

a = [-12,4,0,2,-1,5,8]
def some_function(x): print("old value of x[2] = ", x[2]) x[2] = 5 print("new value of x[2] = ", x[2]) # end of some_function
some_function(a)
a # the array has been changed!

Lambda Functions

Python lambdas are little, "anonymous functions", subject to a more restrictive but more concise syntax than regular Python functions.

lambda <vars>: <function_body>

The Rules:

  • It can only contain expressions, no statements (i.e. return, pass, assert, or raise will fail).

  • It needs to be written in a single line

  • It does not support type annotations

  • Needs to be immediately invoked

(lambda x: x+1)(2)
add_two = lambda x: x+2 add_two(4)
add_period = lambda x: x + '.' add_period('test')
'test.'

Now let's use multiple inputs

lambda x,y: x+y
_(1,2) #note: this "_" trick only works in IPython environments
numbers = (1, 2, 3, 4) result = map(lambda x: x*x, numbers) # converting map object to set numbersSquare = set(result) print(numbersSquare)
(lambda x, y, z: x + y + z)(1, 2, 3)
(lambda x, y, z=3: x + y + z)(1, 2)
(lambda x, y, z=3: x + y + z)(1, y=2)
(lambda *args: sum(args))(1,2,3)
(lambda **kwargs: sum(kwargs.values()))(one=1, two=2, three=3)

Exploring how Lambdas are used: map, and filter

Map

Mapping is a slick way to loop without specifying interation bounds that are required in constructs such as for and while loops.

Mapping "maps" a function (either built in or user defined) to an array of data (list, dictionaries, sets, and tuples).

map(func,list)
  • map by default returns a mapping iterator and not a array of data.

  • break and continue do nothing in the map construct.

Let's consider a very basic example:

numbers = [[1, 2, 3, 4],[2,3]] result = map(sum, numbers) print(result) # converting map object to set print(list(result))
<map object at 0x0000022C63F53358> [10, 5]
def calculateSquare(n): return n*n numbers = [1, 2, 3, 4] result = map(calculateSquare, numbers) # converting map object to set numbersSquare = list(result) print(numbersSquare)
[1, 4, 9, 16]
# Now doing the same thing as above but with lambdas numbers = (1, 2, 3, 4) result = map(lambda x: x*x, numbers) # converting map object to set print(set(result))
{16, 1, 4, 9}
pascal = [[1],[1,2,1],[1,3,3,1],[1,4,6,4,1],[1,5,10,10,5,1]] list( map(sum,pascal) )
[1, 4, 8, 16, 32]

Lets take a look at a more complicated example: counting cases in a list

b = [0,0,0,1,1,1,1,2,2,3,3,3,3,3,4,5,5,5] nums = set(b) list( map(b.count,nums) )
[3, 4, 2, 5, 1, 3]

Filter

Used to filter data in a list, tuple, set, or dictionary based on a user specified criteria.

filter(<function>, <iterable>)

Return an iterator yielding those items of [removed] for which [removed] is true.

# Let's look at a simple example that doesn't work a=[0,1,2,3,4,5] [a<2]
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-89-6170e7c0a6e2> in <module> 1 # Let's look at a simple example that doesn't work 2 a=[0,1,2,3,4,5] ----> 3 [a<2] TypeError: '<' not supported between instances of 'list' and 'int'
# Let's use filter to solve the above problem # Note: the first input into filter is a function, this is where lambdas come in. list( filter(lambda x:x<2,[0,1,2,3,4,5]) )
[0, 1]
# Let's compare this to map list( map(lambda x:x<2,[0,1,2,3,4,5]) )
[True, True, False, False, False, False]
# Here's a simple filtering example with an equation list(filter(lambda x:x**2+4*x+16 >= 30, [0,1,2,3,4,5,6]))
[3, 4, 5, 6]
# Filtering Sets print(set(filter(lambda x:x<4,{4,2,5,3,1,8,9}))) # Filtering Strings print(list(filter(lambda x:x<'g',set('I am the very model of a major general'))))
{1, 2, 3} ['e', ' ', 'f', 'd', 'a', 'I']

Summary

  • Python has lots of flexibility for specifying function input

  • The return statement is the primary way of sending data out of a function

  • However, if given a references to another object, a function can make changes that are not local

  • map, filter, and lambda are very useful operations in functional programming.

Next Lesson: 1.6 Namespaces