Mutable Objects with Dataclass Classes
We can use make_dataclass
to create classes
that we can then use to create mutable objects. This is
a very handy way to store a collection of properties
in a single object.
# 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 confirm that sets do not work,
# since these objects are mutable:
try:
s = { dog1 }
except Exception as e:
print(e) # prints: unhashable type: 'Dog'
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():
return random.choice(['pink','yellow','lightGreen','gold','white'])
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=dot.color)
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)