Classes#
objects
class
attributes
methods
instances
__init__
Objects#
Class Question #1#
Given what we’ve discussed in this course so far, if you wanted to store information about a date, how would you do so?
A) string
B) dictionary
C) list
D) integers stored in separate variables
Storing Dates (Motivation)#
# A date, stored as a string
date_string = '29/09/1988'
print(date_string)
# A date, stored as a list of numbers
date_list = ['29', '09', '1988']
date_list
# A date, stored as a series of numbers
day = 29
month = 9
year = 1988
print(day)
# A date, stored as a dictionary
date_dictionary = {'day': 29, 'month': 9, 'year': 1988}
date_dictionary
Objects are ways to organize data (variables) and functions together.
Example Object: Date#
# Import a date object
from datetime import date
date?
# Set the data we want to store in our date object
my_day = 29
my_month = 9
my_year = 1988
# Create a date object
my_date = date(my_year, my_month, my_day)
print(my_date)
# Check what type of thing `my_date` is
type(my_date)
Accessing Attributes & Methods#
.
, followed by the attribute/method name in the object.
Date - Attributes#
Attributes look up & return information about the object.
attributes maintain the object’s state, simply returning information about the object to you
# Get the day attribute
my_date.day
# Get the month attribute
my_date.month
# Get the year attribute
my_date.year
Date - Methods#
Reminder: these are functions that belong to and operate on the date object directly.
methods modify the object’s state
# Method to return what day of the week the date is
my_date.weekday()
# Reminder: check documentation with '?'
date.weekday?
It’s also possible to carry out operations on multiple date objects.
# define a second date
my_date2 = date(1980, 7, 29)
print(my_date, my_date2)
# calculate the difference between times
time_diff = my_date - my_date2
print(time_diff.days, "days") #in days
print(time_diff.days/365,"years") #in years
Listing Attributes & Methods : dir
#
# tab complete to access
# methods and attributes
my_date.
# works to find attributes and methods
# for date type objects generally
date.
## dir ouputs all methods and attributes
## we'll talk about the double underscores next lecture
dir(my_date)
Class Question #2#
Given the code below:
my_date = date(year=1050, month=12, day=12)
Which is the best description:
A)
my_date
is an object, with methods that store data, and attributes that store proceduresB)
my_date
is variable, and can be used with functionsC)
my_date
is an attribute, with methods attached to itD)
my_date
is a method, and also has attributesE)
my_date
is an object, with attributes that store data, and methods that store procedures
Class Question #3#
For an object lets
with a method do_something
, how would you execute that method?
A)
do_something(lets)
B)
lets.do_something
C)
lets.do_something()
D)
lets.do.something()
E) ¯\_(ツ)_/¯
Class Question #4#
For an object lets
with an attribute name
, how would you return the information stored in name
for the object lets
?
A)
name(lets)
B)
lets.name
C)
lets.name()
D) lets.get.name()
E) ¯\_(ツ)_/¯
Objects Summary#
Objects allow for data (attributes) and functions (methods) to be organized together
methods operate on the object type (modify state)
attributes store and return information (data) about the object (maintain state)
dir()
returns methods & attributes for an objectSyntax:
obj.method()
obj.attribute
date
anddatetime
are two types of objects in Python
Classes#
class
keyword opens a code block for instructions on how to create objects of a particular type.
Think of classes as the blueprint for creating and defining objects and their properties (methods, attributes, etc.). They keep related things together and organized.
Example Class: Dog#
# Define a class with `class`.
# By convention, class definitions use CapWords (Pascal)
class Dog():
# Class attributes for objects of type Dog
sound = 'Woof'
# Class methods for objects of type Dog
def speak(self, n_times=2):
return self.sound * n_times
A reminder:
attributes maintain the object’s state; they lookup information about an object
methods alter the object’s state; they run a function on an object
class
notes:
classes tend to use CapWords convention (Pascal Case)
instead of snake_case (functions and variable names)
()
afterDog
indicate that this is callablelike functions, Classes must be executed before they take effect
can define attributes & methods within
class
self
is a special parameter for use by an objectrefers to the thing (object) itself
like functions, a new namespace is created within a Class
# Initialize a dog object
george = Dog()
# the object george has 'sound' attribute(s) from Dog()
george.sound
# the pbject george has 'Dog' method(s)
# remember we used `self`
george.speak()
Class Question #5#
Which of the following statements is true about the example we’ve been using?
class Dog():
sound = 'Woof'
def speak(self, n_times=2):
return self.sound * n_times
A)
Dog
is a Class,sound
is an attribute, andspeak
is a method.B)
Dog
is a function,sound
is an attribute, andspeak
is a method.C)
Dog
is a Class,sound
is a method, andspeak
is an attribute.D)
Dog
is a function,sound
is an method, andspeak
is an attribute.
Using our Dog Objects#
# Initialize a group of dogs
pack_of_dogs = [Dog(), Dog(), Dog(), Dog()]
# take a look at this
pack_of_dogs
# take a look at this
type(pack_of_dogs[0])
for dog in pack_of_dogs:
print(dog.speak(6))
Instances & self#
self
refers to the current instance.
# Initialize a dog object
george = Dog()
From our example above:
Dog is the Class we created
george
was an instance of that classself just refers to whatever the current instance is
Class Question #6#
How many instances of Dog()
are created below and how many times does the speak()
method execute?
pack_of_dogs = [Dog(), Dog(), Dog(), Dog()]
counter = 1
for doggie in pack_of_dogs:
if counter <= 2:
print(doggie.speak())
counter += 1
else:
break
A) 2 instances, 2 method executions
B) 2 instances, 4 method executions
C) 4 instances, 2 method executions
D) 4 instances, 4 method executions
E) ¯\_(ツ)_/¯
Instance Attributes#
An instance attribute is specific to the instance we’re on. This allows different instances of the same class to be unique (have different values stored in attributes and use those in methods).
# Initialize a group of dogs
pack_of_dogs = [Dog(), Dog(), Dog(), Dog()]
print(pack_of_dogs[0].sound)
print(pack_of_dogs[1].sound)
pack_of_dogs[1].sound = 'meow'
print(pack_of_dogs[0].sound)
print(pack_of_dogs[1].sound)
This creates four different Dog
type objects and stores them in a list. But, up until now…every Dog
was pretty much the same.
__init__
is a special method used to define instance attributes.
Example Class: Dog Revisited#
Two trailing underscores (a
dunder
, or double underscore) is used to indicate something Python recognizes and knows what to do every time it sees it.Here, we use
__init__
to execute the code within it every time you initialize an object.
class Dog():
# Class attributes for Dogs, since they are defined outside the __init__
sound = 'Woof'
bad_foods = ['chocolate', 'onions']
# Initializer, allows us to specify instance-specific attributes
# leading and trailing double underscores indicates that this is special to Python
def __init__(self, name='Fido'):
self.name = name
#instance attribute for Dogs, since it is defined in the __init__ method
self.favorite_foods = ['steak']
def speak(self, n_times=2):
return self.sound * n_times
# Initialize a dog
# what goes in the parentheses is defined in the __init__
gary = Dog(name='Gary')
another_dog = Dog()
# Check gary's attributes
print(gary.sound) # This is an class attribute
print(gary.name) # This is a instance attribute
# Check gary's methods
gary.speak()
Class attributes vs. Instance attributes#
In our Dog class, sound
and bad_foods
are class attributes because they are defined outside of the __init__
method, and name
and favorite_foods
are instance attributes because they are defined within the __init__
method. A change to a class attribute is reflected across all instances of that class, and a change to an instancec attribute is only reflected in that instance. Note that in our example class, sound
is a class attribute, but it is a string, which is immutable, and therefore any “change” to it is in reality destroying the existing class attribute “sound” and replacing it with an instance attribute of the same name.
# let's create a couple of new Dogs:
sparky = Dog('Sparky')
spot = Dog('Spot')
# we can see that they have the same values in the class variables:
print("Sparky's sound is: ", sparky.sound)
print("Sparky's bad foods are:", sparky.bad_foods)
print("Spot's sound is: ", spot.sound)
print("Spot's bad foods are:", spot.bad_foods)
# now we change Spot's bad food list:
spot.bad_foods.append('avocados')
# and we can see that this change is reflected in both instances, since it was a change to a class attribute:
print("Sparky's bad foods are:", sparky.bad_foods)
print("Spot's bad foods are:", spot.bad_foods)
# now let's look at the instance attributes:
print("Sparky's name is: ", sparky.name)
print("Sparky's favorite foods are:", sparky.favorite_foods)
print("Spot's name is: ", spot.name)
print("Spot's favorite foods are:", spot.favorite_foods)
# when we change an instance attribute, that change is not reflected in other instances:
spot.favorite_foods.append('turkey')
# we can see that this change is reflected in Spot, but not Sparky:
print("Sparky's favorite foods are:", sparky.favorite_foods)
print("Spot's favorite foods are:", spot.favorite_foods)
Class Question #7#
Edit the code we’ve been using for the Class Dog
to include information about the breed of the Class Dog in NewDog
?
# EDIT CODE HERE
class NewDog():
sound = 'Woof'
def __init__(self, name):
self.name = name
def speak(self, n_times=2):
return self.sound * n_times
polly = NewDog('Polly', 'Corgi')
print(polly.name)
print(polly.breed)
polly.breed = 'Dalmatian'
print(polly.breed)
A) I did it!
B) I think I did it!
C) So lost. -_-
Class example: Cat#
# Define a class 'Cat'
class Cat():
sound = "Meow"
def __init__(self, name):
self.name = name
def speak(self, n_times=2):
return self.sound * n_times
Instances Examples#
# Define some instances of our objects
pets = [Cat('Jaspurr'), Dog('Barkley'),
Cat('Picatso'), Dog('Ruffius')]
for pet in pets:
print(pet.name, ' says:')
print(pet.speak())
Class Question #8#
What will the following code snippet print out?
class MyClass():
def __init__(self, name, email, score):
self.name = name
self.email = email
self.score = score
def check_score(self):
if self.score <= 65:
return self.email
else:
return None
student = MyClass('Rob', 'rob@python.com', 62)
student.check_score()
A) True
B) ‘Rob’
C) False
D) ‘rob@python.com’
E) None
Code Style: Classes#
CapWords for class names
one blank line between methods/functions
Good Code Style
class MyClass():
def __init__(self, name, email, score):
self.name = name
self.email = email
self.score = score
def check_score(self):
if self.score <= 65:
return self.email
else:
return None
Code Style to Avoid
class my_class(): # uses snake case for name
def __init__(self, name, email, score):
self.name = name
self.email = email
self.score = score # no blank lines between methods
def check_score(self):
if self.score <= 65:
return self.email
else:
return None
Example: ProfCourses()
#
Let’s put a lot of these concepts together in a more complicated example…
What if we wanted some object type that would allow us to keep track of the professor’s courses? Well…we’d want this to work for any Professor, so we’ll call it ProfCourses
.
We would likely want an object type and then helpful methods that allow us to add a class to the course inventory and to compare between courses.
class ProfCourses():
# create three instance attributes
def __init__(self, prof):
self.n_courses = 0
self.courses = []
self.prof = prof
morgan_courses = ProfCourses('Morgan')
print(morgan_courses.n_courses)
print(morgan_courses.prof)
add_course()
method
class ProfCourses():
def __init__(self, prof):
self.n_courses = 0
self.courses = []
self.prof = prof
# add method that will add courses as a dictionary
# to our attribute (courses)...which is a list
def add_course(self, course_name, quarter, n_students):
self.courses.append({'course_name': course_name,
'quarter' : quarter,
'n_students': n_students})
# increase value store in n_courses
# by 1 any time a class is added
self.n_courses += 1
# create morgan_courses
morgan_courses = ProfCourses('Morgan')
# add a class
morgan_courses.add_course('COGS18', 's123', 80)
# see output
print(morgan_courses.courses)
morgan_courses.n_courses
compare()
method
class ProfCourses():
def __init__(self, prof):
self.n_courses = 0
self.courses = []
self.prof = prof
def add_course(self, course_name, quarter, n_students):
self.courses.append({'course_name': course_name,
'quarter' : quarter,
'n_students': n_students})
self.n_courses += 1
# add method to compare values in courses
def compare(self, attribute, direction='most'):
fewest = self.courses[0]
most = self.courses[0]
for my_course in self.courses:
if my_course[attribute] <= fewest[attribute]:
fewest = my_course
elif my_course[attribute] >= most[attribute]:
most = my_course
if direction == 'most':
output = most
elif direction == 'fewest':
output = fewest
return output
# create morgan_courses
morgan_courses = ProfCourses('Morgan')
# add a bunch of classes
morgan_courses.add_course('COGS18', 's123', 80)
morgan_courses.add_course('COGS108', 's223', 80)
morgan_courses.add_course('COGS101a', 'sp23', 160)
morgan_courses.add_course('COGS18', 'fa23', 300)
# see the courses
print(morgan_courses.n_courses)
morgan_courses.courses
# make comparison among all courses
# returns the class with the most students
morgan_courses.compare('n_students')
# return the class with the fewest students
morgan_courses.compare('n_students', 'fewest')
extending the functionality of the compare()
method by adding more attributes
class ProfCourses():
def __init__(self, prof):
self.n_courses = 0
self.courses = []
self.prof = prof
def add_course(self, course_name, quarter,
n_students, n_exams, n_assignments):
# add in additional key-value pairs
self.courses.append({'course_name': course_name,
'quarter' : quarter,
'n_students': n_students,
'n_exams' : n_exams,
'n_assignments' : n_assignments})
self.n_courses += 1
def compare(self, attribute, direction='most'):
fewest = self.courses[0]
most = self.courses[0]
for my_course in self.courses:
if my_course[attribute] <= fewest[attribute]:
fewest = my_course
elif my_course[attribute] >= most[attribute]:
most = my_course
if direction == 'most':
output = most
elif direction == 'fewest':
output = fewest
return output
# create morgan_courses
morgan_courses = ProfCourses('Morgan')
# add a bunch of classes
morgan_courses.add_course('COGS18', 's123', 80, 2, 5)
morgan_courses.add_course('COGS108', 's223', 80, 2, 5)
morgan_courses.add_course('COGS101a', 'sp23', 160, 3, 0)
morgan_courses.add_course('COGS18', 'fa23', 300, 3, 5)
# see the courses
print(morgan_courses.n_courses)
# return the class with the most exams
morgan_courses.compare('n_exams', 'most')
# return the class with the fewest assignments
morgan_courses.compare('n_assignments', 'fewest')
Improving & updating this code
account for ties in
compare()
edit code in
compare()
to make thefor
loop and following conditional more intuitiveadd a method to put course list in time order
etc.
Classes Review#
class
creates a new class typenames tend to use CapWords case
can have attributes (including instance attributes) and methods
obj.attribute
accesses data stored in attributeobj.method()
carries out code defined within method
instance attributes are defined with
__init__
__init__
is a reserved method in PythonThis “binds the attributes with the given arguments”
self
refers to current instance
to create an object (instance) of a specified class type (
ClassType
):object_name = ClassType(input1, input2)
self
is not given an input when creating an object of a specified class
Everything in Python is an Object!#
Data variables are objects#
print(isinstance(True, object))
print(isinstance(1, object))
print(isinstance('word', object))
print(isinstance(None, object))
a = 3
print(isinstance(a, object))
Functions are objects#
print(isinstance(sum, object))
print(isinstance(max, object))
# Custom function are also objects
def my_function():
print('yay Python!')
isinstance(my_function, object)
Class definitions & instances are objects#
class MyClass():
def __init__(self):
self.data = 13
my_instance = MyClass()
print(isinstance(MyClass, object))
print(isinstance(my_instance, object))