8  Functions

Jupyter Notebook

We have already been using functions here and there but in this chapter we will introduce them formally and get into the details. A function encapsulates a block of code designed to perform a specific task or set of tasks. To perform the task correctly, most functions require that you provide some information (called arguments) when you call them. To call a function you type the name of the function followed by the needed arguments enclosed in parenthesis (()).

functionName(argument1, argument2, argument3...)

The number and type of arguments allowed is different for every function. As a first example, let’s consider the print function, which is the simplest (and most familiar) function that we have used so far.

print("print is a function")  #Single string argument
print("print", "is", "a", "function") # Multiple string arguments
print(5) # Single integer argument
print(5,4) # Multiple integer argument
print([5,4,2,5])  # Single list argument
print() # No arguments
print is a function
print is a function
5
5 4
[5, 4, 2, 5]

The print function is pretty flexible in what it allows for arguments; the arguments can be any type of data (strings, ints, floats, booleans, and even lists) and the number of arguments can be as large as you want. Most functions are a little more strict on what they allow their arguments to be. For example, the factorial function only allows one argument and that argument must be an integer.1 If you attempt to call the factorial function with a float argument or with more than one argument, the result will be an error.

  • 1 The factorial of a float can be calculated, but the math library is not equipped to handle it.

  • from math import factorial
    a = factorial(5.54)
    b = factorial(5,3)

    To Do:

    1. Run the code above and take note of the error message that results.
    2. Comment out the definition of a so you can also see the error message for the definition of b.

    Python functions generally fall into three groups: functions that come standard with Python (called native functions), functions that you can import into Python, and functions that you write yourself.

    8.1 Native Functions

    There are a few functions that are always ready to go whenever you run Python. They are included with the programming language. We call these functions native functions. You have already been using some of them, like these

    myList = [5,6,2,1]
    a = len(myList)  # 'len' function is native.
    
    b = float(5) # 'float' function is native.
    
    c = str(67.3)  # 'str' function is native.

    The len, float and str functions are all native and they all take a single argument. Other native function have been mentioned in previous chapters and others will be mentioned in the future.

    8.2 Imported Functions

    Many times, you will need to go beyond what Python can do by itself2. However, that doesn’t mean you have to create everything you need to do from scratch. Most likely, the function that you need has already been coded. Somebody else created the function and made it available to anyone who wants it. Groups of functions that perform similar tasks are typically bundled together into libraries ready to be imported so that the functions that they contain can be used.

  • 2 For example, Python does not include \(\sin()\) or \(\cos()\) as Native functions.

  • In order to use use a function correctly, you’ll need to know what information(arguments) the function expects you to give it and what information the function intends to return to you as a result. This information can be found in the library’s documentation. Most libraries have great documentation with lists of the included functions, what the functions do, the expected arguments, and examples on how to use the most common ones. You can usually find the library documentation by searching the internet for the library’s name plus “Python documentation”.

    Providing a complete list of all available libraries and function is not really the purpose of this book. Instead, we’ll illustrate how to import functions and use them. As you use Python more and more you should get in the habit of searching out the appropriate library to accomplish the task at hand. When faced with a task to accomplish, your first thought should be, “ I’ll bet somebody has already done that. I’m going to try to find that library.”

    Functions are imported using the import statement. You’ve already seen how to perform very simple mathematical calculations (\(5/6\),\(84\), etc..), but for more complex mathematical calculations like \(\sin( {\pi \over 2} )\) or \(e^{2.5}\) , you’ll need to import these functions from a library.

    import math
    
    a = math.sqrt(5.2)
    b = math.sin(math.pi)
    c = math.e**2.5

    The math. before each function is equivalent to telling Python “Use the sqrt() function that you will find in the math book I told you to grab.” If you just type

    sqrt(5.2)

    Python won’t know where to find the sqrt function and an error message will result. Sometimes the name of the module can be long and typing module. every time you want to use one of it’s functions can be cumbersome. One way around this is to rename the module to a shorter name using the as statement.

    import math as mt
    
    a = mt.sqrt(5.2)
    b = mt.sin(mt.pi)
    c = mt.e**2.5

    Instead of importing an entire module, you can import only a selection of functions from that module using the from statement. This can make your code even more succinct by eliminating the module. prefix altogether. The trade-off is that it won’t be as clear which function belongs to which module.

    from math  import sqrt, sin, pi, e
    
    a = sqrt(5.2)
    b = sin(pi)
    c = e**2.5

    All of the functions belonging to a module can be imported at once using *.

    from math import *
    
    a = sqrt(5.2)
    b = sin(pi)
    c = e**2.5

    8.3 User-defined Functions

    After having programmed for a while, you will notice that certain tasks get repeated frequently. For example, maybe in your research project you need to calculate the force exerted on an atom due to many other nearby atoms. You could copy and paste your force-calculation code every time it was needed, but that would likely result in lots of extra code and become very cumbersome to work with. You can avoid this by creating your own function to calculate the force between any two atoms. Then, every time you need another force calculation, you simple call the function again. You only write the force-calculation part of the code once and then you execute it as many times as you need to.

    To create your own function, you first need to name the function. The name should be descriptive of what it does and makes sense to you and anyone else who might use it. The first line of a function definition starts with the def statement (short for definition) followed by the name of the function with whatever information, called arguments, that needs to be fed into the function enclosed in parenthesis. The last character in this line must be a colon. Everything inside the function is indented four spaces and placed directly below the first line.

    def functionName(arg1,arg2,arg3):
        # Body of Function
        # Body of Function
        # Body of Function

    As an example, let’s construct a function that calculates the distance between two atoms. The function will need to know the location of each atom, which means that there should be two arguments: the xyz coordinates of both atoms passed as a pair of lists or tuples.

    import math
    
    def distance(coords1,coords2):
    
        dx = coords1[0] - coords2[0]
        dy = coords1[1] - coords2[1]
        dz = coords1[2] - coords2[2]
        d = math.sqrt(dx**2 + dy**2 + dz**2)
        print(f"The distance is {d:5.4f}.")
    
    distance([1,2,3],[4,5,6])
    
    distance([4.2,9.6,4.8],[2.9,2.6,3.4])
    The distance is 5.1962.
    The distance is 7.2560.

    This function works just fine with integers or floats for the coordinates.

    8.3.1 The return statement

    The distance function prints out the value for the distance, but what if we want to use this distance in a subsequent calculation? Maybe we want to calculate the average distance between several pairs of atoms. We can instruct the function to return the final distance using the return statement. If the arguments to the function are the inputs, the return statement specifies what the output is. Let’s modify the function above to include a return statement.

    import math
    def distance(coords1,coords2):
        dx = coords1[0] - coords2[0]
        dy = coords1[1] - coords2[1]
        dz = coords1[2] - coords2[2]
        d = math.sqrt(dx**2 + dy**2 + dz**2)
        return d
    
    distOne = distance([1,2,3],[4,5,6])
    
    distTwo = distance([4.2,9.6,4.8],[2.9,2.6,3.4])
    
    averageDistance = (distOne + distTwo)/2
    
    print(f"The average distance is {averageDistance:5.4f}.")
    The average distance is 6.2261.

    8.3.2 Local vs Global Variable Scope

    Variables created inside of a function have local scope. This means that they are not accessible outside of the function. In our distance function the variables dx,dy,dz, and d were all local variables that are used inside the function but have no value outside of it. This is convenient because we don’t have to worry about overwriting a variable or using it twice. If someone sends you a function and you want to use it in your code, you don’t have to worry about what variable he/she chose to use inside his function; they won’t affect your code at all.

    def distance(coords1,coords2):
        dx = coords1[0] - coords2[0]
        dy = coords1[1] - coords2[1]
        dz = coords1[2] - coords2[2]
        d = math.sqrt(dx**2 + dy**2 + dz**2)
        return d
    
    distOne = distance([1,2,3],[4,5,6])
    
    print(dx)  # There is no value associated with this variable outside of the function.

    The down side to all of this is that you don’t have access to function variables unless you pass them out of the function using the return statement.

    Any variables defined outside of a function is called a global variable, which means that Python remembers these assignments from anywhere in your code including inside of functions. Using global variables with the intention to use them inside of functions is usually considered bad form and confusing and is discouraged. One notable exception to this rule are physical constants like \(g = 9.8\) m/s\(^2\) (acceleration due to gravity on Earth) or \(k_B = 1.38 \times 10^{-23}\) (Boltzmann’s constant which is used heavily in thermodynamics) because these values will never change and may be used repeatedly. Generally speaking every variable that is used in a function ought to be either i) passed in as an argument or ii) defined inside of the function. Below is an example of an appropriate use of a global variable.

    def myFunction(a,b):
        c=a+g # <--- Notice the reference to 'g' here 
        d = 3.0 * c
        f = 5.0 * d**4
        return f
    
    #The variable below are global variables. 
    r = 10
    t = 15
    g = 9.8         #<--- g defined to be a global variable
    result = myFunction(r,t)

    8.3.3 Positional vs. Keyword Arguments

    The function arguments we have been using so far are called positional arguments because they are required to be in a specific position inside the parenthesis. To see what I mean consider the example below.

    def example(a,b):
        return a**b
    
    
    resultOne = example(5,2)
    resultTwo = example(2,5)
    
    print(resultOne, resultTwo)
    25 32

    In the first call to example the local variable a gets assigned to be 5 and the local variable b gets assigned 2. In the second call the order of the arguments is switched and the subsequent assignments to a and b switch with it. This produces a different result from the function. Positional arguments are very common but the user must know what information goes where when calling the function.3

  • 3 This is another reason why you want to choose meaningful variable names for your arguments.

  • The other type of argument is the keyword argument. These arguments are attached to a keyword inside of the parenthesis. The advantage of a keyword argument is that the user does not need to be concerned about the location of the argument as long as it has the proper label.

    def example(a=1,b=2):
        return a**b
    
    
    resultOne = example(a=5,b=2)
    resultTwo = example(b=2,a=5)
    
    print(resultOne, resultTwo)
    25 25

    Another advantage to using keyword arguments is that a default value can be coded into the function. This means that we can call the function with some arguments missing and default values will be used for them. In the example above, the default value of b is 2 and if the function is called without specifying a value for that argument, the function will proceed as usual using the default value for b.

    def example(a=1,b=2):
        return a**b
    
    
    result = example(a=5)
    
    print(result)
    25

    8.3.4 Splatting

    Functions can potentially require dozens of arguments to be passed in which can make calling the function long and difficult to look at. One way to shorten the code is to put all of the arguments in a list or tuple.

    def func(a,b,c,d):
    
        return a**b + c/d
    args  = (5,4,6,3)
    
    result = func(*args)
    
    print(result)
    627.0

    8.3.5 Lambda Functions

    When the function you want to construct is very simple (one line), there is a shortcut code for making it called a lambda function. The benefit is that they occupy less lines of code than the standard def functions. A lambda function is defined as shown below with the variable immediately after the lambda statement as the independent variable in the function.

    f = lambda x: x**2
    
    print(f(5))
    25

    As a simple application of lambda functions let’s consider a function from the scipy.integrate library called quad which will perform a numerical integration of a function. The quad function takes three arguments: the function to be integrated, and the upper and lower bound on the integral. We can use a lambda function for the first argument rather than using several lines of code to build one in the traditional fashion.

    from scipy.integrate import quad
    from math import sin, pi
    
    quad(lambda x: sin(pi * x)**2,0,0.4 )
    (0.15322553581056808, 1.7011451781741914e-15)

    8.4 Flash Cards

    1. Describe the basic structure and key elements of a user-defined function.
    2. What are function arguments?
    3. List all of the ways that a function can be imported from a module.
    4. What does the return statement do?
    5. What is the difference between a local and global variables?
    6. What is a keyword argument? Give an example.
    7. What is a lambda function? How do you make one?
    8. Recite Alma 32:21.

    8.5 Exercises

    1. A wooden block is placed on a horizontal surface and pushed across the floor with an initial velocity of \(v_0\). The expression for the position of the block after \(t\) seconds have elapsed is \[x(t) = v_0 t - {1\over 2} \mu g t^2\] where \(\mu\) is the coefficient of friction and \(g = 9.8\) m/s\(^2\). You can easily calculate the time it takes for the block to come to rest to be \[ T = {v_0 \over \mu g}\].

      1. Write a function that takes the coefficient of friction and the initial velocity as arguments and returns the total distance traveled while coming to rest.
      2. Call the function for \(v_0 = 7.2\) m/s and the following values of \(\mu = 0.62,0.3,0.45,0.2\). The answers should be: \([4.266, 8.816, 5.878, 13.224]\).
      3. Use list comprehension to put these travel distances into a list.
      4. Use the zip function to combine the list of friction coefficients with the list of travel distances.
      5. Use a print statement to verify that you did it correctly.
    # Solution here.
    1. In quantum mechanics you will learn that the energy of an electron can’t just be any old value, but can only be certain discrete values. For the hydrogen atom, the allowed energy levels for the electron (in units of electron-volts) are given by \[E(n) = {-m_e e^3 \over 32 \pi^2 \epsilon_0^2 \hbar^2 n^2}\] where \(n\) must be an integer and the other constants are
      • \(m_e = 9.109 \times 10^{-31}\) kg is the mass of an electron.
      • \(e = 1.602 \times 10^{-19}\) C is the fundamental.
      • \(\epsilon_0 = 8.854 \times 10^{-12}\) C V\(^{-1}\) m\(^{-1}\) is the electrical constant.
      • \(\hbar = 1.054 \times 10^{-34}\) J s is the reduced Planck constant.
      1. Construct a function that takes the quantum number (\(n\)) as an argument and returns the energy of the electron in units of electron-volts. All of the fundamental constants in the equation should appear as keyword arguments with default values attached. Call the function for a few values of \(n\) to make sure it is working. You should find that \(E(1) = -13.6\) eV, \(E(2) = -3.4\) eV, \(E(3) = -1.5\) eV, etc.
      2. When an electron transitions from a high energy state to a low energy state, it releases a bundle of light (called a “photon”) with an energy that is exactly equal to the energy difference. Use the function you built in part 1 to calculate the energy of a photon that is released when an electron transitions from the \(n = 5\) state to the \(n = 2\) state.
      3. The wavelength (\(\lambda\)) of the photon can by calculated using the following equation: \[\lambda = {2 \pi \hbar c \over e \Delta E}\]. Calculate the wavelength of light released for the transition described in part 2. Go to this website and determine what color this corresponds to.
      4. Use list comprehension together with the function you built in part 1 to build a list containing the 15 lowest-energy levels for the hydrogen atom.
    me = 9.109e-31
    1. Max Planck discovered that all objects emit a spectrum of radiation and that the intensity of the spectrum depends on the object’s temperature. The equation for the intensity of the emitted light is \[ I(\lambda) = {2 \pi h c^2 \over \lambda^5} {1\over e^{hc \over \lambda k_BT} - 1}\] where
      • \(c = 3.0 \times 10^8\) m/s is the speed of light.
      • \(h = 6.26 \times 10^{-34}\) m\(^2\) kg /s is Plank’s constant.
      • \(k_B = 1.38 \times 10^{-23}\) m\(^2\)kg s\(^{-2}\) K\(^{-1}\) is the Boltzmann constant.
      • T is temperature in Kelvins.
      • \(\lambda\) is wavelength in meters.
      1. Write a function that takes two arguments: the temperature of the object and the wavelength of light. All other physical constants should appear as keyword arguments with appropriate default values attached. The function should return the corresponding light intensity given by the function above.
      2. The temperature of the sun is approximately \(5780\) K and it emits strongly in the visible region of the spectrum. Use the function you built in part 1 to calculate the intensity of light emitted by the sun at the following wavelengths: [250 nm, 500 nm, 750 nm, 1000 nm, 1500 nm] (“nm” is short for nano-meters or \(\times 10^{-9}\) meters). Use list comprehension to build a list of these intensities. Comment on the trend that you notice.
      3. The coldest temperature ever measured in our solar system was \(25\) K at the bottom of a lunar crater during “night” time. That’s -410\(^\circ\) F.. Yowza! However, during “day” time, the temperature of the moon is about \(400\) K. Compute the intensity of daytime light emitted by the moon for the same wavelengths given in part 2. As before, use list comprehension to put these intensities into a list. How do these intensities compare to the sun’s?
      4. The moon emits strongest in the infrared region of the spectrum (\(\lambda > 750\) nm) which our eyes are not able to see. The only reason we can see the moon is because it reflects light from the sun. Recalculate the intensity of light from the moon for the following infrared wavelengths: [6000 nm, 7500 nm, 9000 nm, 10000 nm, 12000 nm]. How do these intensities compare to the sun’s light?
    # Solution here.
    1. Write a function that calculates the area of a chosen, two-dimensional geometric shape from the list below. The function should take one required argument that is a string and specifies which shape is being requested. The side lengths of the shape should be passed in as optional (keyword) arguments. Test your code out on a few shapes to verify that it is working.

    \[A_\text{square} = a^2 ~~\text{(square)}\] \[A_\text{circle} = \pi r^2 ~~\text{(circle)}\] \[A_\text{rectangle} = a \times b ~~\text{(rectangle)}\] \[A_\text{triangle} = {1\over 2} b \times h ~~\text{(triangle)}\] \[A_\text{parallelogram} = b \times h ~~\text{(parallelogram)}\] \[A_\text{pentagon} = {1\over 4} \sqrt{5(5 + 2 \sqrt{5})} a^2 ~~\text{(pentagon)}\] \[A_\text{hexagon} = {3 \sqrt{3}\over 2} a^2 ~~\text{(hexagon)}\]

    # Solution here.
    1. Write a function that calculates the volume of a chosen three-dimensional geometric shape from the list below. The function should take one required argument that is a string and specifies which shape is being requested. The relevant lengths of the shape should be passed in as optional (keyword) arguments. Test your code out on a few shapes to verify that it is working.

    \[V_\text{sphere} = {4\over 3} \pi r^3 ~~\text{(sphere)}\] \[V_\text{cylinder} = \pi r^2 \times h ~~\text{(cylinder)}\] \[V_\text{cube} = a^3 ~~\text{(cube)}\] \[V_\text{cuboid} = a\times b \times c ~~\text{(cuboid)}\] \[V_\text{pyramid} = {l \times w \times h\over 3} ~~\text{(pyramid)}\] \[V_\text{cone} = \pi r^2 {h \over 3} ~~\text{(cone)}\]

    # Solution here.