- 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).
- Namespaces
We can use namespaces to create mutable objects. This is
a very handy way to store a collection of properties (or "fields")
in a single object. (Note: at this time, brython does not
support namespaces, but standard python of course does.)
# Don't forget this import:
from types import SimpleNamespace
# Now we can create new object representing dogs:
dog1 = SimpleNamespace(name='Dino', age=10, breed='shepherd')
print(dog1) # prints: namespace(age=10, breed='shepherd', name='Dino')
print(dog1.name) # prints: Dino
# Next, let's show that this is in fact mutable:
dog1.name = 'Fred'
print(dog1) # prints: namespace(age=10, breed='shepherd', name='Fred')
print(dog1.name) # prints: Fred
# Now let's show that == works properly:
dog2 = SimpleNamespace(name='Spot', age=12, breed='poodle')
dog3 = SimpleNamespace(name='Fred', age=10, breed='shepherd')
print(dog1 == dog2) # prints: False
print(dog1 == dog3) # prints: True
# Finally, let's see what the type of a dog object is:
print(type(dog1)) # prints <class 'types.SimpleNamespace'&rt; (yuck)
- Dataclasses
A Dataclass is like a SimpleNamespace, with these improvements:
- It has required fields.
- It has a custom type.
Let's revisit the example above, this time using a Dataclass:
# Don't forget this import:
from dataclasses import make_dataclass
# Now we can create a new class named Dog where
# instances (individual dogs) have 3 properties
# (fields): name, age, and breed
Dog = make_dataclass('Dog', ['name', 'age', 'breed'])
# Now we can create an instances of the Dog class:
dog1 = Dog(name='Dino', age=10, breed='shepherd')
print(dog1) # prints: Dog(name='Dino', age=10, breed='shepherd')
print(dog1.name) # prints: Dino
# Next, let's show that this is in fact mutable:
dog1.name = 'Fred'
print(dog1) # prints: Dog(name='Fred', age=10, breed='shepherd')
print(dog1.name) # prints: Fred
# Now let's show that the fields are in fact required:
try:
dog2 = Dog(name='Dino', age=10)
except Exception as e:
print(e) # prints: missing 1 required positional argument: 'breed'
# Now let's show that == works properly:
dog2 = Dog(name='Spot', age=12, breed='poodle')
dog3 = Dog(name='Fred', age=10, breed='shepherd')
print(dog1 == dog2) # prints: False
print(dog1 == dog3) # prints: True
# Finally, let's see what the type of a dog object is:
print(type(dog1)) # prints <class 'types.Dog'&rt; (better)
print(isinstance(dog1, Dog)) # prints True (great!)
- Updated Example: Adding and Deleting Shapes
Here is an improved version of the
adding-and-deleting-shapes example from
here.
Instead of representing dots as (cx, cy) pairs,
here we will create a Dot class using make make_dataclass.
Now, each
instance will use dot.cx, dot.cy, dot.r, dot.counter, and dot.color.
from cmu_112_graphics import *
import random
from dataclasses import make_dataclass
Dot = make_dataclass('Dot', ['cx', 'cy', 'r', 'counter', 'color'])
def appStarted(app):
app.dots = [ ]
def pointIsInDot(x, y, dot):
return (((dot.cx - x)**2 + (dot.cy - y)**2)**0.5 <= dot.r)
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 pointIsInDot(event.x, event.y, dot):
dot.counter += 1
dot.color = getRandomColor()
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 redrawAll(app, canvas):
# draw the dots and their counters
for dot in app.dots:
canvas.create_oval(dot.cx-dot.r, dot.cy-dot.r,
dot.cx+dot.r, dot.cy+dot.r,
fill='white', outline=dot.color, width=15)
canvas.create_text(dot.cx, dot.cy, text=str(dot.counter))
# draw the text
canvas.create_text(app.width/2, 20,
text='Example: Adding and Deleting Shapes')
canvas.create_text(app.width/2, 40,
text='Mouse clicks outside dots create new dots')
canvas.create_text(app.width/2, 60,
text='Mouse clicks inside dots increase their counter')
canvas.create_text(app.width/2, 70,
text='and randomize their color.')
canvas.create_text(app.width/2, 90,
text='Pressing "d" deletes circles')
runApp(width=400, height=400)
- Objects and Aliases
# Objects are mutable so aliases change!
# Run this with the visualizer to make it clear!
from types import SimpleNamespace
import copy
dog1 = SimpleNamespace(name='Dino', age=10, breed='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)