# 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()

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

'd'

# elem = "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¶

• 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