CMU 15-112: Fundamentals of Programming and Computer Science
Class Notes: Object-Oriented Programming (OOP), Part 1
Using Objects and Writing Classes
- Methods vs Functions
- Classes and Instances
- Objects and Object-Oriented Programming (OOP)
- Writing Classes
- Writing Constructors
- Writing Methods
- Advantages of Classes and Methods
- Objects and Aliases
- Example: Animations with OOP
- Methods vs Functions
We call methods using s.f() rather than f(s):s = 'This could be any string!' print(len(s)) # len is a function print(s.upper()) # upper is a string method, called using the . notation # we say that we "call the method upper on the string s" print(s.replace('could', 'may')) # some methods take additional arguments
See how we get different errors for improperly calling methods vs functions:n = 123 print(len(n)) # TypeError: object of type 'int' has no len() # This means that len() cannot work properly with int's n = 123 print(n.upper()) # AttributeError: 'int' object has no attribute 'upper' # This means that there is no method upper() for int's - Classes and Instances
- Classes are also called "Types" in Python.
- For example, these are classes: int, float, str, bool
- Instances are values of a given class or type.
- For example: 'abc' is a str instance (also called a string)
- Classes are also called "Types" in Python.
- Objects and Object-Oriented Programming (OOP)
- Every value in Python is an Object.
- Every instance is an object, and its type is some class.
- Every class is an object, too (its type is type!).
- That is why we call this Object-Oriented Programming
- We are using objects only a little bit now.
- Soon we will write our own classes.
- Then we will add some sophistication to how we write and use classes and objects.
- Even so, because we are using objects now, we are already using Object-Oriented Programming (OOP).
- Every value in Python is an Object.
- Writing Classes
# Create our own class: class Dog: # a class must have a body, even if it does nothing, so we will # use 'pass' for now... pass # Create instances of our class: d1 = Dog() d2 = Dog() # Verify the type of these instances: print(type(d1)) # Dog (actually, class '__main__.Dog') print(isinstance(d2, Dog)) # True # Set and get properties (aka 'fields' or 'attributes') of these instances: d1.name = 'Dot' d1.age = 4 d2.name = 'Elf' d2.age = 3 print(d1.name, d1.age) # Dot 4 print(d2.name, d2.age) # Elf 3
- Writing Constructors
- Constructors let us pre-load our new instances with properties.
- This lets us write code like so:
d1 = Dog('fred', 4) # now d1 is a Dog instance with name 'fred' and age 4
- We would like to write our constructor like this:
def constructor(dog, name, age): # pre-load the dog instance with the given name and age: dog.name = name dog.age = age
-
Unfortunately, Python does not use 'constructor' as the constructor name.
Instead, it uses '__init__' (sorry about that), like so:
def __init__(dog, name, age): # pre-load the dog instance with the given name and age: dog.name = name dog.age = age
- Also, unfortunately, while we could name the instance 'dog' like we did,
standard convention requires that we name it 'self' (sorry again), like so:
def __init__(self, name, age): # pre-load the dog instance with the given name and age: self.name = name self.age = age
- Finally, we place this method inside the class and we have a constructor
that we can use, like so:
class Dog: def __init__(self, name, age): # pre-load the dog instance with the given name and age: self.name = name self.age = age # Create instances of our class, using our new constructor d1 = Dog('Dot', 4) d2 = Dog('Elf', 3) print(d1.name, d1.age) # Dot 4 print(d2.name, d2.age) # Elf 3
- Writing Methods
-
Start with a function:
class Dog: def __init__(self, name, age): self.name = name self.age = age # Here is a function we will turn into a method: def sayHi(dog): print(f'Hi, my name is {dog.name} and I am {dog.age} years old!') d1 = Dog('Dot', 4) d2 = Dog('Elf', 3) sayHi(d1) # Hi, my name is Dot and I am 4 years old! sayHi(d2) # Hi, my name is Elf and I am 3 years old!
-
Turn the function into a method, and the function call into a method call, like this:
class Dog: def __init__(self, name, age): self.name = name self.age = age # Now it is a method (simply by indenting it inside the class!) def sayHi(dog): print(f'Hi, my name is {dog.name} and I am {dog.age} years old!') d1 = Dog('Dot', 4) d2 = Dog('Elf', 3) # Notice how we change the function calls into method calls: d1.sayHi() # Hi, my name is Dot and I am 4 years old! d2.sayHi() # Hi, my name is Elf and I am 3 years old!
-
Finally, use
self
, as convention requires:class Dog: def __init__(self, name, age): self.name = name self.age = age # Now we are using self, as convention requires: def sayHi(self): print(f'Hi, my name is {self.name} and I am {self.age} years old!') d1 = Dog('Dot', 4) d2 = Dog('Elf', 3) # Notice how we change the function calls into method calls: d1.sayHi() # Hi, my name is Dot and I am 4 years old! d2.sayHi() # Hi, my name is Elf and I am 3 years old! -
Methods can take additional parameters, like so:
class Dog: def __init__(self, name, age): self.name = name self.age = age # This method takes a second parameter -- times def bark(self, times): print(f'{self.name} says: {"woof!" * times}') d = Dog('Dot', 4) d.bark(1) # Dot says: woof! d.bark(4) # Dot says: woof!woof!woof!woof!
-
Methods can also set properties, like so:
class Dog: def __init__(self, name, age): self.name = name self.age = age self.woofCount = 0 # we initialize the property in the constructor! def bark(self, times): # Then we can set and get the property in this method self.woofCount += times print(f'{self.name} says: {"woof!" * times} ({self.woofCount} woofs!)') d = Dog('Dot', 4) d.bark(1) # Dot says: woof! (1 woofs!) d.bark(4) # Dot says: woof!woof!woof!woof! (5 woofs!)
-
Start with a function:
- Advantages of Classes and Methods
- Encapsulation
- Organizes code
A class includes the data and methods for that class. - Promotes intuitive design
Well-designed classes should be intuitive, so the data and methods in the class match commonsense expectations. - Restricts access
len
is a function, so we can calllen(True)
(which crashes)upper
is a method on strings but not booleans, so we cannot even callTrue.upper()
- Organizes code
- Polymorphism
Polymorphism: the same method name can run different code based on type, like so:class Dog: def speak(self): print('woof!') class Cat: def speak(self): print('meow!') for animal in [ Dog(), Cat() ]: animal.speak() # same method name, but one woofs and one meows!
- Encapsulation
- Objects and Aliases
# Objects are mutable so aliases change! # Run this with the visualizer to make it clear! import copy class Dog: def __init__(self, name, age, breed): self.name = name self.age = age self.breed = breed dog1 = Dog('Dino', 10, 'shepherd') dog2 = dog1 # this is an alias dog3 = copy.copy(dog1) # this is a copy, not an alias dog1.name = 'Spot' print(dog2.name) # Spot (the alias changed, since it is the same object) print(dog3.name) # Dino (the copy did not change, since it is a different object) - Example: Animations with OOP
Here is an updated version of the adding-and-deleting dots demo from here. This version adds methods to the Dot class so each dot is responsible for its own drawing and mouse handling.from cmu_112_graphics import * import random class Dot: def __init__(self, cx, cy, r, counter, color): self.cx = cx self.cy = cy self.r = r self.counter = counter self.color = color def redraw(self, app, canvas): # Only redraw this dot canvas.create_oval(self.cx-self.r, self.cy-self.r, self.cx+self.r, self.cy+self.r, fill='white', outline=self.color, width=15) canvas.create_text(self.cx, self.cy, text=str(self.counter), fill='black') def containsPoint(self, x, y): return (((self.cx - x)**2 + (self.cy - y)**2)**0.5 <= self.r) def mousePressed(self, event): # We are guaranteed (event.x, event.y) is in this dot self.counter += 1 self.color = getRandomColor() def timerFired(self, app): self.counter += 1 def appStarted(app): app.dots = [ ] app.timerDelay = 1000 # once per second def getRandomColor(): colors = ['red', 'orange', 'yellow', 'green', 'blue', 'pink', 'lightGreen', 'gold', 'magenta', 'maroon', 'salmon', 'cyan', 'brown', 'orchid', 'purple'] return random.choice(colors) def mousePressed(app, event): # go through dots in reverse order so that # we find the topmost dot that intersects for dot in reversed(app.dots): if dot.containsPoint(event.x, event.y): dot.mousePressed(event) return # mouse click was not in any dot, so create a new dot newDot = Dot(cx=event.x, cy=event.y, r=20, counter=0, color='cyan') app.dots.append(newDot) def keyPressed(app, event): if (event.key == 'd'): if (len(app.dots) > 0): app.dots.pop(0) else: print('No more dots to delete!') def timerFired(app): for dot in app.dots: dot.timerFired(app) def redrawAll(app, canvas): for dot in app.dots: dot.redraw(app, canvas) # draw the text canvas.create_text(app.width/2, 20, text='Example: Adding and Deleting Shapes', fill='black') canvas.create_text(app.width/2, 40, text='Mouse clicks outside dots create new dots', fill='black') canvas.create_text(app.width/2, 60, text='Mouse clicks inside dots increase their counter', fill='black') canvas.create_text(app.width/2, 70, text='and randomize their color.', fill='black') canvas.create_text(app.width/2, 90, text='Pressing "d" deletes circles', fill='black') runApp(width=400, height=400)