# 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
```