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!