The Face Module
Open the emotion menu from an earlier lesson and the live-tuning program side by side. Both of them define a function that draws an eye. Both define one that draws an eyebrow. Both define a mouth. The definitions are nearly identical, and every program you have written that draws a face has been carrying its own private copy.
That duplication is about to become a superpower, because getting rid of it is the single highest-leverage move in programming.
Time to clean up the workshop
You already know how to draw every part of my face. This lesson is about writing it down once, in one place, so you never have to write it again. Every pixel tells a story!
Two Ideas With Real Names
This lesson does not teach a single new drawing command. It teaches two ways of thinking that computer scientists gave names to a long time ago, because they matter that much.
Decomposition means breaking a problem into parts small enough to name. A face is not one thing you draw — it is eyes, plus eyebrows, plus a mouth. Once each part has a name, you can work on one part without holding the other two in your head.
Abstraction means hiding how a part works behind what it is called. After this lesson
you will write face.eyes(10, 10) and stop thinking about ellipses entirely. The ellipse is
still there; you just do not have to look at it anymore.
| Idea | The question it answers | What it looks like in code |
|---|---|---|
| Decomposition | What are the pieces? | Separate functions for eyes, eyebrows, and mouth |
| Abstraction | What do I call this piece, and what can I forget? | face.eyes(10, 10) instead of two ellipse() calls |
Where the Face Facts Live Now
Your kit already has one shared file, config.py, and it holds the hardware facts — which
pin the display's clock is on, how many pixels wide the screen is. Every program imports it so
those numbers only exist in one place.
face.py does exactly the same job for the face facts: how far apart the eyes sit, how
long an eyebrow is, how to draw each style of mouth.
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 | |
Look closely at that code and notice what is not new. Those are the same two ellipse()
calls you wrote in the face-layout lesson, in the same order, with the same numbers. Nothing
was invented. It was only moved.
Moved, Not Rewritten
Refactoring means changing how code is organized without changing what it does. If the face looks different after this lesson, something went wrong — a clean refactor is invisible from the outside.
One Mouth Function Instead of Five
Faces need more than one kind of mouth. A smile is an arc curving up, a frown is the same arc
flipped, an open mouth is a filled ellipse, and a smirk is a flat line with one corner curled.
Rather than five separate names to remember, face.py gives you one function and a style
name that picks the shape.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
That single function is what makes the next lesson possible. Because the mouth style is now a value you can pass around, an entire expression can be written as a row of data instead of a block of code.
Three Expressions in Nine Lines
Here is the payoff. The emotion menu spends about 55 lines defining face parts before it draws
anything at all. With face.py doing that work, three complete expressions take nine lines.
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 | |
Notice that show() is the only place in the whole program that knows the clear-draw-label-show
sequence. Every expression trusts it to get that right, which means there is exactly one place
to fix if it ever gets it wrong.
The Test That Proves It Worked
Open face.py and change EYE_SPACING from 26 to 34. Run the program again. One edit just moved the eyes on every expression at once — that is abstraction paying you back.
The Trade You Are Making
Abstraction is not free, and pretending otherwise would be dishonest. When you hide the
ellipse() calls behind face.eyes(), you also hide them from yourself. A beginner reading
your program can no longer see how an eye is drawn without opening a second file.
That trade is almost always worth it, and here is the rule of thumb: hide a detail once you have written it correctly three times. Before then, writing it out teaches you something. After then, writing it out just gives you three places to make the same typo.
Before face.py |
After face.py |
|---|---|
Every program has its own copy of draw_eye() |
One copy, in one file |
| Fixing an eyebrow means editing 8 programs | Fixing an eyebrow means editing 1 file |
You can see the ellipse() call right there |
You have to open face.py to see it |
| New expression: copy 55 lines, then edit | New expression: 3 lines |
Things to Try
- Add a fourth expression. You should not need to write a single
oled.ellipse()call — onlyface.eyes(),face.eyebrows(), andface.mouth()with different numbers. - Break it on purpose. Change
face.EYE_Yto 60 and run again. Because every expression shares one definition, every expression breaks the same way — which is exactly what makes the bug easy to find. Change it back. - Go back and shrink an old program. Rewrite the winking face using only
face.pyparts, and count how many lines disappear. - Find the third copy. Look through your earlier programs for any other block of code that
appears in three or more of them, and move it into
face.pytoo.
One file to rule them all
Your face parts now live in one place, which means every program you write from here on starts with a face already built. Great expression!
References
- Emotion Types — the seven expressions whose duplicated drawing code this lesson consolidates
- Basic Face Layouts — where the eye spacing and mouth position numbers came from
- Ellipse — the quadrant fill codes that
face.mouth()now hides behind a style name - MicroPython Framebuf Documentation — the drawing commands
face.pywraps