Validate Arguments Passed to a Function

The Importance of Evaluating Argument Functions

In Python, we often pass objects to our functions - be it variables, tuples or lists.

Before we compute the logic of our function, it's often a good idea to first validate that the arguments passed to the function meet our criteria.

For example, if we had a function that calculated the area of a square, we can't compute the area given a side lenght of -2. Shapes can't have negative side lengths, otherwise they wouldn't be a shape.

The evaluation of our arguments before going into the main logic of our function is a pattern called a guardian. The first three coditions act as guardians, protecting the code from errors not unique to our main logic.

Example - Validate Arguments to a Function

Let's start with a function we built in another tutorial to calculate the slope of a line when given two coordinate points.

In this example, we'll validate the conditions that:

1) Each coordinate point is in a collection that's either a list or tuple.

2) Check there are two coordinate points - equivalent to an x and y coordinate, in each data structure structure.

3) The values in that data structure are real numbers - so either an int or float.

Our initial function

def slope_of_line(point_one, point_two):
    """
    Calculate slope of a line given two points

    Keyword arguments:
    point_one -- tuple of two numeric values
    point_two -- tuple of two numeric values
    """
    x_index = 0
    y_index = 1
    numerator = point_two[y_index] - point_one[y_index]
    denominator = point_two[x_index] - point_one[x_index]
    slope = numerator / denominator

    return slope

New concepts to use in our validation

conditional expressions

Typically we say if-else statements span multiple lines. However, we can often write them in one line with a conditional expression.

Here's an example of a conditional statement:

number_status = None

if 5 > 2:
    number_status = True
else:
    number_status = False
number_status
True

Here's that same statement as a conditional expression

number_status = 5 > 2
number_status
True

In this conditional expression, number_status is assigned True if the condition is met; otherwise it's assigned False.

isinstance function

In Python, we can utilize the built-in isinstance function to check an object's type.

Here's an example in which we check if "hi" is a string. Since it is a string, the isinstance function will return True.

isinstance("hi", str)
True

We can pass a tuple to the second argument of isinstance to check for multiple types. As long as our coordinate point is at least one of those types, isinstance will evaluate to True.

isinstance(3, (int, str))
True

We're returned True because 3 is of type int - an integer

len function

We can use the Python built-in len() function to calculate number of items in a tuple or list.

len([3, 2])
2

We're returned 2 because there are two items in the list above

all function

The Python built-in all function. It returns True if all boolean values are True in the sequence; otherwise, it returns False.

Here's all True values in a list.

all([True, True])
True

As expected, we're returned True.

And if there's a False value in a list...

all([True, False])
False
list comprehensions

Generate a new list in one line

[x+5 for x in (3,2)]
[8, 7]

We looped through the tuple of (3, 2) and added 5 to each item. We're returned a new list.

Create function to evaluate necessary conditions

def evaluate_arguments_to_calculate_slope(point):
    """
    Evaluate three conditions of point to see if we can later use this point to calculate the slope of a line

    Keyword arguments:
    point -- tuple or list of x-y coordinates of a point
    """
    precondition_statuses = []

    # validate each data structure is a list or tuple
    condition_status = isinstance(point, (tuple, list))
    precondition_statuses.append(("tuple or list data structure", condition_status))

    # validate there are two values in that data structure
    condition_status = len(point) == 2
    precondition_statuses.append(("two values in data structures", condition_status))

    '''
    Validate the two values in that data struxture are floats or ints.
    Create a list comprehension to create a new list of two Boolean values.
    Logic returns True if the value is a float or int and False if neither data type
    '''
    digit_statuses = [isinstance(value, (float, int)) for value in point]
    # returns True if both items in list are boolean True values; otherwise, returns False
    condition_status = all(digit_statuses)
    precondition_statuses.append(("ints or floats", condition_status))

    return precondition_statuses

We can validate this function works.

evaluate_arguments_to_calculate_slope([3, 2])
[('tuple or list data structure', True),
 ('two values in data structures', True),
 ('ints or floats', True)]

[3, 2] meets all our condtions so we see 3 True value.

evaluate_arguments_to_calculate_slope([3, "hi"])
[('tuple or list data structure', True),
 ('two values in data structures', True),
 ('ints or floats', False)]

[3, "hi"] doesn't meet all our conditions since "hi" is a string, not a float or int.

Programatically analyze condition results

We can automate the manual analysis to see the returned boolean values of our evaluate_arguments_to_calculate_slope function.

def all_argument_conditions_met(condition_results):
    """
    Evalute booleans of conditions

    Keyword arguments:
    condition_results -- list of tuples of (condition name, boolean status)
    """
    conditions_pass = True

    for condition in condition_results:
        if condition[1] is False:
            conditions_pass = False
    return conditions_pass
all_argument_conditions_met([('tuple or list data structure', True), ('two values in data structures', True), ('ints or floats', False)])
False

Our all_argument_conditions_met evaluates to False because one condition was False

Put All Our Functions Together

def slope_of_line(point_one, point_two):
    """
    Calculate slope of a line given two points

    Keyword arguments:
    point_one -- tuple of two numeric values
    point_two -- tuple of two numeric values
    """
    return_value = None

    point_one_status = all_argument_conditions_met(evaluate_arguments_to_calculate_slope(point_one))
    point_two_status = all_argument_conditions_met(evaluate_arguments_to_calculate_slope(point_two))

    if point_one_status is True and point_two_status is True:
        x_index = 0
        y_index = 1
        numerator = point_two[y_index] - point_one[y_index]
        denominator = point_two[x_index] - point_one[x_index]
        slope = numerator / denominator
        return_value = slope
    else:
        return_value = "We can't calculate the slope because one of our pre-conditions was not met"
    return return_value

Test Function with Various Point Combinations

point_one = (1, 1)
point_two = (2, 3)
point_three = (5, 5)
print(slope_of_line(point_one, point_two))  # should return 2
2.0
print(slope_of_line(point_one, point_three)) # should return 1
1.0
print(slope_of_line(point_two, point_three)) # should return 2/3 or 0.67
0.6666666666666666