Python Basics 2

CIS1902 Python Programming

Reminders

  • If you're on the waitlist still, see me after class
  • Please join Canvas and Ed
  • Office Hours have been schduled for 12:00-1:30pm in Levine 601
  • First homework will be released tomorrow!

Agenda

  1. Lists
  2. Tuples
  3. Sets
  4. Strings
  5. Dictionaries
  6. Classes & Objects

Lists

# two ways to instantiate lists
l = list()
l = list([1,2,3,4,5])  # this is actually a cast!
l = []
l = [1,2,3,4,5]

# retrieving elements is easy
l[0]  # 1
l[-1]  # 5

# we can slice a list
l[1:3]  # [2,3,4]
l[1:]  # [2,3,4,5]
l[:3]  # [1,2,3]

len(l)  # length of object

# a neat trick to instantiate a list
l = [0] * 5  # [0,0,0,0,0]
l = [1,2,3]
# adds an element to end of list
l.append(5)  # [1,2,3,5]
# adds element at the specified index
l.insert(3, 4)  # [1,2,3,4,5]
l.append("6")  # lists can contain multiple types!

elem = l.pop()  # remove and return last element
elem = l.pop(2)  # remove and return element at index

# remove first occurrence of given element
l.remove("thing_to_remove")

# we can concat two lists easily
l + [6,7,8]
l.extend([6,7,8])
l.extend((6,7,8))  # extend takes any iterable

Lists

students = ["David", "Ben", "Justin"]

# iteration is done using "for _ in _:"
for s in students:
  print(s)

# you can still do this if you want
for i in range(0, len(s)):
  s = students[i]
  print(s)

# but this is more pythonic
for i, s in enumerate(students):
  print(i, s)
numbers = [7, 4, 12, 20, 16, 1]

# sorted() returns a sorted copy of the original list
sorted(numbers)[0]  # 1
numbers[0]  # still 7

# we can sort in-place as well
numbers.sort()
numbers[0]  # 1

# sorted descending order is easy
sorted(numbers, reverse=True)
numbers.sort(reverse=True)

Tuples

model = "civic"
make = "honda"

car = (model, make)  # note regular parens
car = model, make  # this also works

# tuple deconstruction is very nice
year, model, make = 2020, "civic", "honda"

# tuple indexing is the same as lists
car[0]
car[0:2]

# neat trick, we can swap variables easily
x, y = 1, 2
x, y = y, x

# same trick as lists works too
t = (0) * 3
  • Tuples are immutable
    • Attempting to set car[0] = "accord" would error
    • Gotcha: tuples store the memory locations of its objects. Thus, if the underlying objects are mutated, then the objects the tuple references are mutated.
  • We can iterate similarly to lists

Sets

# instantiate a set in two ways
students = set()
students = {"David", "Ben", "Justin"}

# adding and removing elements
students.add("Chris") 
students.remove("David")

# checking existence
david_exists = "David" in students
if "David" not in students:
  print("David is not a student")
naturals = {0,1,2,3,4,5,6,7}
primes = {2,3,5,7}

# intersection
naturals.intersection(primes)
naturals & primes

# union
naturals.union(primes)
naturals | primes

# difference
naturals.difference(primes)
naturals - primes

Strings

Strings can be thought of as a data structure themselves

s = "my very long string"

# indexing is the same as lists/tuples
s[0]  # "m"
s[:7]  # "my very"

# a python trick
s[::2]  # "m eyln tig", every 2nd element
# every -1 element, i.e. reversed
s[::-1]  # "gnirts gnol yrev ym"
# checking substrings
if "long" in s:
  print("string contains 'long'")

Iteration

  • Lists, sets, tuples, and strings can all be iterated similarly as we saw because they are iterable objects.
  • We'll revisit this next week, but these objects have implemented specific magic methods __iter__() and __next__()
  • Python specifically uses the notion of Duck Typing to determine if an object is some type.
    • Instead of doing type checks, we simply check if certain methods or attributes exist
    • "If it walks like a duck and quacks like a duck, it must be a duck" 🦆

Copying

  • If you're not familiar or comfortable with pointers and references, dealing with objects and their locations in memory can get confusing
    • If we have objects in l, then copy may not work as expected. Instead, we can use deepcopy, which copies nested objects recursively.
l = [1,2,3]
l2 = l

l[0] = 100  # both l and l2 are mutated!
print(l, l2)  # [100,2,3] [100,2,3]

from copy import copy, deepcopy

l = [1,2,[3,4]]
l2 = copy(l)

l[2][0] = 100
print(l, l2)  # [1,2,[100,4]] [1,2,[100,4]]

l = [1,2,[3,4]]
l2 = deepcopy(l)
l[2][0] = 100
print(l, l2)  # [1,2,[100,4]] [1,2,[3,4]]

Dictionaries

  • Python's hash table data structure, i.e. key-value store
  • Dictionaries are mutable objects that take a hashable object as a key and any arbitrary object as the value
    • Lists and sets are not hashable, but tuples can be if their elements are
# instatiation
attendance = dict()
attendance = {"David": 5, "Ben": 4}

# setting and getting
attendance["Justin"] = 5

# removing element
del attendance["Ben"]

Dictionaries

  • If we try to index into a dictionary with a key that doesn't exist we get an error
  • Sometimes this is a bit annoying, so we use the defaultdict package
    • We will cover imports and packages later, but defaultdict comes bundled with Python already!
if "Sara" not in attendance:  # not fun :(
  attendance["Sara"] = 0
attendance["Sara"] += 1

from collections import defaultdict
words = [
  "some", "words", "to", "be", 
  "counted", "some", "more", "words",
]
count = defaultdict()

for w in words:  # much nicer!
  count[w] += 1

# defaultdict uses 0 by default, but we can specify the type
defaultdict(str)

Classes & Objects

  • All variables of a class are public and can be defined at runtime
    • In fact, private variables do not exist in Python!
  • However, we can "enforce" that certain attributes are set through the constructor
    • Note the double underscore for __init__(). This means it is a reserved function in Python, which we will cover later.
# define using "class" reserved word
class Car:
  pass

# to instantiate object
car = Car()

# note that we don't even need setter/getter methods!
car.model = "civic"
car.make = "honda"

# we can explicitly require these in the constructor
class Car:
  def __init__(self, model, make):
    # note the similarity of `self` and `this` in Java
    self.model = model
    self.make = make

Classes & Objects

  • Inheritance is fairly simple as well, just add the parent class in parentheses
    • If you want to inherit multiple classes, just add commas, e.g. Student(Person, OtherClass)
  • Note that name is inherited from Person
  • Additionally, note that __init__ is overridden in Student
class Person:
  def __init__(self, name):
    self.name = name

  def say_hello(self):
    print(f"Hi my name is {self.name}!")

class Student(Person):
  def __init__(self, name, grade):
    Person.__init__(self, name)
    self.grade = grade

  def study(self):
    self.grade = min(100, self.grade + 10)
    print(f"{self.name} studied and " + 
          f"their grade is now {self.grade}!")

s = Student("David", 50)
s.say_hello()
s.study()