Building Python Programs Chapter 11 Classes and Objects
Building Python Programs Chapter 11: Classes and Objects
Clients of objects • client program: A program that uses objects. • Example: shapes is a client of Drawing. Panel. shapes. py (client program) def main(): Drawing. Panel(. . . ). . . main() drawingpanel. py (class) class Drawing. Panel: . . .
Classes and objects • class: A program entity that represents either: 1. A program / module, or 2. A template for a new type of objects. • The drawingpanel class is a template for creating Drawing. Panel objects. • object: An entity that combines state and behavior. • object-oriented programming (OOP): Programs that perform their behavior as interactions between objects.
A programming problem • Given a file of cities' names and (x, y) coordinates: Winslow 50 20 Tucson 90 60 Phoenix 10 72 Bisbee 74 98 Yuma 5 136 Page 150 91 • Write a program to draw the cities on a Drawing. Panel, then simulates an earthquake that turns all cities red that are within a given radius: Epicenter x? 100 Epicenter y? 100 Affected radius? 75
Observations • The data in this problem is a set of points. • It would be better stored together
Observations • The data in this problem is a set of points. • It would be better stored as Point objects. • A Point would store a city's x/y data. • We could compare distances between Points to see whether the earthquake hit a given city. • Each Point would know how to draw itself. • The overall program would be shorter and cleaner.
Abstraction • abstraction: A distancing between ideas and details. • We can use objects without knowing how they work. • abstraction in an i. Pod: • You understand its external behavior (buttons, screen). • You don't understand its inner details, and you don't need to.
Blueprint analogy i. Pod blueprint state: current song volume battery life behavior: power on/off change station/song change volume choose random song creates i. Pod #1 i. Pod #2 i. Pod #3 state: song = "1, 000 Miles" volume = 17 battery life = 2. 5 hrs state: song = "Letting You" volume = 9 battery life = 3. 41 hrs state: song = "Discipline" volume = 24 battery life = 1. 8 hrs behavior: power on/off change station/song change volume choose random song
Our task • In the following slides, we will implement a Point class as a way of learning about defining classes. • • We will define a type of objects named Point. Each Point object will contain x/y data called attributes. Each Point object will contain behavior called methods. Client programs will use the Point objects.
Point objects (desired) p 1 = Point(5, -2) p 2 = Point() # origin, (0, 0) • Data in each Point object: Attribute name Description x the point's x-coordinate y the point's y-coordinate • Methods in each Point object: Method name Description set. Location(x, y) sets the point's x and y to the given values translate(dx, dy) adjusts the point's x and y by the given amounts distance(p) how far away the point is from point p draw(g) displays the point on a drawing panel
Point class as blueprint Point class state: int x, y behavior: set_location(int x, int y) translate(int dx, int dy) distance(Point p) draw(Graphics g) Point object #1 state: x = 5, y = -2 behavior: set_location(int x, int y) translate(int dx, int dy) distance(Point p) draw(Graphics g) Point object #2 state: x = -245, y = 1897 behavior: set_location(int x, int y) translate(int dx, int dy) distance(Point p) draw(Graphics g) Point object #3 state: x = 18, y = 42 behavior: set_location(int x, int y) translate(int dx, int dy) distance(Point p) draw(Graphics g) • The class (blueprint) will describe how to create objects. • Each object will contain its own data and methods.
Point class, version 1 class Point: def __init__(self): self. x = 0 self. y = 0 • Save this code into a file named point. py. • The above code creates a new type named Point. • Each Point object contains two pieces of data: • an int named x, and • an int named y. • Point objects do not contain any behavior (yet).
Attributes • attribute: A variable inside an object that is part of its state. • Each object has its own copy of each field. • Declaration syntax: self. name = value • Example: class Student: def __init__(self): self. name = "" self. gpa = 0. 0 # each Student object has a # name and gpa field
Accessing attributes • Other classes can access/modify an object's attributes. • access: • modify: variable. attribute = value • Example: p 1 = Point() p 2 = Point() print("the x-coord is " + str(p 1. x)) p 2. y = 13 # access # modify
A class and its client • point. py is not, by itself, a runnable program. • A class can be used by client programs. point_main. py (client program) def main(): p 1 = Point() p 1. x = 7 p 1. y = 2 p 2 = Point() p 2. x = 4 p 2. y = 3. . . main() point. py (class of objects) class Point: def __init__(self): self. x self. y x 7 y 2 x 4 y 3
point_main client example def main(): # create two Point objects p 1 = Point() p 1. y = 2 p 2 = Point() p 2. x = 4 print(str(p 1. x) + ", " + str(p 1. y)) # 0, 2 # move p 2 and then print it p 2. x += 2 p 2. y += 1 print(str(p 2. x) + ", " + str(p 2. y)) # 6, 1
Client code redundancy • Suppose our client program wants to draw Point objects: # draw each city p 1 = Point() p 1. x = 15 p 1. y = 37 p. canvas. create_oval(p 1. x, p 1. y, p 1. x + 3, p 2. x + 3); p. canvas. create_string(p 1. x, p 1. y, "(" + str(p 1. x) + ", " + str(p 1. y) + ")") • To draw other points, the same code must be repeated. • We can remove this redundancy using a method.
Eliminating redundancy, v 1 • We can eliminate the redundancy with a function: # Draws the given point on the Drawing. Panel. def draw(p, panel): panel. canvas. create_oval(p 1. x, p 1. y, p 1. x + 3, p 2. x + 3); panel. canvas. create_string(p 1. x, p 1. y, "(" + str(p 1. x) + ", " + str(p 1. y) + ")") • main would call the method as follows: draw(p 1, panel)
Problems with function solution • We are missing a major benefit of objects: code reuse. • Every program that draws Points would need a draw function. • The syntax doesn't match how we're used to using objects. draw(p 1, panel) # function (bad) • The point of classes is to combine state and behavior. • The draw behavior is closely related to a Point's data. • The function belongs inside each Point object. p 1. draw(panel) # inside the object (better)
Instance methods • method (or object function): Exists inside each object of a class and gives behavior to each object. def name(self, parameters): statements • same syntax as functions, but with an extra self parameter Example: def shout(self): print("HELLO THERE!")
Instance method example class Point: def __init__(self): self. x = 0 self. y = 0 # Draws this Point object on the given panel def draw(self, panel): . . . • The draw method no longer has a Point p parameter. • How will the method know which point to draw? • How will the method access that point's x/y data?
Point objects w/ method • Each Point object has its own copy of the draw method, which operates on that object's state: p 1 = Point() p 1. x = 7 p 1. y = 2 x p 2 = Point() p 2. x = 4 p 2. y = 3 7 y 2 def draw(self, panel): # this code can see p 1's x and y p 1. draw(panel) p 2 x 4 y 3 def draw(self, panel): # this code can see p 2's x and y
The implicit parameter • implicit parameter: The object on which an instance method is called. • During the call p 1. draw(panel) the object referred to by p 1 is the implicit parameter. • During the call p 2. draw(panel) the object referred to by p 2 is the implicit parameter. • The instance method can refer to that object's attributes. • We say that it executes in the context of a particular object. • draw can refer to the x and y of the object it was called on.
Point class, version 2 class Point: def __init__(self): self. x = 0 self. y = 0 # Changes the location of this Point object. def draw(self, panel): panel. canvas. create_rectangle(x, y, x + 3, y + 3) panel. canvas. create_string("(" + str(x) + ", " + str(y) + ")", x, y) • Each Point object contains a draw method that draws that point at its current x/y position.
Class method questions • Write a method translate that changes a Point's location by a given dx, dy amount. • Write a method distance_from_origin that returns the distance between a Point and the origin, (0, 0). Use the formula: • Modify the Point and client code to use these methods.
Class method answers class Point: def __init__(self): self. x self. y def translate(self, dx, dy): x = x + dx y = y + dy def distance_from_origin(self): return sqrt(x * x + y * y)
Point objects w/ method • Each Point object has its own copy of the distance_from_origin method, which operates on that object's state: p 1 = Point() p 1. x = 7 p 1. y = 2 x 7 y 2 def distance_from_origin(self): # this code can see p 1's x and y return sqrt(self. x * self. x + self. y * self. y) p 2 = Point() p 2. x = 4 p 2. y = 3 p 1. distance_from_origin() p 2 x 4 y 3 def distance_from_origin(self): # this code can see p 2's x and y return sqrt(self. x * self. x + self. y * self. y)
Kinds of methods • accessor: A method that lets clients examine object state. • Examples: distance, distance_from_origin • often returns something • mutator: A method that modifies an object's state. • Examples: set_location, translate
Initializing objects • Currently it takes 3 lines to create a Point and initialize it: p = Point() p. x = 3 p. y = 8 # tedious • We'd rather specify the fields' initial values at the start: p = Point(3, 8) # desired; doesn't work (yet) • We are able to this with most types of objects in Python.
Printing objects • By default, Python doesn't know how to print objects: p = Point() p. x = 10 p. y = 7 print("p is " + str(p)) # p is # <p. Point object at 0 x 000001 BA 6 AE 0 BF 28> # better, but cumbersome; p is (10, 7) print("p is (" + str(p. x) + ", " + str(p. y) + ")") # desired behavior print("p is " + str(p)) # p is (10, 7)
The __str__ method tells Python how to convert an object into a string p 1 = Point(7, 2) print("p 1: " + str(p 1)) • Every class has a __str__, even if it isn't in your code. <point. Point object at 0 x 000001 BA 6 AE 0 BF 28>
__str__ syntax def __str__(self): code that returns a String representing this object • Method name, return, and parameters must match exactly. • Example: # Returns a String representing this Point. def __str__(self): return "(" + str(x) + ", " + str(y) + ")"
Encapsulation
Encapsulation • encapsulation: Hiding implementation details of an object from its clients. • Encapsulation provides abstraction. • separates external view (behavior) from internal view (state) • Encapsulation protects the integrity of an object's data.
Private fields • A attribute can be made invisible to outsiders • No code outside the class can access or change it easily. __name • Examples: self. __id self. __name • Client code sees an error when accessing private attributes
Accessing private state • We can provide methods to get and/or set a attribute's value: # A "read-only" access to the __x field ("accessor") def get_x(self): return self. __x # Allows clients to change the __x field ("mutator") def set_x(self, new_x): self. __x = new_x • Client code will look more like this: print("p 1: (" + str(p 1. get_x()) + ", " + str(p 1. get_y()) + ")") p 1. set_x(14)
Benefits of encapsulation • Provides abstraction between an object and its clients. • Protects an object from unwanted access by clients. • A bank app forbids a client to change an Account's balance. • Allows you to change the class implementation. • Point could be rewritten to use polar coordinates (radius r, angle θ), but with the same methods. • Allows you to constrain objects' state (invariants). • Example: Only allow Points with non-negative coordinates.
Point class, version 4 # A Point object represents an (x, y) location. class Point: self. __x self. __y def __init__(self, initial_x, initial_y): self. __x = initial_x self. __y = initial_y def distance_from_origin(self): return sqrt(self. __x * self. __x + self. __y * self. __y) def get_x(self): return self. __x def get_y(self): return self. __y def set_location(self, new_x, new_y): self. __x = new_x self. __y = new_y def translate(self, dx, dy): self. __x = self. __x + dx self. __y = self. __y + dy
Client code, version 4 def main 9): # create two Point objects p 1 = Point(5, 2) p 2 = Point(4, 3) # print each point print("p 1: (" + str(p 1. get_x()) + ", " + str(p 1. get_y()) + ")") print("p 2: (" + str(p 2. get_x()) + ", " + str(p 2. get_y()) + ")") # move p 2 and then print it again p 2. translate(2, 4) print("p 2: (" + str(p 2. get_x()) + ", " + str(p 2. get_y()) + ")") OUTPUT: p 1 is (5, 2) p 2 is (4, 3) p 2 is (6, 7)
Inheritance
The software crisis • software engineering: The practice of developing, designing, documenting, testing large computer programs. • Large-scale projects face many issues: • • • programmers working together getting code finished on time avoiding redundant code finding and fixing bugs maintaining, reusing existing code • code reuse: The practice of writing program code once and using it in many contexts.
Law firm employee analogy • common rules: hours, vacation, benefits, regulations. . . • all employees attend a common orientation to learn general company rules • each employee receives a 20 -page manual of common rules • each subdivision also has specific rules: • employee receives a smaller (1 -3 page) manual of these rules • smaller manual adds some new rules and also changes some rules from the large manual
Separating behavior • Why not just have a 22 page Lawyer manual, a 21 -page Secretary manual, a 23 -page Marketer manual, etc. ? • Some advantages of the separate manuals: • maintenance: Only one update if a common rule changes. • locality: Quick discovery of all rules specific to lawyers. • Some key ideas from this example: • General rules are useful (the 20 -page manual). • Specific rules that may override general ones are also useful.
Is-a relationships, hierarchies • is-a relationship: A hierarchical connection where one category can be treated as a specialized version of another. • every marketer is an employee • every legal secretary is a secretary • inheritance hierarchy: A set of classes connected by is-a relationships that can share common code.
Employee regulations • Consider the following employee regulations: • Employees work 40 hours / week. • Employees make $40, 000 per year, except legal secretaries who make $5, 000 extra per year ($45, 000 total), and marketers who make $10, 000 extra per year ($50, 000 total). • Employees have 2 weeks of paid vacation leave per year, except lawyers who get an extra week (a total of 3). • Employees should use a yellow form to apply for leave, except for lawyers who use a pink form. • Each type of employee has some unique behavior: • Lawyers know how to sue. • Marketers know how to advertise. • Secretaries know how to take dictation. • Legal secretaries know how to prepare legal documents.
An Employee class # A class to represent employees in general (20 -page manual). class Employee: def get_hours(self): return 40 # works 40 hours / week def get_salary(self): return 40000. 0 # $40, 000. 00 / year def get_vacation_days(self): return 10 # 2 weeks' paid vacation def get_vacation_form(self): return "yellow" # use the yellow form • Exercise: Implement class Secretary, based on the previous employee regulations. (Secretaries can take dictation. )
Redundant Secretary class # A redundant class to represent secretaries. class Secretary: def get_hours(self): return 40 # works 40 hours / week def get_salary(self): return 40000. 0 # $40, 000. 00 / year defget_vacation_days(self): return 10 # 2 weeks' paid vacation def get_vacation_form(self): return "yellow" # use the yellow form def take_dictation(self, text): print("Taking dictation of text: " + text)
Desire for code-sharing • take_dictation is the only unique behavior in Secretary. • We'd like to be able to say: # A class to represent secretaries. class Secretary: copy all the contents from the Employee class def take_dictation(self, text): print("Taking dictation of text: " + text)
Inheritance • inheritance: A way to form new classes based on existing classes, taking on their attributes/behavior. • a way to group related classes • a way to share code between two or more classes • One class can extend another, absorbing its data/behavior. • superclass: The parent class that is being extended. • subclass: The child class that extends the superclass and inherits behavior. • Subclass gets a copy of every field and method from superclass
Inheritance syntax class name(superclass): • Example: class Secretary(Employee): . . . • By extending Employee, each Secretary object now: • receives a get_hours, get_salary, get_vacation_days, and get_vacation_form method automatically • can be treated as an Employee by client code (seen later)
Improved Secretary code # A class to represent secretaries. class Secretary (Employee): def take_dictation(self, text): print("Taking dictation of text: " + text) • Now we only write the parts unique to each type. • Secretary inherits get_hours, get_salary, get_vacation_days, and get. Vacation. Form methods from Employee. • Secretary adds the take_dictation method.
Implementing Lawyer • Consider the following lawyer regulations: • Lawyers who get an extra week of paid vacation (a total of 3). • Lawyers use a pink form when applying for vacation leave. • Lawyers have some unique behavior: they know how to sue. • Problem: We want lawyers to inherit most behavior from employee, but we want to replace parts with new behavior.
Overriding methods • override: To write a new version of a method in a subclass that replaces the superclass's version. • No special syntax required to override a superclass method. Just write a new version of it in the subclass. class Lawyer(Employee): # overrides get_vacation_form method in Employee class def get_vacation_form(): return "pink". . . • Exercise: Complete the Lawyer class. • (3 weeks vacation, pink vacation form, can sue)
Lawyer class # A class to represent lawyers. class Lawyer(Employee): # overrides get_vacation_form from Employee class def get_vacation_form(self): return "pink" # overrides get_vacation_days from Employee class def get_vacation_days(self): return 15 # 3 weeks vacation def sue(self): print("I'll see you in court!") • Exercise: Complete the Marketer class. Marketers make $10, 000 extra ($50, 000 total) and know how to advertise.
Marketer class # A class to represent marketers. class Marketer(Employee): def advertise(): print("Act now while supplies last!") def get_salary(): return 50000. 0 # $50, 000. 00 / year
Levels of inheritance • Multiple levels of inheritance in a hierarchy are allowed. • Example: A legal secretary is the same as a regular secretary but makes more money ($45, 000) and can file legal briefs. class Legal. Secretary(Secretary): . . . • Exercise: Complete the Legal. Secretary class.
Legal. Secretary class # A class to represent legal secretaries. class Legal. Secretary(Secretary): def file_legal_briefs(self): print("I could file all day!") def get_salary(self): return 45000. 0 # $45, 000. 00 / year
Calling overridden methods • Subclasses can call overridden methods with super(Class. Name, self). method(parameters) • Example: class Legal. Secretary(Secretary): def get_salary(self): base_salary = super(Legal. Secretary, self). get_salary() return base_salary + 5000. 0. . .
Inheritance and constructors • Imagine that we want to give employees more vacation days the longer they've been with the company. • For each year worked, we'll award 2 additional vacation days. • When an Employee object is constructed, we'll pass in the number of years the person has been with the company. • This will require us to modify our Employee class and add some new state and behavior. • Exercise: Make necessary modifications to the Employee class.
Modified Employee class Employee: def __init__(self, initial_years): self. __years = initial_years def get_hours(self): return 40 def get_salary(self): return 50000. 0 def get_vacation_days(self): return 10 + 2 * self. __years def get_vacation_form(self): return "yellow"
Problem with constructors • Now that we've added the constructor to the Employee class, our subclasses do not compile. The error: Type. Error: __init__() missing 1 required positional argument: 'initial_years' • The short explanation: Once we write a constructor (that requires parameters) in the superclass, we must now write constructors for our employee subclasses as well.
Modified Marketer class # A class to represent marketers. class Marketer(Employee): def __init__(years): super(Marketer, self). __init__(years) def advertise(): selfprint("Act now while supplies last!") def get_salary(): return super(Marketer, self). get_salary() + 10000. 0 • Exercise: Modify the Secretary subclass. • Secretaries' years of employment are not tracked. • They do not earn extra vacation for years worked.
Modified Secretary class # A class to represent secretaries. class Secretary(Employee): def __init__(self): super(Secretary, self). __init__(0) def take_dictation(self, text): print("Taking dictation of text: " + text) • Since Secretary doesn't require any parameters to its constructor, Legal. Secretary compiles without a constructor. • Its default constructor calls the Secretary constructor.
Inheritance and fields • Try to give lawyers $5000 for each year at the company: class Lawyer(Employee): . . . def get_salary(self): return super(Lawyer, self). get_salary() + 5000 * years. . . • Does not work; the error is the following: Attribute. Error: 'Lawyer' object has no attribute '_Employee__years' ^ • Private fields cannot be directly accessed from subclasses. • One reason: So that subclassing can't break encapsulation. • How can we get around this limitation?
Improved Employee code Add an accessor for any field needed by the subclass Employee: self. __years def __init__(self, initial_years): self. __years = initial_years def get_years(self): return self. __years. . . class Lawyer(Employee): def __init__(self, years): super(Lawyer, self). __init__(years) def get_salary(self): return super(Lawyer, self). get_salary() + 5000 * get_years(). . .
Revisiting Secretary • The Secretary class currently has a poor solution. • We set all Secretaries to 0 years because they do not get a vacation bonus for their service. • If we call get_years on a Secretary object, we'll always get 0. • This isn't a good solution; what if we wanted to give some other reward to all employees based on years of service? • Redesign our Employee class to allow for a better solution.
Improved Employee code • Let's separate the standard 10 vacation days from those that are awarded based on seniority. class Employee: def __init__(self, initial_years): self. __years = initial_years def get_vacation_days(self): return 10 + self. get_seniority_bonus() # vacation days given for each year in the company def get_seniority_bonus(self): return 2 * self. __years. . . • How does this help us improve the Secretary?
Improved Secretary code • Secretary can selectively override get_seniority_bonus; when get_vacation_days runs, it will use the new version. • Choosing a method at runtime is called dynamic binding. class Secretary(Employee): def __init__(self, years): super(Secretary, self). __init__(years) # Secretaries don't get a bonus for their years of service. def get_seniority_bonus(self): return 0 def take_dictation(self, text): print("Taking dictation of text: " + text)
- Slides: 68