7. Python Code Blocks: Functions#

7.1. Lesson overview#

A function is a labelled code block that is executed when its name is called. When writing longer Python programs, we may notice sections of code that have a similar logical pattern. We can bundle up that logic into one defined code block called a function, and instead of repeating the same lines of code many times over, we simply call the function with one line of code. This creates a more manageable code base with fewer lines of duplicated code throughout our program. In this lesson, we will explain how to write functions, how to return the output of a function, how to pass arguments to a function, and explore how variables are scoped between function code blocks.

Definition: function

A labelled code block that executes when its name is called.

7.2. Creating a function#

Creating a simple function is straightforward in Python. Let us look at the example code below that creates the function test():

def test():
    print("running test function")

The first line has the command def test(): which is the function declaration statement that starts the code block. The keyword def stands for “define”, as in we are defining our function, and test is the name we give to the function. Function declarations require two parenthesis () after the function name. Finally, there is the colon : which ends the function declaration statement. The subsequent indented code contains the code block that the function will represent. In this case, the function will print out the string running test function to the terminal.

After we have defined our function, we can call it by typing test():

test()
running test function

The parenthesis () after test means we are calling the function. This will execute the code block of the function. If we do not include (), the function will not be called and the code block will not be executed. The importance of using () after the function’s name is demonstrated below:

def test():
    print("running test function")

print("Without ():")
test
print("With ():")
test()
Without ():
With ():
running test function

Hey! Listen!

Remember that consistent indentation inside a code block is required in Python. See Creating Code Blocks for more info.

7.2.1. Example: Insert name here#

Create a function called my_name() that prints out the statement,

My name is [NAME].

where [NAME] is your name. After creating the function, demonstrate that it works.


Solution:

This short example demonstrates the basics in creating and using a function. The code block below uses the name of Goldy Gopher when creating my_name().

# Print out name to shell
def my_name():
    print("My name is Goldy Gopher!")

# Calling function
my_name()
My name is Goldy Gopher!

7.3. Returning an output#

In our example above, the output of the function is printed out. However, we may want a function to do a bunch of operations on some input and then return the result instead of simply printing it out. We can do this by using the return keyword to return the function output, which we can assign to a variable. The code below demonstrates how return is used in a function:

# Convert Fahrenheit to Celsius
def convert_f_to_c(temp):    
    celsius = (temp - 32) * (5/9)
    return celsius
    
temp_c = convert_f_to_c(5)
print("Temp in C:", temp_c)
Temp in C: -15.0

In this example we have defined a function convert_f_to_c() that takes in one argument temp (arguments will be explained in the next section), which represents a temperature in the Fahrenheit scale. Our function converts temp to the Celsius scale and assigns the value to the variable celsius. The return celsius command returns the output of the function. Note that if we do not return the output, then temp_c will get assigned a None value, because the function will have returned nothing.

We can also return multiple outputs from a function through the return keyword. The code below demonstrates how this can be done by modifying our convert_f_to_c() function:

# Convert Fahrenheit to Celsius
def convert_f_to_c(temp):    
    celsius = (temp - 32) * (5/9)
    units = "celsius"
    return celsius, units
    
temp_c, unit = convert_f_to_c(5)
print("Temp:", temp_c)
print("Unit:", unit)
Temp: -15.0
Unit: celsius

Here, the return statement is now written as return celsius, units to signify we want the variables celsius and units returned in that order. In this example, we then assign temp_c to take the first (i.e., celsius) and unit takes the second output (i.e., units) when convert_f_to_c(5) is called.

If you only assign one variable to a multiple output return statement, you will instead get a tuple of the outputs joined together:

output = convert_f_to_c(5)
print("Tuple of outputs:",output)
print("Data type of variable \"output:\"", type(output))
Tuple of outputs: (-15.0, 'celsius')
Data type of variable "output:" <class 'tuple'>

7.3.1. Example: Temperature conversion#

Using the function convert_f_to_c() from the section above, create a code block that converts the following list of temperatures:

Temperatures to convert: 68 oF, -319 oF, 150.2 oF, and 901.4 oF

Use the following guidelines and tips when creating your code:

  • Create a list called temp_f that stores the starting temperatures.

  • In its current state, convert_f_to_c() will not accept a list as an argument. If you do this, you will get an error. Instead, use a for loop to pass each value one at a time into the function.

  • Store your converted temperatures into a new list object called temp_c. You will need to first initialize this object without any value. This can be done with either the command temp_c = [] or temp_c = list().

  • Converted temperatures can be added to temp_c using the .append() method for lists. Look online for examples on how to use this useful method!


Solution:

This example demonstrates how our increasing knowledge in Python allows us to adapt previously made code for new applications. Since convert_f_to_c() only accepts single values as an input (e.g., an int or float object) we utilize a for loop to access each temperature one at a time. An example solution is shown below:

# Convert Fahrenheit to Celsius
def convert_f_to_c(temp):    
    celsius = (temp - 32) * (5/9)
    return celsius

# List of 
temp_f = [68, -319, 150.2, 901.4]

# Initialize converted list
temp_c = []

# Loop over all entries in temp_f
for i in temp_f:
    celsius_temp = round(convert_f_to_c(i),1)
    temp_c.append(celsius_temp)

# Display converted temperatures
print("Temp in C:", temp_c)
Temp in C: [20.0, -195.0, 65.7, 483.0]

7.4. Arguments#

A variable that is passed into a function is called an argument. Arguments allow us to pass in data to the function. In the previous example, the function convert_f_to_c() has one argument, the temp variable. The name of the argument as defined in the function and is passed to the function’s code block as a variable. When we call convert_f_to_c(5), the value 5 is passed as the argument and gets assigned to the variable temp in the function’s code block.

Definition: argument

A variable that is passed into a function.

7.4.1. Positional arguments#

In our function convert_f_to_c(temp), the position in which temp is passed into the function (in this case that temp is actually passed into the function) is critical for the function to work. If we call convert_f_to_c() without passing any arguments we will get an error:

temp_c = convert_f_to_c()
print("Temp in C:", temp_c)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [9], in <cell line: 1>()
----> 1 temp_c = convert_f_to_c()
      2 print("Temp in C:", temp_c)

TypeError: convert_f_to_c() missing 1 required positional argument: 'temp'

The output TypeError: convert_f_to_c() missing 1 required positional argument: 'temp' mentions that we are missing a positional argument. Positional arguments are arguments that are passed into a function in a specific order. For this example, the Python shell is expecting temp to be the first variable passed into. Since no variable is passed into the function, a positional error is flagged.

Definition: positional arguments

Arguments that are passed into a function in a specific order.

As you can probably guess, functions can be called using multiple positional arguments. An example of this is shown below:

def convert_f(temp, units):
    # Convert temp to Celsius
    new_temp = (temp - 32) * (5/9)    
    if units == "K":
        new_temp += 273.15
    return new_temp
    
temp_k = convert_f(5, "K")
print('Temp in K:', temp_k)
Temp in K: 258.15

Here, convert_f() requires two arguments to be passed to it. When we call convert_f(5, 'K'), the order of the arguments are assigned from left to right, so 5 is assigned to temp and 'K' is assigned to units. The Python shell automatically knows what variables to assign each argument to because of their position when passed into the function (hence the name “positional arguments”). Since the specific order that arguments are passed into a function matters when using positional arguments, an incorrect order can result in an error. See the example below in which we flip the argument order:

temp_k = convert_f("K", 5)
print('Temp in K:', temp_k)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [11], in <cell line: 1>()
----> 1 temp_k = convert_f("K", 5)
      2 print('Temp in K:', temp_k)

Input In [10], in convert_f(temp, units)
      1 def convert_f(temp, units):
      2     # Convert temp to Celsius
----> 3     new_temp = (temp - 32) * (5/9)    
      4     if units == "K":
      5         new_temp += 273.15

TypeError: unsupported operand type(s) for -: 'str' and 'int'

Since the shell is expecting a float or int value for temp and not a str, and error is reported when new_temp is assigned. In short, be very mindful of the specific order you pass positional arguments into a function!

7.4.2. Keyword arguments#

Another way we can pass an argument into a function is by associating an argument to a specific identifier (i.e., a keyword) that is associated with the function’s code block. Arguments that are passed this way are called keyword argument, and are useful as there is no ambiguity into what each argument represents.

Definition: keyword arguments

Arguments that are assigned an identifying keyword when passed into a function.

For example, let us demonstrate how keyword arguments can be used with our convert_f() function. The example below shows how to pass our two input variables as keyword arguments:

temp_k = convert_f(temp = 5, units = "K")
print("Temp in K:", temp_k)
Temp in K: 258.15

When using keyword arguments, we pass arguments using the format keyword = value. Since the arguments are explicitly defined, the specific order how they are passed into the function do not matter. For example, the code below flips the positional order of the keyword arguments with no issue:

temp_k = convert_f(units = "K", temp = 5)
print("Temp in K:", temp_k)
Temp in K: 258.15

Overall, calling a function using keyword arguments is a helpful for following the logic and readability of your Python code.

7.4.3. Common errors when passing arguments#

There are a few common errors to watch out for when passing arguments into functions. For example, if we mix keyword and positional arguments we will see the following error:

temp_k = convert_f(temp = 5, "K")
print("Temp in K:", temp_k)
  Input In [14]
    temp_k = convert_f(temp = 5, "K")
                                    ^
SyntaxError: positional argument follows keyword argument

The error SyntaxError: positional argument follows keyword argument is reporting that we tried to pass a positional argument after a keyword argument, which we cannot do. When calling a function, all positional arguments must be listed first before any keyword arguments.

A similar error will occur if we try to pass too many arguments into a single variable. An example of this is shown below:

temp_k = convert_f(5, temp = 10)
print("Temp in K:", temp_k)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [15], in <cell line: 1>()
----> 1 temp_k = convert_f(5, temp = 10)
      2 print("Temp in K:", temp_k)

TypeError: convert_f() got multiple values for argument 'temp'

Here, the error TypeError: convert_f() got multiple values for argument 'temp' is reporting that we already passed a value to temp with the first positional argument, so passing temp = 10 afterwards caused an error. Likewise, if you pass more arguments than what a function is expecting, Python will report an error:

temp_k = convert_f(5, "K", 10)
print("Temp in K:", temp_k)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [16], in <cell line: 1>()
----> 1 temp_k = convert_f(5, "K", 10)
      2 print("Temp in K:", temp_k)

TypeError: convert_f() takes 2 positional arguments but 3 were given

The error TypeError: convert_f() takes 2 positional arguments but 3 were given reports that convert_f()only accepts two arguments (i.e., temp and units), but the function received three arguments which caused the error.

7.4.4. Assigning default values to arguments#

Sometimes it is useful to assign a default value to an argument if that value is commonly used. An added benefit to doing this is that these arguments no longer need to be assigned during every function call. The code below demonstrates this concept using a function that converts a temperature from one of the four common temperature scales (Celsius, Fahrenheit, Kelvin, or Rankine) to another temperature scale:

def convert_temp(temp, output_unit = "K", input_unit = "F"):

    # Convert input temp to Kelvin
    if input_unit == "F":    
        temp_k = (temp - 32) * (5 / 9) + 273.15
    elif input_unit == "C":
        temp_k = temp + 273.15
    elif input_unit == "R":
        temp_k = temp * (5 / 9)
    
    # Convert Kelvin to desired output
    if output_unit == "F":
        new_temp = ((temp_k - 273.15) * (9 / 5)) + 32 
    elif output_unit == "C":
        new_temp = temp_k - 273.15
    elif output_unit == "R":
        new_temp = temp_k * (9 / 5)
    elif output_unit == "K":
        new_temp = temp_k
        
    return new_temp, output_unit
    
temp, unit = convert_temp(5)
print(f"Temp in {unit}:", round(temp,1))
Temp in K: 258.1

As seen above, our function takes in three arguments. Notice that two of the arguments are assigned values in the def line by using the keyword argument notation from earlier (i.e., output_unit = "K" and input_unit = "F"). It is through this process that we can declare a default value for an argument. Notice though that the argument temp has not been assigned a default value. Therefore, any time we want to call convert_temp(), we will need to provide an argument for temp. This can be done using a positional argument, like shown in the example above, or using a keyword argument:

temp, unit = convert_temp(temp = 10)
print(f"Temp in {unit}:", round(temp,1))
Temp in K: 260.9

Even though we have defined default values to both output_unit and input_unit, we can override them during a function call by either using a positional or keyword argument. An example of this is shown below:

temp, unit = convert_temp(5, "F", "C")
print(f"Temp in {unit}:", round(temp,1))
Temp in F: 41.0

Here, the function call convert_temp(5, 'F', 'C') assigns 5 to temp, 'F' to output_unit, and C to input_unit, which follows the argument order of the function. The function then returns the value 41.0, which makes sense since \(5 \ ^o C = 41 \ ^o F\). However, overriding default values is not an “all or nothing” situation. We can also selectively choose which argument values to override while retaining the default state for other arguments. An example of this is shown in the code below:

temp, unit = convert_temp(5, input_unit = "C")
print(f"Temp in {unit}:", round(temp,1))
Temp in K: 278.1

In this example, we choose to only override the default value for input_unit while still using the default value for output_unit. There are limits to how the Python shell will accept a mixed ordering of default and non-default arguments. For example, all arguments that do NOT have a default value must be listed prior to the arguments that will have a default value. This requirement in Python prevents possible positional argument errors from originating during later function calls as the shell will not be able to link a positional argument to the appropriate non-default valued argument. The example below shows what will happen if you fail to do this:

def convert_temp(temp = 10, output_unit, input_unit = "F"):
    # Convert input temp to Kelvin
    if input_unit == "F":    
        temp_k = (temp - 32) * (5 / 9) + 273.15
    elif input_unit == "C":
        temp_k = temp + 273.15
    elif input_unit == "R":
        temp_k = temp * (5 / 9)
    
    # Convert Kelvin to desired output
    if output_unit == "F":
        new_temp = ((temp_k - 273.15) * (9 / 5)) + 32 
    elif output_unit == "C":
        new_temp = temp_k - 273.15
    elif output_unit == "R":
        new_temp = temp_k * (9 / 5)
    elif output_unit == "K":
        new_temp = temp_k
        
    return new_temp, output_unit
    
temp, unit = convert_temp("C")
print(f"Temp in {unit:}:", round(temp,1))
  Input In [21]
    def convert_temp(temp = 10, output_unit, input_unit = "F"):
                                ^
SyntaxError: non-default argument follows default argument

Here, the error SyntaxError: non-default argument follows default argument occurring in Line 1 (i.e., during the creation of the function using the def keyword) indicates that an argument without a default value is being listed prior to an argument that has a default value. A simple fix to this error is to switch the order of the arguments in the def line so that output_unit (i.e., the argument without a default value) is listed first:

def convert_temp(output_unit, temp = 10, input_unit = "F"):
    # Convert input temp to Kelvin
    if input_unit == "F":    
        temp_k = (temp - 32) * (5 / 9) + 273.15
    elif input_unit == "C":
        temp_k = temp + 273.15
    elif input_unit == "R":
        temp_k = temp * (5 / 9)
    
    # Convert Kelvin to desired output
    if output_unit == "F":
        new_temp = ((temp_k - 273.15) * (9 / 5)) + 32 
    elif output_unit == "C":
        new_temp = temp_k - 273.15
    elif output_unit == "R":
        new_temp = temp_k * (9 / 5)
    elif output_unit == "K":
        new_temp = temp_k
        
    return new_temp, output_unit
    
temp, unit = convert_temp("C")
print(f"Temp in {unit:}:", round(temp,1))
Temp in C: -12.2

Now the function works according to plan!

Hey! Listen!

When looking through Python code, you sometimes may see function definitions that include the syntaxes *args and/or **kwargs, like for example,

def do_stuff(a, b = 3, *args, **kwargs):.

Both of these syntaxes are known as arbitrary argument lists, and allow a function to be called with any number of arguments. The *args syntax allows for a list of positional arguments and the **kwargs syntax allows for a dict of keyword arguments. Arbitrary argument lists are useful in situations where functions are calling other functions, which then call even more functions. The use of *args or **kwargs allows arguments to pass through multiple functions so each function can get the arguments it needs.

7.5. Variable scoping in code blocks#

A typical Python program will use many variables across many code blocks, so it is important to keep in mind the scope of a variable’s name across blocks of code. The scope represents how visible a variable’s name is seen throughout the code. Depending on how a variable is defined, the scope of a variable may be visible throughout the entire code or only within a small code block. This is demonstrated in the follow example:

num = 1                     #### Start code block 1, global scope

def multi_twenty(num):         #### Start code block 2, local scope    
    # multiply num by 20          # local scope
    print("local num:", num)      #
    print("id num:", id(num))     #
    num = num * 20                # local scope
    return num                 #### End of code block 2, local scope   

big_num = multi_twenty(5)      #  
print("Big num", big_num)      #  
print("global num:", num)      #
print("id num:", id(num))   #### End of code block 1, global scope
local num: 5
id num: 140693339324784
Big num 100
global num: 1
id num: 140693339324656

In this example, we assign a variable named num in two places. We first assign num the value 1 in code block 1, and then assign num a different value (via a recursive value of num * 20) in the function multi_twenty, which is part of code block two. Each of these code blocks have a different variable scope for num. Code block one, has a global variable scope as it is the “outermost” code block. Code block two, which is an “inner” code block, has a local variable scope inside the code block. When we call multi_twenty(5), we assign 5 to num in the function, which is technically a different variable named num from the globally scoped num variable in the first line of code. This is proven using the function id() that shows each num variable is referencing two different memory locations.

Definition: scope

How visible a variable’s name is seen throughout the code.

It is important to note that variables assigned in a higher scope can be read in a local scope. The example below demonstrates with the globally scoped variable C_RATIO:

# global scope 
C_RATIO = 5/9

# code block scope (functional scope)
def convert_f_to_c(temp):    
    celsius = (temp - 32) * C_RATIO
    return celsius
    
temp_c = convert_f_to_c(5)
print("Temp in C:", temp_c)
Temp in C: -15.0

Here, we see that C_RATIO is read inside the function convert_f_to_c() even though it was not passed as an argument. If we created a new variable called C_RATIO inside of convert_f_to_c(), this new variable would have been used instead of the original value (i.e., see the previous example using our multi_twenty() function). All in all, failure to keep the scope of variables in mind for your code can lead to unintended consequences.

Hey! Listen!

While not required for a program to work, it is recommended to name global variables using all uppercase letters. For instance, in the above example we wrote C_RATIO = 5/9 instead of c_ratio = 5/9. While there is no functional difference between uppercase and lowercase variable names, uppercasing global variables makes it easier to understand which variables are intended to be global in scope.

7.6. Docstrings and help()#

It is important to document how your function operates in case another user (or even yourself) would like to reuse your function on a later date. A docstring is a string that follows after the function definition statement (i.e., the line with def) and provides a place to describe what a function does, the arguments that are passed into the function, details about the arguments, and the function returns.

Docstrings are built into all functions in Python. To create a docstring we use the multi-line string notation (i.e., a str that starts and ends with three single or double quotes) on the line after the def line. The code below demonstrates how a docstring can be used with our convert_temp() function from earlier:

As mentioned above, docstrings utilize the multi-line string format in which the string content is bounded between triple quotes (here we used """). As a side note, besides being used for docstrings, the multi-line string format is a useful format whenever you need to create a string that needs to span multiple lines of code:

multi_line_string = """
We can type
multiple lines
in a triple quoted string.
"""
print(multi_line_string)
We can type
multiple lines
in a triple quoted string.
help(convert_temp)
Help on function convert_temp in module __main__:

convert_temp(temp, output_unit='K', input_unit='F')
    Convert temperature from one unit to another. Returns temperature and output_unit.
    
    Arguments:
    temp: Numeric temperature to be converted
    output_unit: String designating the output unit. Can be one of 'F', 'C', 'R', 'K'. Default is 'K'
    input_unit: String designating the input unit. Can be one of 'F', 'C', 'R', 'K'. Default is 'F'
    
    Returns:
    new_temp: New temperature
    output_unit: Unit of the new temperature

Here, you can see that the return is the entire docstring for the function! There are numerous ways to format a docstring, and sometimes when browsing Python source code, you may see docstrings in this format:

def convert_temp_docs(temp, output_unit='K'):
    """
    Convert temperature from one unit to another. Returns temperature and output_unit.
    
    :param temp: Temperature that will be converted
    :type: float
    :param output_unit: String designating the output unit. Can be one of 'F', 'C', 'R', 'K'. Default is 'K'
    :type: str
    """
    pass
   

This is an example of the ReStructuredText format for styling docstrings. By developing a docstring format, documentation generators can automatically create documentation based on the docstring of the functions and classes. It is important to note the ReStructuredText keywords :param, :type, and :return: are not part of the function definition, they are just helpful hints for documentation generation. There are several other style guides that exist for Python docstrings, and you can search for them online to find one that works for you. By following the rules of a docstring style guide will allow clear and readable documentation of your code.

7.6.1. Example: Documenting your work#

Using what you have learned so far with this guide, create a function that converts a force between one of the three most commonly used force units (i.e., newtons (N), pound-force (lbf), and dyne (dyn)). The conversion between the three unit scales is,

1 N = 0.225 lbf = 100,000 dyn

Input arguments should include the starting force’ value, the starting force’s units, and the desired units. Set default values for the initial units and the final units to be newtons and pound-force, respectively. Include a way for the function to display an error if the starting or desired units are not one of the three units. Finally, include a docstring that follows the ReStructuredText format.


Solution:

There are a few ways to code this function. The example code below builds off of the earlier temperature conversion function by first converting the starting force to newtons and then converting to the desired force. A chain of if, elif, and else keywords are used for the logic checks. Error handling is done by passing strings through the function that state that an error has been caused. These can be registered by viewing the returned variables. While this is works for our needs, there are more effective ways to address error states using logging reports called tracebacks. An upcoming lesson will show you how to utilize tracebacks for documenting errors. Finally, note the use of the ReStructuredText documentation format for the docstring. While not mandatory when creating function, adding a docstring, even if it is written in plain text, is useful for explaining how function operates.

def convert_force(initial_force, initial_units="N", converted_units="lbf"):
    """
    Convert force between newtons, pound force, and dynes. Returns the 
    converted force and units.

    :param initial_force: Force to be converted
    :type: float
    :param initial_units: Initial units of force. Allowable values are 
    "N", "lbf", and "dyn". Default value is "N"
    :type: str
    :param converted_units: Units of force to be converted to. Allowable 
    values are "N", "lbf", and "dyn". Default value is "N"
    :type: str

    :return:
        - converted_force - converted force value (type: float)
        - converted_units - final units (type: str)
    """
    
    # Convert force to N to standardize
    if initial_units == "N":
        force_N = initial_force
    elif initial_units == "lbf":
        force_N = initial_force / 0.225
    elif initial_units == "dyn":
        force_N = initial_force / 100000
    else:
        force_N = "\"incorrect initial force units\""

    # Convert to new units
    if force_N == "\"incorrect initial force units\"":
        converted_force = force_N
    elif converted_units == "N":
        converted_force = force_N
    elif converted_units == "lbf":
        converted_force = force_N * 0.225
    elif converted_units == "dyn":
        converted_force = force_N * 100000
    else:
        converted_force = "\"incorrect converted force units\""
        converted_units = ""
    
    return converted_force, converted_units

To test the basic “functionality” of this function, the code below converts 10 N of force to pounds-force. We should get 2.25 lbf.

converted_force, converted_units = convert_force(10)

print(f"The converted force is {converted_force} {converted_units}")
The converted force is 2.25 lbf

Since we are using the default values for initial_units and converted_units, we do not need to include them in the argument list. For readability, however, it is sometimes better to include them:

converted_force, converted_units = convert_force(10, initial_units="N", converted_units="lbf")

print(f"The converted force is {converted_force} {converted_units}")
The converted force is 2.25 lbf

Let’s now try converting 2,536.2 dyn to newtons. We should get 0.025362 N, which is shown in the code below:

converted_force, converted_units = convert_force(2536.2, initial_units="dyn", converted_units="N")

print(f"The converted force is {converted_force} {converted_units}")
The converted force is 0.025362 N

The code block below demonstrates how the function handles an incorrect desired unit:

converted_force, converted_units = convert_force(2536.2, initial_units="dyn", converted_units="bad")

print(f"The converted force is {converted_force} {converted_units}")
The converted force is "incorrect converted force units" 

Finally, to show that the docstring works for this function, we issue the help() command:

help(convert_force)
Help on function convert_force in module __main__:

convert_force(initial_force, initial_units='N', converted_units='lbf')
    Convert force between newtons, pound force, and dynes. Returns the 
    converted force and units.
    
    :param initial_force: Force to be converted
    :type: float
    :param initial_units: Initial units of force. Allowable values are 
    "N", "lbf", and "dyn". Default value is "N"
    :type: str
    :param converted_units: Units of force to be converted to. Allowable 
    values are "N", "lbf", and "dyn". Default value is "N"
    :type: str
    
    :return:
        - converted_force - converted force value (type: float)
        - converted_units - final units (type: str)

Depending on your programming environment, your help() return may display the docstring in either plain text or as ReStructuredText format. Here, the shell returns the docstring in a plain text format.


7.7. Conclusion#

An important “Pythonic” principle is to avoid having duplicated code throughout a program. With a function, we define a reusable code block that accepts input arguments and returns an output. From here, we can call this code block many times throughout a program and issue different arguments to this code block without having to rewrite code. Functions can have variables passed into them using both positional arguments and keyword arguments, and we can also define default values for arguments. Each function’s code block has its own variable scope in which it operates, which is important to track in order to prevent unintended behavior from a program. Finally, documenting how a function works is an extremely important part of function creation. The use of docstrings in Python makes function documentation easy to create, and the built-in function help() makes it very easy to read a function’s docstring.