Skip to content

Programming Concepts 5: Functions

Nicholas DiBari edited this page Jan 18, 2018 · 11 revisions

Functions are used to specify a block of code to by a function name, which can they be used at other points in the program to execute the code defined in the function. Functions are a way to reuse the same chunk of code over and over again, which makes for fewer lines of code and an easier to read program. Functions can also take in parameters, which are values used in the context of the function. In this way a programmer can easily alter the behavior of the function using and allow for the function to operate on variables stored in program memory. Let's look at a simple example:

>>> def add(num1, num2):
....    """Return the sum of two numbers"""
....    return num1 + num2
....

The first line defines a function named add, which accepts two parameters: num1 and num2. The second line is referred to as a docstring, a comment explaining the behavior of the function. Docstrings are a way of documenting how code works, so future work on the same code is (relatively) painless.

You can call a function by writing <func_name>(param1, param2, ...). In our case, our function name is and, and it takes two parameters. So we would call it by writing add(<param1>, <param2>). params are the values we pass to the function.

The function returns the value of the two numbers added together, which exits the function and returns 2 to the point of the program where the function was called:

>>> add(2, 2)
4
>>> # Can also take strings too!
>>> add("Hello ", " there!")
Hello there!
>>> x = 10
>>> y = 5
>>> # Store the return value from add in a variable
>>> # named sum
>>> sum = add(10, 5)
>>> print(sum)
15

Note that functions don't actually have to accept any arguments, or return anything. You can have a function that simply executes some commands and exits. In this case the function would technically return None, as shown:

>>> def do_something_simple():
....    print('Doing something simple...')
....
>>> do_something_simple()
Doing something simple...
>>> ret_val = do_something_simple()
Doing something simple...
>>> type(ret_val)
NoneType

So far we've been passing parameters as positional arguments. This means that when the function is called, the first parameter passed to the function will be assigned to the first parameter of the function, the second parameter to the second, and so on. Note the difference in the following:

>>> # Using the add() function from earlier:
>>> add('Hello ', 'there!')
Hello there!
>>> add('there! ', 'Hello')
there! Hello

Another way to pass arguments to a function is through keywords. The signature of the function, or the area where arguments are defined, specifies the name of the argument that should be specified. Keyword arguments define a default value to be used if the argument is not passed.

>>> def greeting(time, name='there'):
....    """
....    Displays a greeting and prints the time
....    @param time (str): Current time
....    @param name (str): Optional name to use in the prompt
....    """
....    print('Hello ' + name + '!')
....    print('The time is currently \'' + time + '\'')
....
>>> greeting('12:00 PM', 'Nick')
Hello Nick!
The time is currently '12:00 PM'
>>> greeting('11:00 AM')
Hello there!
The time is currently '11:00 AM'

Note that keyword arguments should always come after positional arguments. Doing otherwise would cause an SyntaxError:

>>> greeting('John', '11:00 AM')
  File "<ipython-input-11-4f6f0e125523>", line 1
    greeting(name='Nick', '11:00 AM')
SyntaxError: non-keyword arg after keyword arg

You can also pass arbitrary arguments to the function by including **kwargs in the function signature. This special parameter is included after all other parameters and captures any keyword arguments not captured by another keyword argument

>>> def some_complex_function(records, user, url='http://example.com', **kwargs):
....    print('Received kwargs: {}'.format(kwargs))
....    print('Starting complex function with {} records..'.format(len(records)))
....    register_user(user, url)  # Calling a function in a function!
....    # Do more complex stuff..
....
>>> some_complex_function(
....    records,
....    'Nick',
....    url='https://mysite.org',
....    respond=True,
....    priority='LOW',
....    status='PENDING'
....)
Received kwargs: {'respond': True, 'priority': 'LOW', 'status': 'PENDING'}
Starting complex function with 6 records..

You can use kwargs as a dictionary to access the keyword arguments:

>>> def some_complex_function(records, user, url='http://example.com', **kwargs):
....    if kwargs.get('priority') == 'URGENT':
....        print('Oh dear! On it right away!')
....        do_something_urgent(records, user, url)
....    print('Starting complex function with {} records..'.format(len(records)))
....    register_user(user, url)  # Calling a function in a function!
....    # Do more complex stuff..
....
>>> some_complex_function(
....    records,
....    'Nick',
....    url='https://mysite.org',
....    respond=True,
....    priority='URGENT',
....    status='FAILED'
....)
Oh dear! On it right away!