10/22/17
1
Case Study: Fractions
Want to add a new type
§ Values are fractions: ½, ¾
§ Operations are standard
multiply, divide, etc.
§ Example: ½*¾ =
Can do this with a class
§ Values are fraction objects
§ Operations are methods
Example: frac1.py
class Fraction(object):
"""Instance is a fraction n/d
INSTANCE ATTRIBUTES:
_numerator: top [int]
_denominator: bottom [int > 0]
"""
def __init__(self,n=0,d=1):
"""Init: makes a Fraction"""
self._numerator = n
self._denominator = d
Problem: Doing Math is Unwieldy
What We Want
1
2
+
1
3
+
1
4
5
4
What We Get
>>> p = Fraction(1,2)
>>> q = Fraction(1,3)
>>> r = Fraction(1,4)
>>> s = Fraction(5,4)
>>> (p.add(q.add(r))).mult(s)
This is confusing!
Why not use the
standard Python
math operations?
Operator Overloading
Many operators in Python a special symbols
§ +, -, /, *, ** for mathematics
§ ==, !=, <, > for comparisons
The meaning of these symbols depends on type
§ 1 + 2 vs 'Hello' + 'World'
§ 1 < 2 vs 'Hello' < 'World'
Our new type might want to use these symbols
§ We overload them to support our new type
Returning to Fractions
What We Want
1
2
+
1
3
+
1
4
5
4
Operator Overloading
Python has methods that
correspond to built-in ops
§ __add__ corresponds to +
§ __mul__ corresponds to *
§ __eq__ corresponds to ==
§ Not implemented by default
To overload operators you
implement these methods
Why not use the
standard Python
math operations?
Operator Overloading: Multiplication
class Fraction(object):
"""Instance attributes:
_numerator: top [int]
_denominator: bottom [int > 0]""”
def __mul__(self,q):
"""Returns: Product of self, q
Makes a new Fraction; does not
modify contents of self or q
Precondition: q a Fraction"""
assert type(q) == Fraction
top= self._numerator*q._numerator
bot= self._denominator*q._denominator
return Fraction(top,bot)
>>> p = Fraction(1,2)
>>> q = Fraction(3,4)
>>> r = p*q
>>> r = p.__mul__(q)
Python
converts to
Operator overloading uses
method in object on left.
Operator Overloading: Addition
class Fraction(object):
"""Instance attributes:
_numerator: top [int]
_denominator: bottom [int > 0]""”
def __add__(self,q):
"""Returns: Sum of self, q
Makes a new Fraction
Precondition: q a Fraction"""
assert type(q) == Fraction
bot= self._denominator*q._denominator
top= (self._numerator*q._denominator+
self._denominator*q._numerator)
return Fraction(top,bot)
>>> p = Fraction(1,2)
>>> q = Fraction(3,4)
>>> r = p+q
>>> r = p.__add__(q)
Python
converts to
Operator overloading uses
method in object on left.
10/22/17
2
Comparing Objects for Equality
Earlier in course, we saw ==
compare object contents
§ This is not the default
§ Default: folder names
Must implement __eq__
§ Operator overloading!
§ Not limited to simple
attribute comparison
§ Ex: cross multiplying
1 2
2 4
class Fraction(object):
"""Instance attributes:
_numerator: top [int]
_denominator: bottom [int > 0]"""
def __eq__(self,q):
"""Returns: True if self, q equal,
False if not, or q not a Fraction"""
if type(q) != Fraction:
return False
left = self._numerator*q._denominator
rght = self._denominator*q._numerator
return left == rght
4 4
is Versus ==
p is q evaluates to False
§ Compares folder names
§ Cannot change this
p == q evaluates to True
§ But only because method
__eq__ compares contents
id2
Point
id2
p
id3
q
x 2.2
y
z
5.4
6.7
id3
Point
x 2.2
y
z
5.4
6.7
Always use (x is None) not (x == None)
Recall: Overloading Multiplication
class Fraction(object):
"""Instance attributes:
_numerator [int]: top
_denominator [int > 0]: bottom """
def __mul__(self,q):
"""Returns: Product of self, q
Makes a new Fraction; does not
modify contents of self or q
Precondition: q a Fraction"""
assert type(q) == Fraction
top = self._numerator*q._numerator
bot= self._denominator*q._denominator
return Fraction(top,bot)
>>> p = Fraction(1,2)
>>> q = 2 # an int
>>> r = p*q
>>> r = p.__mul__(q) # ERROR
Python
converts to
Can only multiply fractions.
But ints “make sense” too.
Solution: Look at Argument Type
Overloading use left type
§ p*q => p.__mul__(q)
§ Done for us automatically
§ Looks in class definition
What about type on right?
§ Have to handle ourselves
Can implement with ifs
§ Write helper for each type
§ Check type in method
§ Send to appropriate helper
class Fraction(object):
def __mul__(self,q):
"""Returns: Product of self, q
Precondition: q a Fraction or int"""
if type(q) == Fraction:
return self._mulFrac(q)
elif type(q) == int:
return self._mulInt(q)
def _mulInt(self,q): # Hidden method
return Fraction(self._numerator*q,
self._denominator)
A Better Multiplication
class Fraction(object):
def __mul__(self,q):
"""Returns: Product of self, q
Precondition: q a Fraction or int"""
if type(q) == Fraction:
return self._mulFrac(q)
elif type(q) == int:
return self._mulInt(q)
def _mulInt(self,q): # Hidden method
return Fraction(self._numerator*q,
self._denominator)
>>> p = Fraction(1,2)
>>> q = 2 # an int
>>> r = p*q
>>> r = p.__mul__(q) # OK!
Python
converts to
See frac3.py for a full
example of this method
Advanced Example: A6 Pixels
Image is list of list of RGB
§ But this is really slow
§ Faster: byte buffer (???)
§ Beyond scope of course
Compromise: Pixels class
§ Has byte buffer attribute
§ Pretends to be list of tuples
§ You can slice/iterate/etc
Uses data model to do this
0 1 2 3 4 5 6 7 8 9 10 11 12
0
1
2
3
4
5
6
7
8
9
10
11
12
[(255,255,255), (255,255,255), …]