Week 5 - Abstractions
Table of Contents
Container Types
Remember back in week 1 when I said that there were “other container types that we will get into later”? Well, here they are:
- List type: A mutable container. Lists use square brackets.
# notice that lists can hold all kinds of elements, including other containers >>> ["Hello!", 2, True, [1], (0, 1)] ["Hello!", 2, True, [1], (0, 1)]
- Tuple type: An immutable container. Tuples use round brackets. 1
# same with tuples! >>> ("Hello!", 2, True, [1], (0)) ("Hello!", 2, True, [1], (0))
List Operations
Here are some of the most commonly-used list operations, assuming lst
is a list:
lst[start:end:count]
: Creates a copy of the list from starting positionstart
to ending positionend
, taking everycount
th element.start
defaults to0
,end
defaults tolen(lst)
, andcount
defaults to1
when each is not specified.lst.append(x)
: Appends x to the end of the list.lst.extend(x)
: Extends x, an iterable, to the end oflst
.- This is equivalent to appending each item from x to
lst
. - Or roughly in code:
for item in a: lst.append(item)
- This is equivalent to appending each item from x to
lst.insert(pos, x)
: Insertsx
atpos
position.lst.remove(x)
: Removes first item whose value is equal tox
lst.pop(index)
: Removes item whose index isindex
fromlst
.
For a full list of list operations, check the Python reference.
An Aside on Mutability
You may have noticed that there wasn’t a section on tuple methods. This is because tuples are immutable, resulting in them only having two methods: index
and count
. 2
Immutability is a feature of certain objects in Python where the value of the object cannot be changed after being declared. This means that in the case of tuples, no matter what you do, once the tuple is declared, its values are final and unchangeable.
You might ask, isn’t the following mutating the variable?
>>> a = (1, 2)
>>> a = (2, 3)
>>> a
(2, 3)
It’s not. The tuple itself is not changed; instead, the name a
is rebound to a different value.
Abstractions: An Abstract
Programmers are, by nature, lazy people. We try to simplify things, to the point where we try to abstract away components. Much like how driving a car doesn’t require knowing how the car works under the hood, abstractions allow programmers to write code that uses parts of other code without knowing how it works.
A Motivation for Abstractions
Let’s take the example of a coordinate pair. This can be represented by a two-element tuple, like this: (x, y)
. Suppose we were given these two points, home
and school
, and were told to create a distance
function to compute the distance between any two points. This might be a valid function:
>>> home = (0, 0) # I'm the center of the world
>>> school = (3, 4)
# Let's make a function to compute the distance betweeen two points:
>>> def distance(a, b):
x_distance = a[0] - b[0]
y_distance = a[1] - b[1]
pythagorean_distance = sqrt(x_distance ** 2 + y_distance ** 2)
return pythagorean_distance
>>> distance(home, school)
5.0
So far, this seems great! It looks like we can perform all sorts of computations to these coordinate objects. However, one problem will quickly arise.
Suppose the container type of these coordinates is changed. Instead of a tuple of (x, y)
, we’re now given a tuple of (location_name, x, y)
(making our locations ("home", 0, 0)
and ("school", 3, 4)
). Our implementation of distance is now suddenly broken! This gives a motivation for abstractions
A Functional Introduction to Abstractions
Now, with the power of abstractions, let’s say we’re given this functional interface:
make_location(location_name, x, y) -> location
: A function that takes in a location name, an x coordinate, and a y coordinate, and returns a location object.x_location(location) -> x
: A function that takes in a location object and returns its x location.y_location(location) -> y
: A function that takes in a location object and returns its y location.location_name(location) -> name
: A function that takes in a location object and returns its name.
With this, let’s make the distance
function again:
>>> def distance(a, b):
x_distance = x_location(a) - x_location(b)
y_distance = y_location(a) - y_location(b)
pythagorean_distance = sqrt(x_distance ** 2 + y_distance ** 2)
return pythagorean_distance
And let’s try it on our home
and school
, also defined below:
>>> home = make_location("home", 0, 0)
>>> school = make_location("school", 3, 4)
>>> distance(home, school)
5.0
Note that this abstraction makes it so that the distance function works regardless of the implementation of the abstraction itself !
Two valid implementations of the abstractions could be as follows; notice that although the implementations are different, the distance function still works!:
Implementation 1
### Abstraction ###
>>> def make_location(location_name, x, y):
return (location_name, x, y)
>>> def x_location(location):
return location[1]
>>> def y_location(location):
return location[2]
>>> def location_name(location):
return location[0]
### End Abstraction ###
>>> def distance(a, b):
x_distance = x_location(a) - x_location(b)
y_distance = y_location(a) - y_location(b)
pythagorean_distance = sqrt(x_distance ** 2 + y_distance ** 2)
return pythagorean_distance
>>> home = make_location("home", 0, 0)
>>> school = make_location("school", 3, 4)
>>> distance(home, school)
5.0
Implementation 2
### Abstraction ###
>>> def make_location(location_name, x, y):
return (location_name, (x, y))
>>> def x_location(location):
return location[1][0]
>>> def y_location(location):
return location[1][1]
>>> def location_name(location):
return location[0]
### End Abstraction ###
>>> def distance(a, b):
x_distance = x_location(a) - x_location(b)
y_distance = y_location(a) - y_location(b)
pythagorean_distance = sqrt(x_distance ** 2 + y_distance ** 2)
return pythagorean_distance
>>> home = make_location("home", 0, 0)
>>> school = make_location("school", 3, 4)
>>> distance(home, school)
5.0