Python Fundamentals Tutorial: Functions

7. Functions

7.1. Defining

A function in Python is defined with the def keyword. Functions do not have declared return types. A function without an explicit return statement returns None. In the case of no arguments and no return value, the definition is very simple.

Calling the function is performed by using the call operator () after the name of the function.

>>> def hello_function():
...     print 'Hello World, it\'s me.  Function.'
...

>>> hello_function()
Hello World, it's me.  Function.

7.2. Arguments

The arguments of a function are defined within the def statement. Like all other variables in Python, there is no explicit type associated with the function arguments. This fact is important to consider when making assumptions about the types of data that your function will receive.

Function arguments can optionally be defined with a default value. The default value will be assigned in the case that the argument is not present in the call to the function. All arguments without default values must be listed before arguments with default values in the function definition.

Any argument can be passed either implicitly by position or explicitly by name, regardless of whether or not it has a default value defined.

>>> def record_score(name, score=0):
...     print '%s scored %s' % (name, score)
...

>>> record_score('Jill', 4)
Jill scored 4

>>> record_score('Jack')
Jack scored 0

>>> record_score(score=3, name='Pail')
Pail scored 3

>>> record_score(2)
2 scored 0

>>> record_score(score=2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  TypeError: record_score() takes at least 1 non-keyword argument (0 given)

>>> record_score()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  TypeError: record_score() takes at least 1 argument (0 given)
[Note]Note

Look carefully at the example above. There is an asymmetry in the use of the = sign for defining vs. passing arguments that can be confusing to beginners. An argument with a default value can be passed using only position and an argument without a default can be passed using a keword.

7.3. Mutable Arguments and Binding of Default Values

When defining default arguments, take special care with mutable data types. The instance of the default value is bound at the time the function is defined. Consequently, there is a single instance of the mutable object that will be used across all calls to the function.

>>> def add_items(new_items, base_items=[]):
...     for item in new_items:
...         base_items.append(item)
...     return base_items
...

>>> add_items((1, 2, 3))
[1, 2, 3]

>>> add_items((1, 2, 3))
[1, 2, 3, 1, 2, 3]

As a result, it is best to use default value of None as a flag to signify the absense of the argument and handle the case inside the function body.

>>> def add_items(new_items, base_items=None):
...     if base_items is None:
...         base_items = []
...     for item in new_items:
...         base_items.append(item)
...     return base_items
...

>>> add_items((1, 2, 3))
[1, 2, 3]

>>> add_items((1, 2, 3))
[1, 2, 3]

7.4. Accepting Variable Arguments

In addition to named arguments, functions can accept two special collections of arguments.

The first is a variable-length, named tuple of any additional positional arguments received by the function. This special argument is identified by prefixing it with a single asterisk (*).

The second is a variable-length dictionary containing all keyword arguments passed to the function that were not explicitly defined as part of the function arguments. This argument is identified by prefixing it with two asterisks (**).

It is not required, but conventional and therefore highly recommended, to name these two arguments args and kwargs, respectively.

The use of these two arguments is illustrated in the following set of examples.

>>> def variable_function(*args, **kwargs):
...     print 'args:', args
...     print 'kwargs:', kwargs
...

>>> variable_function('simple')
args: ('simple',)
kwargs: {}

>>> variable_function(type='Complex')
args: ()
kwargs: {'type': 'Complex'}

>>> def mixed_function(a, b, c=None, *args, **kwargs):
...     print '(a, b, c):', (a, b, c)
...     print 'args:', args
...     print 'kwargs:', kwargs
...

>>> mixed_function(1, 2, 3, 4, 5, d=10, e=20)
(a, b, c): (1, 2, 3)
args: (4, 5)
kwargs: {'e': 20, 'd': 10}

7.5. Unpacking Argument Lists

It is also possible to construct argument lists (positional or keyword) and pass them into a function.

For positional arguments, insert them into a tuple / list and prepend with an asterisk (*) in the function call.

For keyword arguments, use a dictionary and prepend with two asterisks (**).

>>> def printer(a, b, c=0, d=None):
...     print 'a: {0}, b: {1}, c: {2}, d: {3}'.format(a, b, c, d)
...
>>> printer(2, 3, 4, 5)
a: 2, b: 3, c: 4, d: 5

>>> ordered_args = (5, 6)
>>> keyword_args = {'c': 7, 'd': 8}

>>> printer(*ordered_args, **keyword_args)
a: 5, b: 6, c: 7, d: 8
[Note]Note

The example above shows another potentially confusing asymmetry in Python. You can pass arguments using the regular style to a function defined using variable arguments, and you can pass unpacked variable argument lists to a function defined without variable arguments.

7.6. Scope

Each function evaluation creates a local namespace that is manipulated at any level within the function. As a result, variables can be initially defined at a seemingly lower level of scope than they are eventually used.

>>> def deep_scope():
...     if True:
...         if True:
...             if True:
...                 x = 5
...     return x
...

>>> deep_scope()
5
[Warning]Warning

This model for scope can simplify your code, but pay attention. If you don’t anticipate all code paths, you can end up referencing undefined variables.

>>> def oops(letter):
...     if letter == 'a':
...         out = 'A'
...     return out
...

>>> oops('a')
'A'

>>> oops('b')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in oops
UnboundLocalError: local variable 'out' referenced before assignment

7.7. Lab

  1. Copy classmates.py to classfilter.py
  2. Ask the user for a role on the command-line
  3. Using a function and the builtin filter(), print a list of your classmates with that role