Skip to content

Building Basic Logic in Python

Lesson 5: Conditional


We often want our programs to take actions based on what's going on at the time (e.g. the current value of some variable). In a Python program, the if...elif...else statement is how we perform this sort of decision-making. It allows for conditional execution of a line of code or a block of codes based on the value of the conditional expression.

Booleans

A conditional expression in Python is evaluated to a boolean, bool for short. A bool variable can take two values, True or False. Python implements all of the usual operators for bool logic using simple English words and, or, and not.

b1 = True
b2 = False
b1 and b2
False
b1 or b2
True
not b1
False

First conditional statement

b = True

if b:
    print("This will be printed")
This will be printed

b = False

if b:
    print("This will not be printed")

What is true?

The bool function

We can check whether a variable of other data types (e.g. int, string, and even list) will be regarded as True or False using the bool() function. Some of the results in the below examples (e.g. bool[""]) may not be what you think at first sight. Loosely speaking, for container data types like list, an empty state will be evaluted to False.

bool(3)
True
bool(0.0)
False
bool("Berkeley")
True
bool("")
False
bool([1, 2, 3])
True
bool([])
False
bool([0])
True
bool([""])
True

No need to explicitly convert using the bool() function

if []:
    print("This will not be printed")
if [""]:
    print("This will be printed")
This will be printed

Some other conditional expressions

  • ==, !=, >, <
  • in membership assesment
  • is identity operator
  • in and is can be combined with not to form not in and is not
3 == 2
False
"H" in "Hydrogen"  # character(s) in string
True
"a" not in "Hydrogen"
True
2 in [1, 2, 3]  # componment in list
True
element = None

element is None
True
element = "Hydrogen"

element is not None
True

The if elif else statement

Let's start with an illustrative example of the state of water based on temperature.

water_temperature = -1  # degrees celsius

Below is the simple if statement: "if the temperature of water is greater than or equal to 100 °C, it must be boiling".

if water_temperature >= 100:
    print("Boiling!")

Now, let's add an else to catch our if statement: "if ... greater than ..., otherwise, the water is not boiling".

if water_temperature >= 100:
    print("Boiling!")
else:
    print("Not boiling!")
Not boiling!

We can increase the number of levels of the elif statement in the following manner:

if (condition 1):
    execute code for case 1
.
.
.
elif (condition k):
    execute code for case k
.
.
.
elif (condition n):
    execute code for case n
else:
    execute "catch all" case
water_temperature = -5

if water_temperature >= 100:
    print("Gas!")
elif water_temperature > 0:
    print("Liquid!")
else:
    print("Solid!")
Solid!

Lesson 6: Dictionary


Dictionary is a data type to store key:value pairs. It maps key to value. Dictionaries are optimized for retrieving data. We must know the key to retrieve the value. A dictionary is ordered (since Python 3.7), changeable, and does not allow duplicates.

Create dict and access value

elements = {"Hydrogen": 1, "Carbon": 6}
elements
{'Hydrogen': 1, 'Carbon': 6}
n = elements["Carbon"]
n
6

Basic operations on dict (add, edit, and remove)

# add via key-value
elements["Copper"] = 27  # oops, seems wrong

elements
{'Hydrogen': 1, 'Carbon': 6, 'Copper': 27}
# edit
elements["Copper"] = 29  # that's better

elements
{'Hydrogen': 1, 'Carbon': 6, 'Copper': 29}
# add multiple key value pairs from another dict
elements.update({"Nitrogen": 7, "Oxygen": 8})

elements
{'Hydrogen': 1, 'Carbon': 6, 'Copper': 29, 'Nitrogen': 7, 'Oxygen': 8}
# remove by key
v = elements.pop("Nitrogen")  # return the value, here 7

print("return value:", v)
print("current dict:", elements)
return value: 7
current dict: {'Hydrogen': 1, 'Carbon': 6, 'Copper': 29, 'Oxygen': 8}

Dictionary keys should be immutable

  • Immutable objects: in simple words, an immutable object cannot be changed after it is created. Pyhton built-in data types such as int, float, bool, string, and tuple are immutable.
  • Mutable objects: a mutable object can be changed after it is created, e.g. list and dict.
elem = "Hydrogen"
elem[2]
'd'
# elem[2] = "D"  # this gives error; cannot edit a string
# molecule = {["Hydrogen", "H"]: 1}  # this gives error; mutable list cannot be dict key
molecule = {("Hydrogen", "H"): 1}  # a tuple of string can be used as dict key

Loop over dictionary

simple loop over keys

elements = {"Hydrogen": 1, "Carbon": 6, "Oxygen": 8}

for k in elements:
    v = elements[k]
    print("key =", k)
    print("value =", v)
key = Hydrogen
value = 1
key = Carbon
value = 6
key = Oxygen
value = 8

loop over key value pairs together

for k, v in elements.items():
    print("key =", k)
    print("value=", v)
key = Hydrogen
value= 1
key = Carbon
value= 6
key = Oxygen
value= 8

More ways to create dictionary

names = ["Hydrogen", "Carbon", "Oxygen"]
numbers = [1, 6, 8]

dict comprehension

elements = {i: s for i, s in zip(numbers, names)}
elements
{1: 'Hydrogen', 6: 'Carbon', 8: 'Oxygen'}

dict function

elements = dict(zip(numbers, names))
elements
{1: 'Hydrogen', 6: 'Carbon', 8: 'Oxygen'}

Lesson 7: Function


Break down programs into functions

  • Readability: human beings can only keep a few items in working memory at a time. Encapsulate complexity so that we can treat it as a single "thing".
  • Reusability: write one time, use many times.
  • Testing: components with well-defined boundaries are easier to test.
  • ...

Define a function using def with a name, arguments, and a block of code

  • Function name must obey the same rules as variable names (i.e. combination of alphanumerics and the underschore "_")
  • Put arguments in parentheses
  • Then a colon, and finally an indented code block
# Empty parentheses if the function doesn't take any arguments


def say_hi():
    print("Hi!")


say_hi()
Hi!

def say_hi_2(name):
    print("Hi", name, "!")


say_hi_2("Berkeley")
Hi Berkeley !

A Function can return a result to its caller using return

import math


def get_vector_magnitude(x, y):
    mag = math.sqrt(x ** 2 + y ** 2)

    return mag
m = get_vector_magnitude(3, 4)

print("magnitude of vector:", m)
magnitude of vector: 5.0

# A function without explicit `return` returns `None`

print("return value of `say_hi()` is:", say_hi())
Hi!
return value of `say_hi()` is: None

Can specify default values for arguments

  • All arguments with defaults must come after all arguments without (otherwise, argument-to-parameter matching would be ambigious)
  • Makes common cases simpler, and signals intent
  • Functional can return multiple values
def scale_vector_by_factor(x, y, factor=1):
    x *= factor
    y *= factor

    return x, y
print("scale_vector_by_factor with default:", scale_vector_by_factor(3, 4))
scale_vector_by_factor with default: (3, 4)

print("scale_vector_by_factor:", scale_vector_by_factor(3, 4, 2))
scale_vector_by_factor: (6, 8)

scaled_x, scaled_y = scale_vector_by_factor(3, 4, 2)

print("scaled x:", scaled_x, "scaled_y:", scaled_y)
scaled x: 6 scaled_y: 8

Can pass arguments by name

  • Helpful when functions have lots of options

    If you have a procedure with ten parameters, you probably missed some.
    -- from "Epigrams in Programming", by Alan J. Perlis

print("scale_vector_by_factor:", scale_vector_by_factor(x=3, y=4, factor=2))
scale_vector_by_factor: (6, 8)

Lesson 8: Class


Classes provide a means of bundling data and functionality on the data together.

Define a class using the keyword class with a name

  • Name in camel case by convension
  • Optionally parentheses (put in super classes the current class inherits from)
  • Then a colon, and finally an indented code block

The __init__() magic method

Oftentimes, we want our class to be general and can handle arbitrary input data. This can be achieved by the __init__() magic method.

  • Constructor to assign values to the data attributes of the instance of the class at initialization.
  • self represents the instance of the class. Using self, we can access the attributes and methods of the class.
class TwoDimVector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
v = TwoDimVector(6, 8)
print(v.x)
print(v.y)
6
8

User-defined class methods

  • get access to and modify class attributes
  • perform calculations
import math


class TwoDimVector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def get_magnitude(self):
        mag = math.sqrt(self.x ** 2 + self.y ** 2)

        return mag

    def scale_by_factor(self, factor=1.0):
        self.x *= factor
        self.y *= factor
v = TwoDimVector(3, 4)
print("vector magnitude", v.get_magnitude())

v.scale_by_factor(2)
print("vector magnitude after scale", v.get_magnitude())
vector magnitude 5.0
vector magnitude after scale 10.0

The property decorator

  • A decorator feature in Python wraps in a function, appends several functionalities to existing code and then returns it
  • Property decorator makes the use of getter and setters easier
import math


class TwoDimVector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def scale_by_factor(self, factor=1.0):
        self.x *= factor
        self.y *= factor

    @property
    def magnitude(self):
        m = math.sqrt(self.x ** 2 + self.y ** 2)
        return m
v = TwoDimVector(3, 4)

v.magnitude
5.0

Type annotation

Type hints for function and variables greatly improves code readability. To set type hints for an argument, add colon ":" and the expected type after the name of the argument.

  • The Python runtime does not enforce function and variable type annotations
  • But they can be used by third party tools such as type checkers, IDEs, linters, etc.
  • The typing module provides more types, like List and Dict
import math


class TwoDimVector:
    """
    A two-dimensional vector class.

    Args:
        x: first component of a two-dimensional vector.
        y: second component of a two-dimensional vedtor.
    """

    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    def scale_by_factor(self, factor: float = 1.0):
        self.x *= factor
        self.y *= factor

    @property
    def magnitude(self) -> float:
        m = math.sqrt(self.x ** 2 + self.y ** 2)
        return m