Classes#
Author: Mike Wood
Learning Objectives: By the end of this notebook, you should be able to:
Define classes and implement instance variables
Declare methods within a class
Implement special method attributes in custom classes
Implement subclasses that inherit variables and methods from their superclasses
Python classes in this book so far#
In previous notebooks, we have been using classes and objects in our coding, even if we haven’t necessarily thought of them in this way. For example, we have used the list class to generate list objects. Further, we have used list methods on our list objects.
# define a list called my list
my_list = [3,2,1]
# print the type of my list
print(type(my_list))
# call the sort method of the list class
my_list.sort()
# print the sorted list
print(my_list)
<class 'list'>
[1, 2, 3]
As we can see classes are used to generate objects. Then, class methods are used to operate on those objects.
Defining Classes#
In Python we can define our own classes to create our own types of objects. When definining classes, use the class keyword and then add the name of the class:
# define a class called University
# fill in the body with a single line that has the keyword pass
class University:
pass
A class can then be used to generate objects of that class:
# define an object called sjsu with the University class
sjsu = University()
# print sjsu
print(sjsu)
# print the type of sjsu
print(type(sjsu))
<__main__.University object at 0x111ff0250>
<class '__main__.University'>
Once an object is created, you can add attributes to your object on the fly:
# add an attribute called location to sjsu
sjsu.location = 'San Jose, CA'
# add an attribute called name to sjsu
sjsu.name = 'San Jose State University'
Then, the attributes of an object can be accessed from the object
# print the location and system from the sjsu object
print(sjsu.location)
print(sjsu.name)
San Jose, CA
San Jose State University
The __init__ method#
Similar to Constructors in Java, Python classes have an __init__ method which is run automatically when an object is constructed with a class. The __init__ (and all methods of a class) must have their first argument as self, referring to the instance of the object generated by the class.
# modify the definition of the University class to have an init method
# formulate the init method to take in a location and a name
class University:
def __init__(self):
self.location = 'San Jose, CA'
self.name = 'San Jose State University'
The self.* declarations above generate instance variables in the class
# define a new sjsu object from the new University class
sjsu = University()
# print the location and name from the sjsu object
print(sjsu.location)
print(sjsu.name)
San Jose, CA
San Jose State University
Try the code above without the self reference in the init method - what happens?.
We may also want to provide a method by which the user can provide inputs to the class when generating objects. We can accomplish this by adding additional arguments to the init method:
# modify the definition of the University class to have an init method
# formulate the init method to take in a location and a name
class University:
def __init__(self, location, name):
self.location = location
self.name = name
Test the init method using a different university:
# define a new ucsc object from the new University class
# provide a location and name argument to the class
ucsc = University('Santa Cruz','UC Santa Cruz')
# print the location and name from the sjsu object
print(ucsc.location)
print(ucsc.name)
Santa Cruz
UC Santa Cruz
🤔 Mini-Exercise#
Goal: Create a class called Computer that takes in two arguments for the type (e.g. desktop, laptop, tablet) and year_created.
💡 Solution#
Show code cell content
# create your Computer class here
class Computer:
def __init__(self, machine_type, year):
self.type = machine_type
self.year_created = year
# create an object that describes your personal computer
my_computer = Computer('Laptop',2021)
# print the type and year_created instance variables to
# ensure they were created as expected
print(my_computer.type)
print(my_computer.year_created)
Laptop
2021
Methods#
When you’ve created an object with a class, next you’d like to build in methods for your class.
# modify the definition of the University class to have a new
# method to compute the duration of a course
class University:
def __init__(self, location, name):
self.location = location
self.name = name
def course_duration(self):
if 'State University' in self.name:
return('16 weeks')
elif 'UC' in self.name:
return('10 weeks')
else:
return('University system not recognized')
To call methods on an object, reference the object and the method name:
# re-define the University objects above
sjsu = University('San Jose, CA', 'San Jose State University')
ucsc = University('Santa Cruz, CA', 'UC Santa Cruz')
# call and print the course_duration method on the sjsu and ucsc objects
print(sjsu.course_duration())
print(ucsc.course_duration())
16 weeks
10 weeks
Note that in the above code block, we don’t reference the self argument even though the course_duration method was defined with this in mind. If you’d like to have a method with an argument, you can provide it after the self argument:
# modify the definition of the University class to have a new
# method enroll to set a population instance variable
class University:
def __init__(self, location, name):
self.location = location
self.name = name
self.state = 'California'
def enroll(self,students):
self.population = students
Then, the method can be called by providing the argument to your method:
# redefine the University objects above
sjsu = University('San Jose, CA', 'San Jose State University')
# call the enroll method to implement the population instance variable
sjsu.enroll(26851)
# print the population instance variable
print(sjsu.population)
26851
🤔 Mini-Exercise#
Goal: Create a method called mb_storage for your Computer class that defines the total storage available on your computer in Megabytes and stores it in an instance variable called storage. Your method should contain one required argument for the number of bytes and an optional argument for byte type (default = ‘MB’). For example, if you call your function as mb_storage(256000), the storage instance variable should have the value 256000. If you call your function as mb_storage(500,'GB'), the storage instance variable should have the value 500000. Provide options for ‘MB’, ‘GB’,’ and ‘TB’, and raise a ValueError if the input is not one of these strings.
💡 Solution#
Show code cell content
# create your Computer class here
class Computer:
def __init__(self, machine_type, year):
self.machine_type = machine_type
self.year = year
def mb_storage(self, bytes, unit='MB'):
if unit=='MB':
self.storage = bytes
elif unit=='GB':
self.storage = 1000*bytes
elif unit=='TB':
self.storage = 1000*1000*bytes
else:
raise ValueError(unit+' not recognized')
# create your Computer class here
# create an object that describes your personal computer
my_computer = Computer('Laptop',2021)
# call the mb_storage method on your computer object
# test different values and units and print out the
# storage instance variable to ensure it is created as expected
my_computer.mb_storage(500,'MB')
print(my_computer.storage)
500
Special Method Attributes#
In Python, we can override the functions that underly the typical Python operators in our classes. For example, in Python, we don’t have a “toString()” method. However, we can override the str() method to implement this functionality.
# define a class called University
# fill in the __init__ body with the simple name assignment, as above
# add a new __str__ method to return the name of the string
class University:
def __init__(self, location, name):
self.location = location
self.name = name
self.state = 'California'
def __str__(self):
return(self.name)
# define a new University for San Jose State University
sjsu = University('San Jose','San Jose State University')
# convert the object to a str and make a print statement:
print('My university is '+str(sjsu))
My university is San Jose State University
Try the above example without the __str__ method - what happens?
🤔 Mini-Exercise#
Goal: Edit the Univerity class to override the __add__ method. The new method should return the name of the name instance variable to include the string ‘ and ‘ as well as the name of the new university.
💡 Solution#
Show code cell content
# edit the University class here
class University:
def __init__(self, location, name):
self.location = location
self.name = name
def __str__(self):
return(self.name)
def __add__(self, new_school):
return(self.name + ' and ' + new_school.name)
# define two Univeristy objects for sjsu and ucsc
sjsu = University('San Jose','San Jose State University')
ucsc = University('Santa Cruz','UC Santa Cruz')
# print the output of sjsu+ucsc
print(sjsu+ucsc)
San Jose State University and UC Santa Cruz
Subclasses and Inheritance#
When generating classes, we may be interested in creating a class with all of the methods and attributes of an existing class but with the flexibility for addtional methods and attributes.
# define a subclass of the class University called College
# fill the body in with the keyword pass
class College(University):
pass
# create a College object called CoS
CoS = College('San Jose','San Jose State University')
# print the location and name of the College object
print(CoS.location)
print(CoS.name)
San Jose
San Jose State University
What if we want to add an additional argument into our __init__ method? In this case, we can define a new init method that calls the __init__ method of the superclass using the super() reference:
# define a subclass of the class University called College
# fill the body in with the keyword pass
class College(University):
def __init__(self, location, name, dean):
super().__init__(location,name)
self.dean = dean
CoS = College('San Jose','College of Science','Michael Kaufman')
# print the location and name of the College object
print(CoS.location)
print(CoS.name)
print(CoS.dean)
San Jose
College of Science
Michael Kaufman
Name Scoping with Subclasses#
What happens when you define an instance variable within a subclass that has the same name as an instance variable from its superclass?
# define a University class with one argument for name
# in the init method assigned to an instance variable and
# one instance variable for the location,
# set to the word "California"
class University:
def __init__(self, name):
self.location = 'California'
self.name = name
# define a subclass class College with two arguments for name and dean
# in the init method which are assigned to instance variables, and
# one instance variable for the location, set to the word "San Jose"
class College(University):
def __init__(self, name, dean):
super().__init__(name)
self.dean = dean
self.location = 'San Jose'
# initiate a CoS object passing in the name of the college and dean
CoS = College('College of Science','Michael Kaufman')
# print the location of this object
print(CoS.location)
San Jose
What location is printed? Why?
🤔 Mini-Exercise#
Goal: Create a subclass of your Computer class for the type of computer you are working on. For example, your class may be called MacBook or SurfacePro. Your subclass should take in three arguments: the two arguments for the Computer class as well as one additional argument for your operating system (e.g. MacOS, Linux, etc). The operating system should be stored in a given instance variable. Be sure to test all 3 instance variables of your class to ensure they were assigned correctly.
💡 Solution#
Show code cell content
class MacBook(Computer):
def __init__(self, machine_type, year, OS):
super().__init__(machine_type, year)
self.OS = OS
my_macbook = MacBook('Laptop',2021,'MacOS')
print(my_macbook.machine_type)
print(my_macbook.year)
print(my_macbook.OS)
Laptop
2021
MacOS