CIS-1902 Python Programming

Instructor: David Cao (davidcao@seas)
TAs: Chris Liu (liuchris@seas)
Pragya Singh (pragya7@wharton)

Agenda

  1. Administrative Things
  2. Course Overview
  3. Python Introduction
  4. Python Basics
  5. Python Setup

Admin Stuff

  • Course Website is where all course information is
  • Canvas for HW submission
  • EdStem for announcements, questions, etc
  • Waitlist

Who is this for?

  • This is not just a programming class!
  • Meant to be an accelerated introduction to using Python "in the real world"
  • If you have extensive experience with Python already, you probably won't get much out of this course
  • On the other side, if you have little programming experience, this course will be challenging

Helpful Skills

  • Comfortable reading code & documentation
  • Familiarity with Unix
  • Some background in probability
  • Some background in the structure of the internet (servers, APIs, databases)

Syllabus

Why Python?

  • Simplicity and readability ✨
  • Vast developer and library support that covers virtually every aspect of computer science
  • Not that performant natively, but can be boosted easily by calling underlying functions or libraries that are written in performant languages, e.g. C
    • This is why sometimes Python is considered a scripting language
  1. # Isn't Python great?
  2. print("Hello, World!")
  1. // Java
  2. class HelloWorld {
  3. public static void main(String[] args) {
  4. System.out.println("Hello, World!");
  5. }
  6. }
  1. // Go
  2. package main
  3. import "fmt"
  4. func main() {
  5. fmt.Println("Hello, World!")
  6. }
  1. // C++
  2. #include <stdio.h>
  3. int main() {
  4. printf("Hello, World!");
  5. return 0;
  6. }
  1. // rust
  2. fn main() {
  3. println!("Hello World!");
  4. }

What is Python? 🐍

  • An object-oriented, dynamically typed, interpreted language
  • Developed by Guido van Rossum in 1991 (!)
    • A scripting language that would appeal to Unix/C hackers
    • Named after Monty Python! 🐟 🌲
  • The most popular programming language in the world, only seems to be getting more traction

python dictionary definition

How do I use Python?

There are two ways of running Python. Either we can invoke Python directly within it's interpreter, or we can write files with our Python code.

  • Interpreter is similar to bash, but speaks Python. Typically used for simple and quick tasks.
  • Files are your typical codebase, used for larger projects.

Python Interpreter

  1. ❯ python
  2. Python 3.10.12 (main, Jan 18 2024, 12:41:08) [Clang 15.0.0 (clang-1500.1.0.2.5)] on darwin
  3. Type "help", "copyright", "credits" or "license" for more information.
  4. >>>

Python Interpreter

  1. >>> 5 + 10
  2. 15
  3. >>> 5 ** 2
  4. 25
  5. >>> my_var = "Hello, World!"
  6. >>> print(my_var)
  7. Hello, World!
  8. >>>

Python as Scripts

If you've written your Python in a file, you can simply run it like so

  1. ❯ echo "print('Hello, World!')" > my_script.py
  2. ❯ python my_script.py
  3. Hello, World!

Python is Imperative

  1. my_object = MyClass()
  2. my_object.do_something()

but also has functional elements

  1. def my_func(a, b, c):
  2. ...
  3. # functions themselves are objects!
  4. print(my_func)
  5. # prints out: <function my_func at 0x102aad990>

Python Basics

Syntax

  • No curly brackets???
  • Python uses whitespace to determine scope, so readibility is built in
    • Python doesn't care if you use 2-space tabs, 4-space tabs, or tab characters as long as you stay consistent
    • Caution: make sure your editor converts tabs to spaces or spaces to tabs, otherwise you might get some strange errors!
  1. # my incredibly insightful comment
  2. """
  3. a really long and more insightful
  4. comment that needs multiple lines
  5. """
  6. print("Hello, World!")
  1. // Java
  2. class HelloWorld {
  3. public static void main(String[] args) {
  4. System.out.println("Hello, World!");
  5. }
  6. }

Variables & Types

  • Variables are implicitly typed
    • Type is inferred at runtime, i.e. dynamically typed
  • Variables are inherently mutable, constants do not exist!
    • We can redefine a variable to a different type without issues, but this isn't very clean code
  • Casting is easy and typically implicit
  1. >>> my_var = 10 # no type declaration needed!
  2. >>> type(my_var)
  3. int
  4. >>> my_var = "hello!"
  5. >>> type(my_var)
  6. str
  7. >>> my_var = 10
  8. >>> str(my_var)
  9. "10"
  10. >>> print(my_var) # print casts everything to a str implicitly
  11. >>> my_var = "10"
  12. >>> int(my_var)
  13. 10
  14. >>> my_var = None # None is Python's null
  15. >>> type(my_var)
  16. NoneType
  17. >>> type(type(my_var)) # every type is type type :)
  18. type

Control Flow

  1. # note the : token to indicate a scope
  2. if x > 15:
  3. print("x is larger than 15")
  4. elif x < 15:
  5. print("x is less than 15")
  6. else:
  7. print("x is exactly 15")
  8. # this could also be written as
  9. if x > 15: print("x is larger than 15")
  10. elif x < 15: print("x is less than 15")
  11. else: print("x is exactly 15")
  12. # but I don't recommend it as it looks messy
  13. # however, these are useful as one-line control statements
  14. y = "gt" if x > 15 else "lte"

Control Flow

  1. i = 0
  2. # again note the : token and indentation
  3. while i < 10:
  4. print(i)
  5. i += 1
  6. # there is no do-while in Python, instead
  7. while True:
  8. do_something()
  9. if condition:
  10. break # exit loop
  1. for i in range(0,10):
  2. print(i)
  3. for i in range(0,10):
  4. if i % 2 == 0:
  5. # skip below code and continue loop
  6. continue
  7. print(i)

Funtions

  1. # functions are defined using the "def" keyword
  2. # once again, we see our good friend :
  3. def my_func(x):
  4. return x + 5
  5. def my_func(x):
  6. # you can stub out functions with the pass keyword
  7. # my_var = my_func(x)
  8. # will result in my_var = None
  9. pass

We will revisit functions later to see the power of Python's functional attributes!

Data Structures & More

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

Lists

  1. # two ways to instantiate lists
  2. l = list()
  3. l = list([1,2,3,4,5]) # this is actually a cast!
  4. l = []
  5. l = [1,2,3,4,5]
  6. # retrieving elements is easy
  7. l[0] # 1
  8. l[-1] # 5
  9. # we can slice a list
  10. l[1:3] # [2,3,4]
  11. l[1:] # [2,3,4,5]
  12. l[:3] # [1,2,3]
  13. len(l) # length of object
  14. # a neat trick to instantiate a list
  15. l = [0] * 5 # [0,0,0,0,0]
  1. l = [1,2,3]
  2. # adds an element to end of list
  3. l.append(5) # [1,2,3,5]
  4. # adds element at the specified index
  5. l.insert(3, 4) # [1,2,3,4,5]
  6. l.append("6") # lists can contain multiple types!
  7. elem = l.pop() # remove and return last element
  8. elem = l.pop(2) # remove and return element at index
  9. # remove first occurrence of given element
  10. l.remove("thing_to_remove")
  11. # we can concat two lists easily
  12. l + [6,7,8]
  13. l.extend([6,7,8])
  14. l.extend((6,7,8)) # extend takes any iterable

Lists

  1. students = ["David", "Ben", "Justin"]
  2. # iteration is done using "for _ in _:"
  3. for s in students:
  4. print(s)
  5. # you can still do this if you want
  6. for i in range(0, len(s)):
  7. s = students[i]
  8. print(s)
  9. # but this is more pythonic
  10. for i, s in enumerate(students):
  11. print(i, s)
  1. numbers = [7, 4, 12, 20, 16, 1]
  2. # sorted() returns a sorted copy of the original list
  3. sorted(numbers)[0] # 1
  4. numbers[0] # still 7
  5. # we can sort in-place as well
  6. numbers.sort()
  7. numbers[0] # 1
  8. # sorted descending order is easy
  9. sorted(numbers, reverse=True)
  10. numbers.sort(reverse=True)

Tuples

  1. model = "civic"
  2. make = "honda"
  3. car = (model, make) # note regular parens
  4. car = model, make # this also works
  5. # tuple deconstruction is very nice
  6. year, model, make = 2020, "civic", "honda"
  7. # tuple indexing is the same as lists
  8. car[0]
  9. car[0:2]
  10. # neat trick, we can swap variables easily
  11. x, y = 1, 2
  12. x, y = y, x
  13. # same trick as lists works too
  14. 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

  1. # instantiate a set in two ways
  2. students = set()
  3. students = {"David", "Ben", "Justin"}
  4. # adding and removing elements
  5. students.add("Chris")
  6. students.remove("David")
  7. # checking existence
  8. david_exists = "David" in students
  9. if "David" not in students:
  10. print("David is not a student")
  1. naturals = {0,1,2,3,4,5,6,7}
  2. primes = {2,3,5,7}
  3. # intersection
  4. naturals.intersection(primes)
  5. naturals & primes
  6. # union
  7. naturals.union(primes)
  8. naturals | primes
  9. # difference
  10. naturals.difference(primes)
  11. naturals - primes

Strings

Strings can be thought of as a data structure themselves (a list!)

  1. s = "my very long string"
  2. # indexing/slicing is the same as lists
  3. s[0] # "m"
  4. s[:7] # "my very"
  5. # a python trick
  6. s[::2] # "m eyln tig", every 2nd element
  7. # every -1 element, i.e. reversed
  8. s[::-1] # "gnirts gnol yrev ym"
  1. # checking substrings
  2. if "long" in s:
  3. 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.
  1. l = [1,2,3]
  2. l2 = l
  3. l[0] = 100 # both l and l2 are mutated!
  4. print(l, l2) # [100,2,3] [100,2,3]
  5. from copy import copy, deepcopy
  6. l = [1,2,[3,4]]
  7. l2 = copy(l)
  8. l[2][0] = 100
  9. print(l, l2) # [1,2,[100,4]] [1,2,[100,4]]
  10. l = [1,2,[3,4]]
  11. l2 = deepcopy(l)
  12. l[2][0] = 100
  13. 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
  1. # instatiation
  2. attendance = dict()
  3. attendance = {"David": 5, "Ben": 4}
  4. # setting and getting
  5. attendance["Justin"] = 5
  6. # removing element
  7. 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!
  1. if "Sara" not in attendance: # not fun :(
  2. attendance["Sara"] = 0
  3. attendance["Sara"] += 1
  4. from collections import defaultdict
  5. words = [
  6. "some", "words", "to", "be",
  7. "counted", "some", "more", "words",
  8. ]
  9. count = defaultdict()
  10. for w in words: # much nicer!
  11. count[w] += 1
  12. # defaultdict uses 0 by default, but we can specify the type
  13. 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.
  1. # define using "class" reserved word
  2. class Car:
  3. pass
  4. # to instantiate object
  5. car = Car()
  6. # note that we don't even need setter/getter methods!
  7. car.model = "civic"
  8. car.make = "honda"
  9. # we can explicitly require these in the constructor
  10. class Car:
  11. def __init__(self, model, make):
  12. # note the similarity of `self` and `this` in Java
  13. self.model = model
  14. 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
  1. class Person:
  2. def __init__(self, name):
  3. self.name = name
  4. def say_hello(self):
  5. print(f"Hi my name is {self.name}!")
  6. class Student(Person):
  7. def __init__(self, name, grade):
  8. Person.__init__(self, name)
  9. self.grade = grade
  10. def study(self):
  11. self.grade = min(100, self.grade + 10)
  12. print(f"{self.name} studied and " +
  13. f"their grade is now {self.grade}!")
  14. s = Student("David", 50)
  15. s.say_hello()
  16. s.study()