Problem Solving with Algorithms and Data Structures using Python
Section 1.13: https://runestone.academy/ns/books/published/pythonds/Introduction/ObjectOrientedProgramminginPythonDefiningClasses.html
Section 2.1: https://runestone.academy/ns/books/published/pythonds/ProperClasses/a_proper_python_class.html
Section 2.2 appears to be missing from the book, but we'll cover that too
In Python, we use classes to create new types
A class defines two things:
Let's look at the date type
The date class is defined in the datetime module, and we can import it and use it in our code
import datetime
#creating a new date object
decl_ind_date = datetime.date(1776,7,4)
#datetime.date is a type
print( type(decl_ind_date) )
print("Here's what it looks like when we print a date:",decl_ind_date)
print("Here's what the data that makes up a date looks like:")
print( decl_ind_date.month )
print( decl_ind_date.day )
print( decl_ind_date.year )
#you can call methods on dates - here's one thing you can do with a date
#weekday method returns the number of the day of the week this date fell on (0 = Monday, 6 = Sunday)
print( decl_ind_date.weekday() )
<class 'datetime.date'> Here's what it looks like when we print a date: 1776-07-04 Here's what the data that makes up a date looks like: 7 4 1776 3
month, day, and year are attributes - which data values associated with the object
weekday() is a method - like a function, but you call it using dot notation on a date object
Classes allow you to encapsulate data and actions-on-that-data together into one thing - this is an abstraction technique - it's good programming.
A class defines how objects behave - it is a blueprint that can be used to create many different objects of that type
Syntax:
class:self which refers to the particular object being used at that timeclass Motivator:
def message1(self):
print("You can do it!")
def message2(self):
print("I'm proud of you!")
m = Motivator()
m.message2()
print( type(m) )
I'm proud of you! <class '__main__.Motivator'>
Notice that you always have to make self a parameter, but you don't send it as an argument in parantheses like other arguments.
self is the object (here, m) that the method was called on
Any attribute can be accessed in any of the class's methods using self. Each object of the class has a different set of all the attributes (just like different date objects represent different dates on the calendar)
Initialize attributes using the special __init__() method, which will be invoked whenever a new object of this type is created.
class PersonalMotivator:
def __init__(self,n):
self.name = n
def message1(self):
print("You can do it,",self.name)
def message2(self):
print("I'm proud of you,",self.name)
#creates two objects of the PersonalMotivator class
eric_motivator = PersonalMotivator("Eric")
tim_motivator = PersonalMotivator("Tim")
eric_motivator.message1()
eric_motivator.message2()
tim_motivator.message1()
You can do it, Eric I'm proud of you, Eric You can do it, Tim
self.name get its value from?eric_motivator.message2(), what is self?PersonalMotivator class? Do I have to pass it a name?class Rectangle:
"""
Used for representing rectangles
attributes: length, width
"""
def __init__(self, l, w):
self.length = l
self.width = w
def area(self):
return self.length*self.width
def perimeter(self):
return 2*self.length + 2*self.width
rec1 = Rectangle(5, 10) #instantiates a new object of type Rectangle
rec2 = Rectangle(2, 3) #instantiates a new object of type Rectangle
print("Rectangle 1's area:", rec1.area() ) # self is rec1 here
print("Rectangle 1's perimeter:", rec1.perimeter() )
print("Rectangle 2's area:", rec2.area() ) # self is rec2 here
print("Rectangle 2's perimeter:", rec2.perimeter() )
Rectangle 1's area: 50 Rectangle 1's perimeter: 30 Rectangle 2's area: 6 Rectangle 2's perimeter: 10
I have multiple rectangle objects but only one class definition
A class is a blueprint for creating many objects - it's like how you can build many houses in a neighborhood from one set of blueprints.
Object-oriented programming: a popular style of programming that centers on creating custom classes and objects instantiated from those classes.
Let's say we are creating a program that uses a lot of measurements in feet-inches (e.g., numbers like 5 ft. 6 in. or 5'6") - maybe it is a drafting program for planning home remodeling projects.
To create a basic feet-inches type, we might start with a class like this:
class FeetInches:
def __init__(self,f,i):
self.feet = f
self.inches = i
def simplify(self):
"""
if the number of inches is > 12,
this regroups the excess into feet
"""
self.feet += self.inches // 12
self.inches = self.inches % 12
def display(self):
print(self.feet,"ft.",self.inches,"in.")
room_length = FeetInches(12,4)
room_width = FeetInches(9,15)
#displayinig the room dimensions
room_length.display()
room_width.display()
#simplifying the dimensions and then displaying them
room_length.simplify()
room_width.simplify()
room_length.display()
room_width.display()
12 ft. 4 in. 9 ft. 15 in. 12 ft. 4 in. 10 ft. 3 in.
Having a display() function to nicely print the measurement isn't very flexible. I'd like to treat it like any other data type and just do something like
print("The length of the room is",room_length)
The length of the room is <__main__.FeetInches object at 0x10662c100>
Fortunately, Python allows us to write a special method __repr__() where you can define what the object looks like with a string representation.
This will work with things like print(), str(), and how it displays the object in the interactive shell.
The __repr__() method should return a string representation of the object (not print it).
class FeetInches:
def __init__(self,f,i):
self.feet = f
self.inches = i
def simplify(self):
"""
if the number of inches is > 12,
this regroups the excess into feet
"""
self.feet += self.inches // 12
self.inches = self.inches % 12
def __repr__(self):
return str(self.feet)+"ft. "+str(self.inches)+"in."
room_length = FeetInches(12,4)
room_width = FeetInches(9,15)
#simplifying the dimensions
room_length.simplify()
room_width.simplify()
#displayinig the room dimensions
print("The length of the room is",room_length)
print("The width of the room is",room_width)
str(room_length) #now I can convert it to a string
The length of the room is 12ft. 4in. The width of the room is 10ft. 3in.
'12ft. 4in.'
Unfortunately, our new type doesn't let us do things like this:
room_length = FeetInches(12,4)
room_width = FeetInches(9,15)
room_length + room_width
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Input In [11], in <cell line: 4>() 1 room_length = FeetInches(12,4) 2 room_width = FeetInches(9,15) ----> 4 room_length + room_width TypeError: unsupported operand type(s) for +: 'FeetInches' and 'FeetInches'
Based on what we've covered already from classes, how could we support being able to add two FeetInches values?
One solution might be to just implement an add() function like this:
class FeetInches:
def __init__(self,f,i):
self.feet = f
self.inches = i
def simplify(self):
"""
if the number of inches is > 12,
this regroups the excess into feet
"""
self.feet += self.inches // 12
self.inches = self.inches % 12
def __repr__(self):
return str(self.feet)+"ft. "+str(self.inches)+"in."
def add(self,other_measurement):
total_feet = self.feet + other_measurement.feet
total_inches = self.inches + other_measurement.inches
#create an new FeetInches object with the new measurements
total_FI = FeetInches(total_feet,total_inches)
total_FI.simplify()
return total_FI
measurement1 = FeetInches(3,6)
measurement2 = FeetInches(2,6)
total = measurement1.add(measurement2)
print(total)
6ft. 0in.
However, we can instead implement the special __add__() method, which will make it work with the + operator.
class FeetInches:
def __init__(self,f,i):
self.feet = f
self.inches = i
def simplify(self):
"""
if the number of inches is > 12,
this regroups the excess into feet
"""
self.feet += self.inches // 12
self.inches = self.inches % 12
def __repr__(self):
return str(self.feet)+"ft. "+str(self.inches)+"in."
def __add__(self,other_measurement):
total_feet = self.feet + other_measurement.feet
total_inches = self.inches + other_measurement.inches
#create an new FeetInches object with the new measurements
total_FI = FeetInches(total_feet,total_inches)
total_FI.simplify()
return total_FI
measurement1 = FeetInches(3,6)
measurement2 = FeetInches(2,6)
total = measurement1 + measurement2
print(total)
6ft. 0in.
Similarly, you can support any of the following operators (plus more that I haven't listed):
+: object.__add__(self, other) -: object.__sub__(self, other) *: object.__mul__(self, other) /: object.__truediv__(self, other) //: object.__floordiv__(self, other)%: object.__mod__(self, other)**: object.__pow__(self, other)<<: object.__lshift__(self, other)>>: object.__rshift__(self, other)&: object.__and__(self, other) (this is not the logical and operator)^: object.__xor__(self, other)|: object.__or__(self, other) (this is not the logical or operator)<: object.__lt__(self, other)<=: object.__le__(self, other)==: object.__eq__(self, other)!=: object.__ne__(self, other)>: object.__gt__(self, other)>=: object.__ge__(self, other)Add support for the subtraction operator to the FeetInches class.
Discussion the following questions.
__add__ is for +) to be able to compare two FeetInches objects like in the code below?__add__ and __sub__ both return a new object of type FeetInches. What type of value should the function for < return?measurement1 = FeetInches(3,6)
measurement2 = FeetInches(2,6)
print(measurement1 < measurement2)
Add support for the < (less than) operator to the FeetInches class.
After you implement <, you might get some other operators like > for free. Try the code below and see if it works with your definition. Do <=, ==, !=, >= work? Why or why not?
measurement1 = FeetInches(3,6)
measurement2 = FeetInches(2,6)
print(measurement1 > measurement2)