Pushing Objects Off The Cliff



──────────────────
PyOhio
2011 July 30th
Brandon Craig Rhodes
──────────────────

Names and Objects

Easy for seasoned programmers

slide

How does Python work?

“All our variables are pointers.
We call them names.”


WHAT?! Indirection costs CLOCK CYCLES!

“Welcome to dynamic languages”

slide

How can we explain names and objects
to novice programmers
without
teaching them C or assembly language?

slide

How can we make these concepts
stand on their own?

slide

When we succeed, they understand:

slide

I have a modest proposal

slide

Imagine the edge of a tall cliff

slide

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

slide

Imagine the waves crashing below

slide

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~










 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

slide

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~








     rocks
     ↓           spray
 _  __     waves ↓      _  __
╱ ╲╱  ╲  ∵ ↓     ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

slide

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     ↑
     thus, danger








 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

slide

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     ↑
     you could fall!








 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

slide

Names are your friends.

They stand along the cliff's edge
and hold on to information for you!
The values they hold
are called objects

slide

a = 3

slide

        @       >>> a = 3
      ┌─┴─┐
      └┐a┌┤
       │ ││
~~~~~~~└─┘│~~~~~~~~~~~~~~~~~~
        ╔═╧═╗
        ║ 3 ║
        ╚═══╝







 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

The rules are simple

slide

        @       >>> a = 3
      ┌─┴─┐     >>> del a
      └┐a┌┤
       │ ││
~~~~~~~└─┘│~~~~~~~~~~~~~~~~~~
        ╔═╧═╗
        ║ 3 ║
        ╚═══╝







 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

slide

   @            >>> a = 3
 ┌─┴─┐…         >>> del a
 └┐a┌┤…
  │ ││…
~~└─┘~~~~~~~~~~~~~~~~~~~~~~~~




        ┊   ┊
        ╔═╧═╗
        ║ 3 ║
        ╚═══╝


 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

slide

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~







       (splash)

          ∵
 _  __   ∵ ∵            _  __
╱ ╲╱  ╲   V       ∵    ╱ ╲╱  ╲
╲_╱VVVVVVV VVVVVVVVVVVVVVV╲__╱

Reassignment

Because a name refers
to one object at a time,
an assignment statement will
make it drop any previous value

slide

        @       >>> a = 3
      ┌─┴─┐
      └┐a┌┤
       │ ││
~~~~~~~└─┘│~~~~~~~~~~~~~~~~~~
        ╔═╧═╗
        ║ 3 ║
        ╚═══╝







 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

slide

        @       >>> a = 3
      ┌─┴─┐     >>> a = a + 1
      └┐a┌┤
       │ ││
~~~~~~~└─┘│~~~~~~~~~~~~~~~~~~
        ╔═╧═╗
        ║ 4 ║
        ╚═══╝



        ┊   ┊
        ╔═╧═╗
        ║ 3 ║
        ╚═══╝
 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

slide

        @       >>> a = 3
      ┌─┴─┐     >>> a = a + 1
      └┐a┌┤
       │ ││
~~~~~~~└─┘│~~~~~~~~~~~~~~~~~~
        ╔═╧═╗
        ║ 4 ║
        ╚═══╝




       (splash)

          ∵
 _  __   ∵ ∵            _  __
╱ ╲╱  ╲   V       ∵    ╱ ╲╱  ╲
╲_╱VVVVVVV VVVVVVVVVVVVVVV╲__╱

slide

Assignment looks so innocent!
So positive, so affirming.
Yet it is implicitly destructive

For loops

The reassignment of the loop variable
that happens at the top of a for loop
is another example of this

slide

        @   >>> for a in range(5):
      ┌─┴─┐ ...     print a
      └┐a┌┤
       │ ││
~~~~~~~└─┘│~~~~~~~~~~~~~~~~~~
        ╔═╧═╗
        ║ 4 ║
        ╚═══╝
        ┊   ┊
        ╔═╧═╗
        ║ 3 ║
        ╚═══╝
        ┊   ┊
        ╔═╧═╗
        ║ 2 ║
 _  __  ╚═══╝           _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

slide

From here we can ascend
in rapid spiral through many
key Python concepts

Container Objects

Containers also dangle objects
above the crashing waves

slide

        @    >>> a = {3,4,5}
      ┌─┴─┐  >>> 4 in a
      └┐a┌┤  True
       │ ││
~~~~~~~└─┘│~~~~~~~~~~~~~~~~~~
     ╔════╧══════╗
     ║    set    ║
     ╚╤════╤════╤╝
    ╔═╧═╗╔═╧═╗╔═╧═╗
    ║ 3 ║║ 4 ║║ 5 ║
    ╚═══╝╚═══╝╚═══╝

       Sets are the simplest
     All the objects dangling
        are "in" the set
 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

slide

        @    >>> a = [3,4,5]
      ┌─┴─┐  >>> a[2]
      └┐a┌┤  5
       │ ││
~~~~~~~└─┘│~~~~~~~~~~~~~~~~~~
    ╔═════╧═══════╗
    ║    list     ║
    ╚0╤═══1╤═══2╤═╝
    ╔═╧═╗╔═╧═╗╔═╧═╗
    ║ 3 ║║ 4 ║║ 5 ║
    ╚═══╝╚═══╝╚═══╝


      Lists are more complex
      Each object lives at a
        particular index
 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

slide

        @    >>> a = {3: 9,
      ┌─┴─┐  ...      4: 16}
      └┐a┌┤
       │ ││
~~~~~~~└─┘│~~~~~~~~~~~~~~~~~~
      ╔═══╧══════╗
      ║   dict   ║
      ╚═╤═══════╤╝
   ╔═══╗│  ╔═══╗│
   ║ 3 ╟┤  ║ 4 ╟┤
   ╚══╔═╧═╗╚══╔═╧══╗
      ║ 9 ║   ║ 16 ║
      ╚═══╝   ╚════╝
Dictionaries are most complex
Two sub-objects: key AND value
 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

slide

Python class instances are just
containers that offer even more names
that you can assign and del
class Person(object):
    pass

slide

             @
           ┌─┴─┐  b = Person()
           ├┐b┌┘  b.name = a
           ││ │   b.age = 3
~~~~~~~~~~~│└─┘~~~~~~~~~~~~~~
       ╔═══╧════╗
       ║ Person ║
       ║name age║
       ╚══╪═══╪═╝
    ╔═════╧═╗╔╧══╗
    ║brandon║║ 3 ║
    ╚═══════╝╚═══╝



 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

slide

Have the beginner try this

class Person(object):
    pass

a = 'brandon'
b = Person()
b.name = a
b.age = 3

slide

      @      @    a = 'brandon'
    ┌─┴─┐  ┌─┴─┐  b = Person()
    ├┐a┌┘  ├┐b┌┘  b.name = a
    ││ │   ││ │   b.age = 3
~~~~│└─┘~~~│└─┘~~~~~~~~~~~~~~
    │  ╔═══╧════╗
    │  ║ Person ║
    │  ║name age║
    │  ╚══╪═══╪═╝
   ╔╧═════╧╗╔═╧═╗
   ║brandon║║ 3 ║
   ╚═══════╝╚═══╝




 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

slide

      @      @    a = 'brandon'
    ┌─┴─┐  ┌─┴─┐  b = Person()
    ├┐a┌┘  ├┐b┌┘  b.name = a
    ││ │   ││ │   b.age = 3
~~~~│└─┘~~~│└─┘~~~~~~~~~~~~~~
    │  ╔═══╧════╗
    │  ║ Person ║
    │  ║name age║
    │  ╚══╪═══╪═╝
   ╔╧═════╧╗╔═╧═╗
   ║brandon║║ 3 ║
   ╚═══════╝╚═══╝


“What happens if we ‘del b’?”
 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

slide

      @       @
    ┌─┴─┐   ┌─┴─┐     del b
    ├┐a┌┘   ├┐b┌┘
    ││ │     │ │
~~~~│└─┘~~~~~└─┘~~~~~~~~~~~~~~
   ╔╧══════╗
   ║brandon║
   ╚═══════╝

       ┊        ┊
       ╔═══╧════╗
       ║ Person ║
       ║name age║
       ╚══════╪═╝
            ╔═╧═╗
 _  __      ║ 3 ║       _  __
╱ ╲╱  ╲  ∵  ╚═══╝  ∵   ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

Aliasing

This issue usually first arises
when you introduce container objects,
because containers are mutable

slide

Beginners get confused

>>> a = [3, 4, 5]
>>> b = a
>>> b           # no surprise
[3, 4, 5]
>>> b.append(6)
>>> b           # no surprise
[3, 4, 5, 6]

But, what is the value of a?

The “Name-as-Container Fallacy”

If you think of values
as being held inside of names
then you are doomed

slide

And names are not the only problem
If you think of values as inside
of sets and lists and dicts, then: doomed

slide

    @        >>> a = [3,4,5]
  ┌─┴─┐
  └┐a┌┤
   │ ││
~~~└─┘│~~~~~~~~~~~~~~~~~~~~~~
    ╔═╧═══════════╗
    ║     list    ║
    ╚0╤═══1╤═══2╤═╝
    ╔═╧═╗╔═╧═╗╔═╧═╗
    ║ 3 ║║ 4 ║║ 5 ║
    ╚═══╝╚═══╝╚═══╝




 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

slide

    @    @   >>> a = [3,4,5]
  ┌─┴─┐┌─┴─┐ >>> b = a
  └┐a┌┤└┐b┌┤
   │ ││ │ ││
~~~└─┘│~└─┘│~~~~~~~~~~~~~~~~~
    ╔═╧════╧══════╗
    ║     list    ║
    ╚0╤═══1╤═══2╤═╝
    ╔═╧═╗╔═╧═╗╔═╧═╗
    ║ 3 ║║ 4 ║║ 5 ║
    ╚═══╝╚═══╝╚═══╝




 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

slide

    @    @   >>> a = [3,4,5]
  ┌─┴─┐┌─┴─┐ >>> b = a
  └┐a┌┤└┐b┌┤ >>> b.append(6)
   │ ││ │ ││
~~~└─┘│~└─┘│~~~~~~~~~~~~~~~~~
    ╔═╧════╧═══════════╗
    ║       list       ║
    ╚0╤═══1╤═══2╤═══3╤═╝
    ╔═╧═╗╔═╧═╗╔═╧═╗╔═╧═╗
    ║ 3 ║║ 4 ║║ 5 ║║ 6 ║
    ╚═══╝╚═══╝╚═══╝╚═══╝


        “What is a?”

 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

slide

The picture alone
answers the question

slide

Encourage beginners
to draw pictures of their own
for each new situation
that confuses them

Aliasing, continued

Actually not usually first
encountered in a = b statements

slide

Instead, the beginner meets aliasing
when they first pass a container
as a function parameter

slide

def isbigger(n, m):
    "Is `n` > all values in list `m`?"
    m.append(n)
    m.sort()
    return m[-1] == n

>>> a = [6, 3, 4]
>>> isbigger(8, a)
True
>>> a        # ack! it changed!
[3, 4, 6, 8]

Parameters

A function call
performs one assignment
for every parameter

slide

def isbigger(n, m):
    …

isbigger(8, a)
— which means —
def isbigger(…):
    n = 8
    m = a
    …

slide

    @
  ┌─┴─┐
  └┐a┌┤
   │ ││    @          @
~~~└─┘│~ ┌─┴─┐ ~~~~ ┌─┴─┐ ~~ global namespace
      │  └┐m┌┤      └┐n┌┤
      │   │ ││       │ ││
      │ ~~└─┘│~~~~~~~└─┘│~~~ f() namespace
    ╔═╧══════╧════╗   ╔═╧═╗
    ║     list    ║   ║ 8 ║
    ╚0╤═══1╤═══2╤═╝   ╚═══╝
    ╔═╧═╗╔═╧═╗╔═╧═╗
    ║ 6 ║║ 3 ║║ 4 ║
    ╚═══╝╚═══╝╚═══╝

 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

Intermediate Results

a = 80 * 24 - 1

slide

      @       @
    ┌─┴─┐   ┌─┴─┐  a = 80 * 24 - 1
    ├┐P┌┤   ├┐a┌┘
    ││y││    │ │
~~~~│└─┘│~~~~└─┘~~~~~~~~~~~~~~
  ╔═╧╗╔═╧╗
  ║80║║24║
  ╚══╝╚══╝

slide

      @       @
    ┌─┴─┐   ┌─┴─┐  a = 80 * 24 - 1
    ├┐P┌┤   ├┐a┌┘
    ││y││    │ │
~~~~│└─┘│~~~~└─┘~~~~~~~~~~~~~~
 ╔══╧═╗╔╧╗
 ║1920║║1║
 ╚════╝╚═╝

slide

      @       @
    ┌─┴─┐   ┌─┴─┐  a = 80 * 24 - 1
    ├┐P┌┤ → ├┐a┌┘
     │y│    ││ │
~~~~~└─┘~~~~│└─┘~~~~~~~~~~~~~~
         ╔══╧═╗
         ║1919║
         ╚════╝

slide

But when do intermediate results matter?

Lazy Evaluation

>>> for a in range(99):
...    …
Explain to the student that
the expression on the right is:

slide

You can easily sketch for the student
why the first of these commands
uses more memory:
>>> for a in range(100000):
...     read_datum()
>>> for a in xrange(100000):
...     read_datum()

slide

>>> m = range(100000):
>>> m
[0, 1, 2, 3, 4, ..., 99999]
>>>
>>> m = xrange(100000)
>>> m
xrange(100000)
>>> mi = iter(m)
>>> mi.next()
0
>>> mi.next()
1

Import Statements

slide

only two points here

slide

import sys

means

sys = <the module named "sys">

slide

from sys import version

means

version = <the module named "sys">.version

slide

from sys import version as python_ver

means

python_ver = <the module named "sys">.version

slide

Probably better not to try to draw
a picture for your student at this point!
Would each module be its own cliff?
Then how would you illustrate
that the cliff itself needs to be
“held up” by the references from other modules
and the reference in sys.modules?

“Throwing Away the Ladder”

A name can keep a whole namespace alive
Module creation and deletion
might better be explained
with arm-waving

Scopes

Each function currently underway
maintains a scope full of names
You can think visually about
each scope as a ledge or platform
beneath the global scope

slide

                def f(…):
                    return g(…)
    @
  ┌─┴─┐         f(…)
  └┐ ┌┤   @
   │ │  ┌─┴─┐
~~~└─┘~~└┐ ┌┤~~ @ ~~~~ global scope
         │ │  ┌─┴─┐
        ~└─┘~~└┐ ┌┤~~~ f() scope
               │ │
             ~~└─┘~~~~ g() scope


 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

Lexical Scoping

slide

Warning: the cliff metaphor
might actually make lexical scoping
difficult to think about

slide

Why?
Because the cliff and ledges below
make stack frames so vivid that
a student could make the mistake
of assuming that functions can look
up and see the names above them

slide

This may be especially likely
if the first example of lexical scoping
that a programmer sees would also
work with dynamic scoping

slide

def isbigger(m, n):
    "Is `n` > all values in `m`?"
    m.append(myvalue)
    m.sort()
    return m[-1] == n

>>> a = [6, 3, 4]
>>> isbigger(a, 8)

slide

A novice might discover
that they can refer to a
from inside of f()

slide

def isbigger(m, n):
    "Is `n` > all values in `m`?"
    m.append(myvalue)
    m.sort()
    print m is a   # this happens to work
    return m[-1] == n

>>> a = [6, 3, 4]
>>> isbigger(a, 8)

slide

             f() can see `a`
    @        NOT because `a` is above
  ┌─┴─┐      BUT because `a` surrounds its code
  └┐a┌┤
   │ ││    @          @
~~~└─┘│~ ┌─┴─┐ ~~~~ ┌─┴─┐ ~~ global scope
      │  └┐m┌┤      └┐n┌┤
      │   │ ││       │ ││
      │ ~~└─┘│~~~~~~~└─┘│~~~ f() scope
    ╔═╧══════╧════╗   ╔═╧═╗
    ║     list    ║   ║ 8 ║
    ╚0╤═══1╤═══2╤═╝   ╚═══╝
    ╔═╧═╗╔═╧═╗╔═╧═╗
    ║ 6 ║║ 3 ║║ 4 ║
    ╚═══╝╚═══╝╚═══╝
 _  __                  _  __
╱ ╲╱  ╲  ∵       ∵     ╱ ╲╱  ╲
╲_╱VVVVVVVVVVVVVVVVVVVVVVV╲__╱

slide

So the cliff image
can here become dangerous

Side Issues

slide

Why do people keep complicating
the first week of Python with this stuff?

slide

Small integers are cached

(“The number 3 never actually
falls into the ocean”)
>>> (2 + 1) is (2 + 1)
True
>>> (1000 + 1) is (1000 + 1)
False
>>> 1.0 + 2.0 is 1.0 + 2.0
False

slide

>>> a = 1.0 + 2.0
>>> a
3.0
>>> b = 1.0 + 2.0
>>> b
3.0
>>> a == b
True
>>> id(a)
140202964
>>> id(b)
140202916
>>> a is b
False

slide

Interned strings are cached

(Which includes every string
that appears as a literal in
your Python program)
>>> 'my string' is 'my string'
True
>>> ('my string' + 'a') is ('my string' + 'a')
False
>>> s1 = intern('my string' + 'a')
>>> s2 = intern('my string' + 'a')
>>> s1 is s2
True

slide

Python returns the same data structure
over and over again for immutable literals
>>> (1, 2, 3) is (1, 2, 3)
True
>>> [1, 2, 3] is [1, 2, 3]
False

Q: Do beginners need is?

A: Yes!

None: there can be only one

>>> None is None
True

Why should beginners use is?

slide

Cyclic garbage collection

Do beginners really need to know?

slide

The objects still get deleted
eventually — so The Rules as explained
earlier still work, right?

Certainly not important until 4:30pm

Do tell beginners:

Not all objects die right
when they hit the water;
some take time to drown
So close() files,
use with clauses,
et cetera

slide

Keep explanations simple
until people need the next step

In conclusion

Thank you!

I hope these thoughts
help some of you teach Python
in your own communities
and around the world
Thanks!