- 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)
- 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).
- 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!)
- Advantages of Classes and Methods
- 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)