Implementing Operators in your Classes and Hiding Attributes
References for this lecture
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
Where we left off
Last time, we made a class for representing measurements in feet-inches.
We just implemented 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 logicalandoperator)^:object.__xor__(self, other)|:object.__or__(self, other)(this is not the logicaloroperator)<:object.__lt__(self, other)<=:object.__le__(self, other)==:object.__eq__(self, other)!=:object.__ne__(self, other)>:object.__gt__(self, other)>=:object.__ge__(self, other)
Group Activity Problem 1
Add support for the subtraction operator to the FeetInches class.
Group Activity Problem 2
Discussion the following questions.
- What is the name of the function would I have to define (i.e., like
__add__is for+) to be able to compare twoFeetInchesobjects like in the code below? - Note that
__add__and__sub__both return a new object of typeFeetInches. What type of value should the function for<return?
measurement1 = FeetInches(3,6)
measurement2 = FeetInches(2,6)
print(measurement1 < measurement2)
Group Activity Problem 3
Add support for the < (less than) operator to the FeetInches class.
Group Activity Problem 4
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)
We’ll use this space to go over the solution to the group activities above.
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)
A Playing Card Class
Let’s say we want to create a class for representing playing cards (so we can implement games like War, Bridge, Black Jack, etc.).
Here is a bare-bones implementation:
class PlayingCard:
def __init__(self,v,s):
self.value = v
self.suit = s
two_of_clubs = PlayingCard(2,"♣")
two_of_hearts = PlayingCard(2,"♡")
ten_of_hearts = PlayingCard(10,"♡")
seven_of_spades = PlayingCard(7,"♠")
four_of_diamonds = PlayingCard(4,"♢")
print("Here's what the card looks like:",ten_of_hearts)
Here's what the card looks like: <__main__.PlayingCard object at 0x107713370>
Note that ♣, ♡, ♠, and ♢ are all just text characters (see https://en.wikipedia.org/wiki/Playing_cards_in_Unicode ). You can use C, H, S, and D if you prefer.
Group Activity Problem 5
How should we handle cards without a numic value like Queens or Aces?
What do we have to do to get the cards to print nicely?
What do we have to do to allow us to write code like this?
Copy the code above and begin adding support for these.
if two_of_clubs < ten_of_hearts:
print("Player 2 wins the hand")
Controlling Access
When designing a class, you should plan for how to protect against accidental misuse.
For example, what happens if someone tries to do this?
pikachu = PlayingCard("Pikachu",40)
This particular class is only meant to represent standard French playing cards, so this should cause some kind of error.
One way to handle it might be like this:
class PlayingCard:
def __init__(self,v,s):
if type(v) != type(1) or v > 14 or v < 2:
raise Exception("A PlayingCard's value must be an integer in the range 2-14.")
self.value = v
self.suit = s
twentyseven_of_clubs = PlayingCard(27,"♣")
pikachu = PlayingCard("Pikachu",40)
---------------------------------------------------------------------------
Exception Traceback (most recent call last)
Input In [2], in <cell line: 10>()
7 self.value = v
8 self.suit = s
---> 10 twentyseven_of_clubs = PlayingCard(27,"♣")
11 pikachu = PlayingCard("Pikachu",40)
Input In [2], in PlayingCard.__init__(self, v, s)
3 def __init__(self,v,s):
5 if type(v) != type(1) or v > 14 or v < 2:
----> 6 raise Exception("A PlayingCard's value must be an integer in the range 2-14.")
7 self.value = v
8 self.suit = s
Exception: A PlayingCard's value must be an integer in the range 2-14.
Hiding Attributes
By convention, any attribute or method whose name starts with an underscore should be treated as private, meaning you shouldn’t change the variable outside the class.
self._value
If it doesn’t start with an underscore, it is public and changing it outside the class is fair game.
If you start it with two underscores, then Python performs name mangling, which doesn’t let you change the name outside the class (unless you do something extra to get around the mangling).
self.__value
class PlayingCard:
def __init__(self,v,s):
if type(v) != type(1) or v > 14 or v < 2:
raise Exception("A PlayingCard's value must be an integer in the range 2-14.")
self.__value = v
self.__suit = s
def __repr__(self):
return str(self.__value)+str(self.__suit)
pikachu = PlayingCard(2,"♣")
pikachu.__value = "Pikachu" #this doesn't actually change self.__value
pikachu.__suit = 40
print(pikachu)
2♣
Group Activity Problem 6
Update your class so that self.value and self.suit are hidden (private/mangled).