*******
If/Else
*******
.. admonition:: Activity
:class: activity
Using only what we have learned so far, write a function ``smush(a_number)`` that checks if the number is
positive or negative. If ``a_number`` is positive, the function will return half the value of ``a_number``. If
the number is not positive, simply return ``a_number``.
Conditional Expressions
=======================
* Booleans were discussed in the previous topic
* We discussed them as a type
* How to use Boolean operators
* How to use comparison operators
* But Booleans allow us to do a lot more than just evaluate an expression to ``True``/``False``
* Consider ``smush`` from the above activity
* We know how to *ask* if ``a_number`` is positive
* ``a_number > 0``
* The key now is to tell Python that, ``if`` some condition is ``True``, do something
.. code-block:: python
:linenos:
:emphasize-lines: 11
def smush(a_number: float) -> float:
"""
Returns half the value of the parameter a_number if the value is positive,
otherwise, return the value of a_number.
:rtype: float
:param a_number: Some arbitrary number.
:return: Half of a_number when it is positive, a_number when not positive.
"""
return_value = a_number
if a_number > 0:
return_value = a_number / 2
return return_value
# Tests for smush
assert 5 == smush(10)
assert -10 == smush(-10)
assert 0 == smush(0)
* In the above example we made use of an ``if`` statement
* When the evaluated Boolean value after the ``if`` is ``True``, the indented code is executed, otherwise the indented code block is ignored
* If we follow the code within the function when ``a_number`` is ``10``, the execution is as follows
* Assign the value of ``a_number`` (``10``) to ``return_value``
* Evaluate ``a_number > 0``
* ``10 > 0``
* ``True``
* Since the expression evaluated to ``True``, the indented code is run
* Divide ``return_value`` by ``2`` and assign it back into ``return_value``
* Return the value stored in ``return_value`` (``5``)
* Similarly, if we follow the code when ``a_number`` is ``-10``, the execution is as follows
* Assign the value of ``a_number`` (``-10``) to ``return_value``
* Evaluate ``a_number > 0``
* ``-10 > 0``
* ``False``
* Since the expression evaluated to ``False``, the indented code is skipped
* Return the value stored in ``return_value`` (``-10``)
.. admonition:: Activity
:class: activity
Write a function ``is_negative(a_number)`` that returns ``True`` if ``a_number`` is negative and ``False``
otherwise.
Compound Conditions
===================
* When we make use of an ``if`` statement, the value being checked needs to ultimately be evaluated to a Boolean
* This means that we can make use of more complex compound Boolean expressions
* Comparison operators
* Arithmatic operators
* Boolean operators
.. code-block:: python
:linenos:
:emphasize-lines: 10
def three_five_divisible(a_number: float) -> str:
"""
Checks if a number is divisible by both three and five. If it is, return
a string "It is!", otherwise "Nope".
:rtype: str
:param a_number: Some arbitrary number.
:return: String indicating if the number is divisible by three and five
"""
if a_number % 3 == 0 and a_number % 5 == 0:
return "It is!"
return "Nope"
# Tests for three_five_divisible
assert "It is!" == three_five_divisible(0)
assert "It is!" == three_five_divisible(15)
assert "It is!" == three_five_divisible(-30)
assert "Nope" == three_five_divisible(3) # Divisible by 3 but not 5
assert "Nope" == three_five_divisible(-50) # Divisible by 5 but not 3
assert "Nope" == three_five_divisible(1) # Divisible by neither
.. note::
The modulo operator ``%`` (often called just "mod") effectively does division and returns the **remainder**. For
example, ``10 % 3`` is ``1`` since ``10/3`` is ``3`` remainder ``1``.
In the ``three_five_divisible`` example, we are checking if the remainder of the division is ``0``, which would mean
that the value can be evenly divided.
Another common use of ``%`` is checking if a value is even or not --- ``x % 2`` is ``0`` when ``x`` is even since
it would mean that ``x`` can be evenly divided by ``2``.
* The above function ``three_five_divisible`` needs to check if a number is divisible by 3 ``and`` 5
* This means that there are two conditions we need to check for being ``True``
* If we follow the code within the function when ``a_number`` is ``15``, the execution is as follows
* Evaluate ``a_number % 3 == 0 and a_number % 5 == 0``
* ``15 % 3 == 0 and 15 % 5 == 0``
* ``0 == 0 and 0 == 0``
* ``True and True``
* ``True``
* Since the expression evaluated to ``True``, the indented code is run
* Return ``"It is!"``, function ends
* If we follow the code within the function when ``a_number`` is ``9``, the execution is as follows
* Evaluate ``a_number % 3 == 0 and a_number % 5 == 0``
* ``9 % 3 == 0 and 9 % 5 == 0``
* ``0 == 0 and 4 == 0``
* ``True and False``
* ``False``
* Since the expression evaluated to ``False``, the indented code is skipped
* Return ``"Nope"``
.. raw:: html
Alternative Execution
=====================
* This pattern is very common
.. code-block:: python
:linenos:
if x > 10:
do_something()
if not(x > 10):
do_something_else()
* When we have an either/or situation we make use of ``else``
.. code-block:: python
:linenos:
if x > 10:
do_something()
else:
do_something_else()
* The two examples above will effectively do the same thing, but the 2nd is nicer
* Write less
* Intuitive and easy to read/understand
* Eliminate potential bugs
.. admonition:: Activity
:class: activity
Write a function called ``hail`` that takes an integer as an argument. If the integer is even, return the value of
that integer divided by 2. If it is odd, return the value multiplied by 3 and with one added. In other words,
given a number :math:`n`, return :math:`n/2` when it is even and :math:`3n + 1` when it is odd. **Hint:** Don't
forget about ``%``.
.. raw:: html
`This is actually some neat math stuff `_. Isn't it cool that
we're writing a Python function that's doing exactly what the math is saying?
.. note::
If we revisit ``smush``, we can rewrite the function in a few different ways that are all correct.
.. code-block:: python
:linenos:
def smush_version_2(a_number: float) -> float:
"""
Returns half the value of the parameter a_number if the value is positive,
otherwise, return the value of a_number.
:rtype: float
:param a_number: Some arbitrary number.
:return: Half of a_number when it is positive, a_number when not positive.
"""
if a_number > 0:
return_value = a_number / 2
else:
return_value = a_number
return return_value
In ``smush_version_2``, an ``else`` is used and the function has only one ``return``. The use of the ``else`` here
is not required (as seen in the original ``smush``), but the use of ``else`` in this situation may make the function
a little clearer. Additionally, some programmers prefer having their functions have only one ``return``, but this is
by no means *more correct*.
.. code-block:: python
:linenos:
def smush_version_3(a_number: float) -> float:
"""
Returns half the value of the parameter a_number if the value is positive,
otherwise, return the value of a_number.
:rtype: float
:param a_number: Some arbitrary number.
:return: Half of a_number when it is positive, a_number when not positive.
"""
if a_number > 0:
return a_number / 2
else:
return a_number
Another possibility is ``smush_version_3``. You will notice how similar it is to version 2, but here we use two
``return``\s in the ``if`` and ``else`` blocks. Again, this is not *more correct* and it is only shown here to
demonstrate how the same functionality can be implemented differently.
Exclusive Alternatives
======================
* Sometimes we need to check various conditions and ``if``/``else`` isn't good enough
* For example, what if I want a function to take a percentage grade and return a letter grade
.. code-block:: python
:linenos:
def letter_grade_broken(percent_grade: float) -> str:
"""
Calculate the letter grade associated with the provided percent grade.
:rtype: str
:param percent_grade: A grade as a percent
:return: Letter grade for the provided percentage
"""
letter_grade = ""
if percent_grade >= 90:
letter_grade = "A+"
if percent_grade >= 80:
letter_grade = "A"
if percent_grade >= 70:
letter_grade = "B"
if percent_grade >= 60:
letter_grade = "C"
if percent_grade >= 50:
letter_grade = "D"
else:
letter_grade = "F"
return letter_grade
* The above example ``letter_grade_broken`` may be one of the first ideas you come up with, but unfortunately it has a problem
* If we run ``assert "A+" == letter_grade_broken(99)``
* ``letter_grade_broken(99)`` would actually return ``"D"``
* The trick to understanding the problem is to take our time and look at the code
* Call ``letter_grade_broken(99)``
* ``percent_grade`` is assigned the value ``99``
* Check if ``percent_grade >= 90``
* ``percent_grade >= 90``
* ``99 >= 90``
* ``True``
* Since the expression is evaluated to ``True``, the indented code is run
* Assign ``letter_grade`` the value ``"A+"``
* The execution continues
* Check if ``percent_grade >= 80``
* ``percent_grade >= 80``
* ``99 >= 80``
* ``True``
* Since the expression is evaluated to ``True``, the indented code is run
* Assign ``letter_grade`` the value ``"A"``
* ...
* The trouble here is that we really only want one of these ``if`` code blocks to run
* We want them to be *mutually exclusive* alternatives
* There are a few ways one could fix this
* Have a ``return`` in each indented block since that would stop execution of the function once a ``return`` is reached
* Reverse the order of the ``if``\s
* Check upper and lower bounds (e.g. ``percent_grade >= 80 and percent_grade < 90``)
* But arguably the better way to address this is with ``elif``\s
* Can be read as *else, if...*
* These allow us to have at most one of the code blocks in the chain of conditions to run
* In other words, as soon as one of the ``if``\s is true, all other ``if``\s are skipped and the program continues running after the ``else``
* When using ``elif``\s, always end with a final ``else``
.. code-block:: python
:linenos:
def letter_grade(percent_grade: float) -> str:
"""
Calculate the letter grade associated with the provided percent grade.
:rtype: str
:param percent_grade: A grade as a percent
:return: Letter grade for the provided percentage
"""
letter_grade = ""
if percent_grade >= 90:
letter_grade = "A+"
elif percent_grade >= 80:
letter_grade = "A"
elif percent_grade >= 70:
letter_grade = "B"
elif percent_grade >= 60:
letter_grade = "C"
elif percent_grade >= 50:
letter_grade = "D"
else:
letter_grade = "F"
return letter_grade
.. raw:: html
Nesting Conditionals
====================
.. image:: dolls.jpeg
* You can "nest" conditionals inside other conditionals
.. code-block:: python
:linenos:
# Find quadrant with 'nested If's
if x > 0:
if y > 0:
print("First Quadrant")
else:
print("Fourth Quadrant")
else:
if y > 0:
print("Second Quadrant")
else:
print("Third Quadrant")
* For simplicity, ignore point :math:`(0,0)` being in the third quadrant
* In the above example, we *could* have done it without nesting by using ``and``\s
* But some may find the nested version of the code more intuitive and readable
.. code-block:: python
:linenos:
# Find quadrant with 'and's
if x > 0 and y > 0:
print("First Quadrant")
elif x > 0 and y < 0
print("Fourth Quadrant")
elif x < 0 and y > 0:
print("Second Quadrant")
else:
print("Third Quadrant")
For Next Class
==============
* Read `Chapter 6 of the text `_