Module 23 Abstraction
Case Study: Fractions class Fraction(object): • Want to add a new type """Instance is a fraction n/d""" § Values are fractions: ½ , ¾ # INSTANCE ATTRIBUTES: § Operations are standard # _numerator: an int multiply, divide, etc. # _denominator: an int > 0 § Example : ½ * ¾ = ⅜ • Can do this with a class def __init__(self,n=0,d=1): § Values are fraction objects """Init: makes a Fraction""" § Operations are methods self._numerator = n self._denominator = d • Example : frac1.py
Case Study: Fractions class Fraction(object): • Want to add a new type """Instance is a fraction n/d""" § Values are fractions: ½ , ¾ # INSTANCE ATTRIBUTES: § Operations are standard Reminder : Hide # _numerator: an int multiply, divide, etc. attributes, use # _denominator: an int > 0 § Example : ½ * ¾ = ⅜ getters/setters • Can do this with a class def __init__(self,n=0,d=1): § Values are fraction objects """Init: makes a Fraction""" § Operations are methods self._numerator = n self._denominator = d • Example : frac1.py
Problem: Doing Math is Unwieldy What We Want What We Get 1 2 + 1 3 + 1 4 ∗ 5 >>> p = Fraction(1,2) >>> q = Fraction(1,3) 4 >>> r = Fraction(1,4) >>> s = Fraction(5,4) >>> (p.add(q.add(r))).mult(s) This is confusing!
Problem: Doing Math is Unwieldy What We Want What We Get 1 2 + 1 3 + 1 4 ∗ 5 >>> p = Fraction(1,2) >>> q = Fraction(1,3) 4 >>> r = Fraction(1,4) >>> s = Fraction(5,4) Why not use the >>> (p.add(q.add(r))).mult(s) standard Python math operations? This is confusing!
Abstraction • Goal: Hide unimportant details from user § Replace unfamiliar with the familiar § Focus on the core functionality of the type • Data encapsulation is one part of it § Hide direct access to the attributes § Only allow getters and setters • But also involves operator overloading § Replace method calls with operators § Make class feel like a built-in type
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
Special Methods in Python class Point3(object): • Have seen three so far """Instances are points in 3D space""" § __init__ for initializer … § __str__ for str() def __init__(self,x=0,y=0,z=0): § __repr__ for repr() """Initializer: makes new Point3""" • Start/end with 2 underscores … § This is standard in Python def __str__(self,q): § Used in all special methods """Returns: string with contents""” § Also for special attributes … • We can overload operators def __repr__(self,q): § Give new meaning to +, *, - """Returns: unambiguous string""” …
Returning to Fractions What We Want Operator Overloading 1 2 + 1 3 + 1 4 ∗ 5 • Python has methods that correspond to built-in ops 4 § __ add__ corresponds to + § __mul__ corresponds to * § __eq__ corresponds to == Why not use the § Not implemented by default standard Python • To overload operators you math operations? implement these methods
Operator Overloading: Multiplication class Fraction(object): >>> p = Fraction(1,2) """Instance is a fraction n/d""" >>> q = Fraction(3,4) # _numerator: an int >>> r = p*q # _denominator: an int > 0 def __mul__(self,q): Python """Returns: Product of self, q converts to Makes a new Fraction; does not modify contents of self or q >>> r = p.__mul__(q) Precondition: q a Fraction""" assert type(q) == Fraction Operator overloading uses top= self._numerator*q._numerator bot= self._denominator*q._denominator method in object on left. return Fraction(top,bot)
Operator Overloading: Addition class Fraction(object): >>> p = Fraction(1,2) """Instance is a fraction n/d""” >>> q = Fraction(3,4) # _numerator: an int >>> r = p+q # _denominator: an int > 0 def __add__(self,q): Python """Returns: Sum of self, q converts to Makes a new Fraction Precondition: q a Fraction""" >>> r = p.__add__(q) assert type(q) == Fraction bot= self._denominator*q._denominator Operator overloading uses top= (self._numerator*q._denominator+ self._denominator*q._numerator) method in object on left. return Fraction(top,bot)
Comparing Objects for Equality class Fraction(object): • Earlier in course, we saw == """Instance is a fraction n/d""" compare object contents # _numerator: an int § This is not the default # _denominator: an int > 0 § Default : folder names • Must implement __eq__ def __eq__(self,q): """Returns: True if self, q equal, § Operator overloading! False if not, or q not a Fraction""" § Not limited to simple if type(q) != Fraction: attribute comparison return False § Ex : cross multiplying left = self._numerator*q._denominator rght = self._denominator*q._numerator 4 1 2 4 return left == rght 2 4
is Versus == • p is q evaluates to False • p == q evaluates to True § Compares folder names § But only because method __eq__ compares contents § Cannot change this id2 id2 id3 id3 p q Point Point x 2.2 x 2.2 y 5.4 y 5.4 z 6.7 z 6.7 Always use (x is None) not (x == None)
Recall: Overloading Multiplication class Fraction(object): >>> p = Fraction(1,2) """Instance is a fraction n/d""" >>> q = 2 # an int # _numerator: an int >>> r = p*q # _denominator: an int > 0 def __mul__(self,q): Python """Returns: Product of self, q converts to Makes a new Fraction; does not modify contents of self or q >>> r = p.__mul__(q) # ERROR Precondition: q a Fraction""" assert type(q) == Fraction Can only multiply fractions. top = self._numerator*q._numerator bot= self._denominator*q._denominator But ints “make sense” too. return Fraction(top,bot)
Solution: Look at Argument Type class Fraction(object): • Overloading use left type … § p*q => p.__mul__(q) def __mul__(self,q): § Done for us automatically """Returns: Product of self, q Precondition: q a Fraction or int""" § Looks in class definition if type(q) == Fraction: • What about type on right ? return self._mulFrac(q) § Have to handle ourselves elif type(q) == int: return self._mulInt(q) • Can implement with ifs … § Write helper for each type def _mulInt(self,q): # Hidden method return Fraction(self._numerator*q, § Check type in method self._denominator) § Send to appropriate helper
A Better Multiplication class Fraction(object): >>> p = Fraction(1,2) … >>> q = 2 # an int def __mul__(self,q): >>> r = p*q """Returns: Product of self, q Precondition: q a Fraction or int""" Python if type(q) == Fraction: converts to return self._mulFrac(q) elif type(q) == int: return self._mulInt(q) >>> r = p.__mul__(q) # OK! … def _mulInt(self,q): # Hidden method See frac3.py for a full return Fraction(self._numerator*q, example of this method self._denominator)
What Do We Get This Time? class Fraction(object): >>> p = Fraction(1,2) … >>> q = 2 # an int def __mul__(self,q): >>> r = q*p """Returns: Product of self, q Precondition: q a Fraction or int""" if type(q) == Fraction: A: Fraction(2,2) return self._mulFrac(q) elif type(q) == int: B: Fraction(1,1) return self._mulInt(q) C: Fraction(2,4) … D: Error def _mulInt(self,q): # Hidden method return Fraction(self._numerator*q, E: I don’t know self._denominator)
What Do We Get This Time? class Fraction(object): >>> p = Fraction(1,2) … >>> q = 2 # an int def __mul__(self,q): >>> r = q*p """Returns: Product of self, q Precondition: q a Fraction or int""" if type(q) == Fraction: Meaning determined by left. A: Fraction(2,2) return self._mulFrac(q) Variable q stores an int . elif type(q) == int: B: Fraction(1,1) return self._mulInt(q) C: Fraction(2,4) … D: Error CORRECT def _mulInt(self,q): # Hidden method return Fraction(self._numerator*q, E: I don’t know self._denominator)
The Python Data Model http://docs.python.org/3/reference/datamodel.html
We Have Come Full Circle • On the first day, saw that a type is both § a set of values , and § the operations on them • In Python, all values are objects § Everything has a folder in the heap § Just ignore it for immutable, basic types • In Python, all operations are methods § Each operator has a double-underscore helper § Looks at type of object on left to process
Recommend
More recommend