Skip to content

Lab 23: The Face Module — Decomposition and Abstraction

Open Lab 19 and Lab 22 side by side. Both define their own draw_eye(), their own eyebrow function, their own mouth logic — and the definitions are nearly identical. Every lab that draws a face has been carrying its own copy of the same code.

This lab adds no new drawing trick at all. It moves those copies into face.py, one shared file every lab from here forward imports instead. That move has two names in computer science: decomposition, breaking a problem into parts small enough to name, and abstraction, hiding how a part works behind that name.

Sample Program Code

Three complete expressions, in about nine lines, because face.eyes() already knows what an eye is:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# Lab 23: The Face Module -- Decomposition and Abstraction
#
# Open lab 19 and lab 22 side by side. Both of them define draw_eye(),
# draw_eyebrow(), and a mouth function, and both definitions are nearly
# identical. Every lab that draws a face has been carrying its own copy.
#
# This lab does not add a single new drawing trick. It moves those copies
# into face.py, one file that every lab from here on imports. That move
# has a name in computer science: DECOMPOSITION, breaking a problem into
# parts small enough to name, and ABSTRACTION, hiding how a part works
# behind that name.
#
# Count the lines. Lab 19 spends about 70 lines defining face parts before
# it draws anything. Below, three complete expressions take nine lines,
# because face.eyes() already knows what an eye is.
#
# Notice what is NOT in the show() function below: a call to show().
# There isn't one on this display. face.clear() paints black, the drawing
# calls go straight to the glass, and that is the whole cycle.

import face
from utime import sleep

# The names below come from face.py. Nothing is redefined here -- if you
# ever want to change how an eyebrow is drawn, there is now exactly one
# place to change it, and every lab gets the fix.


def happy():
    face.eyes(24, 24)
    face.eyebrows(0, 0, lift=5)
    face.mouth(face.SMILE, 50, 24)


def sad():
    face.eyes(22, 22)
    face.eyebrows(-7, -7, lift=0)
    face.mouth(face.FROWN, 40, 20)


def surprised():
    face.eyes(32, 32)
    face.eyebrows(0, 0, lift=14)
    face.mouth(face.OPEN, 20, 26)


# A list of (name, function) pairs, the same shape lab 18 used for modes.
EXPRESSIONS = (
    ("Happy", happy),
    ("Sad", sad),
    ("Surprised", surprised),
)


def show(name, draw):
    """The one place that knows the clear-draw-label sequence. Every
    expression above trusts this function to handle it."""
    face.clear()
    draw()
    face.label(name)


while True:
    for name, draw in EXPRESSIONS:
        show(name, draw)
        sleep(1.5)

# Things to try:
#
# 1. Add a fourth expression. You should not need to write a single
#    shapes.ellipse() call -- only face.eyes(), face.eyebrows(), and
#    face.mouth() with different numbers.
#
# 2. Open face.py and change EYE_SPACING from 48 to 60. Run this lab
#    again. One edit moved the eyes on every expression at once. That is
#    what abstraction buys you. (Go too far and the eyes start hitting the
#    bezel, which is the round screen reminding you it has opinions.)
#
# 3. Break it on purpose: change face.EYE_Y to 220 and run again. Because
#    every expression shares one definition, every expression breaks the
#    same way -- which also makes the bug easy to find. Change it back.
#
# 4. Compare this lab's file size to lab 19's. Same three expressions,
#    a fifth of the code, and every one of the numbers you can still see
#    is a number about FEELING rather than about pixels.

Here's the first expression:

Simulated output of 23-face-module.py

Count the Lines

Lab 19 spends roughly 55 lines defining eyes, eyebrows, and mouths before it draws a single expression. This lab does the same three expressions in about a fifth of the code — and every number you can still see in it is a number about feeling (how wide, how lifted, how curved), not a number about pixels. That's what abstraction buys: change face.EYE_SPACING once in face.py and every expression in every lab that imports it moves its eyes together.

There's still no show() anywhere in this file, and there never will be again — face.clear() paints black, the drawing calls go straight to the glass, and that's the entire cycle on a display with no buffer to flush.

Worth Thinking About

Pixel Once you've named the parts — eyes, eyebrows, a mouth — you stop thinking in pixels and start thinking in faces. That's the whole trick behind every emotion this kit can draw.