Intro to Python Functions

Published 9/07/2016

Functions

Quick Note

This tutorial moves very fast. If you would like a slower, more in-depth intro to Python, we suggest you take our Intro to Python Evening Course. It's the perfect way to become familiar with Python + gain experience using Python to solve challenging problems.

Prerequisites

This web tutorial follows the Jupyter notebook found on GitHub. If you are viewing it using GitHub, then you cannot execute the cells that contain Python code. To view and run this notebook you'll need to install Jupyter on your computer before you continue. See these installation instructions for help!

Overview

Functions are an essential part of coding and are a way of defining a procedure that you can reuse. They are incredibly helpful in writing clean, readable, and reusable code. I would encourage you to code using functions (or classes) whenever you can.

Python has a very large number of built-in functions, as well as a vast repository of modules/libraries through which other functions (as well as classes) are available. Check out the built-in function docs to see all of the built-in functions that are available to use. One of the libraries whose functions I use regularly is the itertools library.

From a top-down level, functions name a piece of code that takes parameter(s) and allows you to write "tiny commands". The purpose/use of a functions should typically be able to be described in one sentence, and on average functions should probably be no longer than 10 lines.

How do I create them?

Functions are defined using a def statement, followed by the name that you wish to give the function (this name should follow variable naming conventions - camel_case in Python), followed by a set of parentheses that contain any potential parameters that may be passed to the function. Next, we place a colon, and then finally we get to write code for the function body. This code must fall on one or more indented lines (note that the indentation is crucial).

def my_func(passed_arg1, passed_arg2, passed_arg3): 
    # code goes here 
    pass
Note that we don't have to pass any parameters if we don't want to:
def my_func_no_args(): 
    print 'There are no args passed :) '

Examples

In [1]:
def is_palindrome(word): 
    '''
    Input: String
    Output: Bool
    
    Return whether or not the inputted word is a palindrome. 
    '''
    
    # Note we use return to return something back from the function. 
    return word == word[::-1]

print is_palindrome('hello')
print is_palindrome('racecar')
False
True
In [2]:
def get_divisors(number):
    '''
    Input: Integer
    Output: List

    Return a list of the divisors of the inputted number
    '''
    # The return statement can return any kind of data structure. 
    return [divisor for divisor in xrange(1, number + 1) if number % divisor == 0]

print get_divisors(10)
print get_divisors(100)
[1, 2, 5, 10]
[1, 2, 4, 5, 10, 20, 25, 50, 100]

Variable Scope

Variable scope is an important concept to consider when building functions. Knowing how variable scope works will save you from many kinds of common bugs.

Variable scope determines the part (or block) of the program in which that variable is visible. We typically refer to one of two scopes of variables - global scope and local scope. A variable with global scope is visible everywhere and can be used by any function, while a variable with local scope is visible only in the function in which it was defined.

In [3]:
my_global_var = 'This is a global variable.'

def scoping_func(): 
    my_local_var = 'This is a local variable, only usable in the scoping_func.'
    print my_local_var
In [4]:
print my_global_var
This is a global variable.
In [5]:
print my_local_var
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-5-b0b2b2a41781> in <module>()
----> 1 print my_local_var

NameError: name 'my_local_var' is not defined
In [6]:
scoping_func()
This is a local variable, only usable in the scoping_func.

Variable Scope Part Two

When referencing a variable in an expression, Python will search the following scopes to resolve the reference:

1.) The current function's scope.
2.) Any enclosing scopes (like other containing functions).
3.) The scope of the module that contains the code (also called global scope).
4.) The built-in scope (contains the built-in functions).

When assigning a value to a variable, things work a little bit differently. If the variable is already defined in the current scope, then it will just take on the new value that you assign it. However, if it is not defined in the current scope, then Python treats the assignment as a variable definition. Let's take a look at how this plays out...

In [7]:
# This 'found' is in the global scope, so everything has access to it. 
found = True
def find_number(numbers_lst, search_number): 
    # This 'found' is in the scope of 'find_number', and anything that is enclosed in it. 
    found = False
    def inner_func(): 
        for num in numbers_lst:      
            # This has access to the current function's scope and anything above it. So when 
            # it looks for the 'found' variable, it doesn't find it in the 'innner_func' scope, 
            # but does find it in the containing function's ('find_number') scope. Since it 
            # find's it in the 'find_number' scope, it doesn't keep looking, and so it never 
            # find's the one in the global scope. 
            print found
    inner_func()
        
find_number([1, 2, 3, 4, 5], 3)
False
False
False
False
False
In [8]:
def find_number(numbers_lst, search_number): 
    # This 'found' is in the scope of 'find_number', and anything that is enclosed in it. 
    found = False
    def inner_func(): 
        for num in numbers_lst:  
            if num == search_number: 
                # With assignment, it doesn't find it in it's own scope, so it creates it. 
                # This causes us to print True at the end of this inner function. 
                found = True
        print found
    inner_func()
    return found
        
print find_number([1, 2, 3, 4, 5], 3)
True
False

Giving parameters default values

If you'd like, you can give your function parameters default values. You do this within the function definition statement:

def find_number(numbers_lst, search_number=3):
    for num in numbers_lst: 
        if num == search_number: 
            print 'Found'

The way this works is that if the caller of your function passes in a value for search_number, the function uses that. If the caller doesn't pass in a value for search_number, then your function uses the default value that you gave it.

Note that you can also call your functions with either positional parameters (like I have done up until now), or with keyword parameters. The only stipulation is that all positional parameters must be placed before all keyword parameters (i.e. you can't call your function with a keyword parameter placed before a positional parameter).

In [9]:
def find_number(numbers_lst, search_number=3):
    for num in numbers_lst: 
        if num == search_number: 
            print 'Found'

find_number([1, 2, 3, 4, 5]) # Okay because we specified default value. 
find_number([1, 2, 3, 4, 5], 4) # The second passed parameter (4) overrides the default 3. 
find_number([1, 2, 3, 4, 5], search_number=4) # Okay because all positional parameters specified first. 
find_number(numbers_lst=[1, 2, 3, 4, 5], 4) # Not okay, because we specified a keyword parameter before a positional.
  File "<ipython-input-9-7fb3b12a3762>", line 9
    find_number(numbers_lst=[1, 2, 3, 4, 5], 4) # Not okay, because we specified a keyword argument before a positional.
SyntaxError: non-keyword arg after keyword arg

args and kwargs

The use of *args and **kwargs is something that you might see or use with your function. This is one of the really nice features of Python (although I don't use it often); it allows your functions to accept an arbitrary number of optional arguments. *args allows you to accept an arbitrary of number of optional positional arguments, where as **kwargs, which stands for keyword arguments, allows you to accept an arbitrary number of optional keyword arguments.

In [10]:
def args_func(first_arg, *args): 
    print first_arg
    for arg in args: 
        print arg
In [11]:
args_func(1)
1
In [12]:
args_func(1, 2, 3, 4)
1
2
3
4
In [13]:
args_func(1, [2, 3, 4])
1
[2, 3, 4]
In [14]:
def kwargs_func(first_arg, **kwargs): 
    print first_arg
    for kwarg, value in kwargs.iteritems(): 
        print kwarg, value
In [15]:
kwargs_func(1)
1
In [16]:
kwargs_func(1, 2, 3, 4)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-16-6a6dffd7d4b0> in <module>()
----> 1 kwargs_func(1, 2, 3, 4)

TypeError: kwargs_func() takes exactly 1 argument (4 given)
In [17]:
kwargs_func(1, second_arg=2, third_arg=3, fourth_arg=4)
1
second_arg 2
fourth_arg 4
third_arg 3

Want some practice?

Have a look at the intro_function_practice.ipynb notebook!

Next Steps

If you want to see Python in action exploring a real dataset, have a look at Exploring Data with Python using Jupyter Notebooks.

 Check out these related articles

 

5 More Tools Data Scientists Need to Know

 

 

 

What’s the Difference Between Data Engineering and Data Science?

 

 

 

Common Data Science Interview Questions

 

 

Back to Full List

Want to learn more?

Galvanize offers an 6-week part time workshop, as well as a 12-week full-time program in Data Science that teaches you how to make an impact as a contributing member of a data analytics team.

Learn About our Immersive Programs Register for a Workshop

Sign up to get updates direct to your inbox