CS 61A

Time: Wed 10/4/17 3 pm

Mutable Functions

Similar to the elementary data abstraction, just that we can change values in the encapsulation now somehow.

What is nonlocal? And why is it useful in this case? Can we just bind some variable in the parent frame to some new values without using this keyword? Do we need to have something in a nonlocal frame with the name beforehand?

Here's something really bad to do as an example:

def make_bike():
  # The color of the bike
  color = "red"

  def get_color():
    return color

  def change_color(new_color):
    # Change the color of the bike
    color = new_color

    # However now you thought you forgot to use nonlocal
    # But note that it doesn't hoist
    nonlocal color # Invalid syntax

    # Python will just give up with your code :(
    # ... and literally halt here

  return [get_color, change_color]

[get_color, change_color] = make_bike() # Make a bike
change_color("blue") # Make it blue, don't like it being red

And a slightly better example (in which there is no syntax error, but logically incorrect):

def make_factory():
  # The color of the bike by default
  factory_color = "green"

  def make_bike():
    # Use the factory color as default
    color = factory_color

    def get_color():
      return color

    def change_color(new_color):
      # Someone hacked into the script and changed the following lines
      nonlocal factory_color
      factory_color = new_color

    return [get_color, change_color]

  return make_bike

make_bike = make_factory()

# Make a new bike
[get_0_color, change_0_color] = make_bike()

# Inspect its color
get_0_color() # green

# And now we want to repaint the bike
change_0_color("blue")

# And when we try to inspect the bike again...
get_0_color() # green
# Uhm it is still green :/

# Make another bike
[get_1_color, change_1_color] = make_bike()

# Try to inspect its color... which should be green
get_1_color() # blue
# Oh well, the factory settings are modified... uh oh

Can we still implement something with a same interface that does exactly the same without nonlocal, using list, dict etc.?

Some extra confusions

There's something called global; however, Python doesn't act as picky as it does to nonlocal. When it sees something undefined paired up with global, it just doesn't complain. More tears...

Can we use global rather than nonlocal for everything? Please don't.

Objects & Inheritance

Primitively, they are just syntactical sugars for data abstraction. What are methods, bound methods and static methods?

A quick example of each:

class balloon():

  @staticmethod
  def describe():
    print("Balloon!")

  def grow(self):
    print("The balloon grew larger...")

b = balloon()

balloon            # <class '__main__.balloon'>
b                  # <__main__.balloon object at ...>

# Static method

balloon.describe   # <function balloon.describe at ...>
balloon.describe() # Balloon!

b.describe         # <function balloon.describe at ...>
b.describe()       # Balloon!

# Unbound method

balloon.grow       # <function balloon.grow at ...>
balloon.grow(b)    # The balloon grew larger...

# Bound method

b.grow             # <bound method balloon.grow of <__main__.balloon object at ...>>
b.grow()           # The balloon grew larger...

Class variables and instance variables are different. Class variables are shared between instances; however, instance variables are just different from instance to instance.

class sandbag():
  weight = 10

# Someone goes to buy a sandbag
s = sandbag()

# He weighs the sandbag...
s.weight # 10
# And finds that it is of 10 kilos

# And another person comes into the story and does something terrible
sandbag.weight = 5

# The next day, whoever that bought the sandbag tries to re-weigh it
s.weight # 5
# And somehow... It became lighter :(

How do you make custom constructors for a class?

There are some goodies and complications that come with objects too. Multiple inheritance is a beast--check out the diamond problem--and it can bring ambiguities in programming. There are ways to probe into the method resolution order.

What's super, and why do we use it?

Misc about Trees from Last Week

They are naturally sustainable! Oh well, think about how trees are conceptually related to pairs. Define the terminologies around trees--tree, root, label, branch, leaf (the language we use may vary in different places).

Consider the following implementation:

def tree(label, branches=[]):
  return [label] + branches

def label(tree):
  return tree[0]

def branches(tree):
  return tree[1:]

There are multiple ways to get started on playing with such tree structure! As a few examples:

  1. Given a tree, how can we find a largest/smallest label in the leaves?
  2. Given a tree, how can we find a largest/smallest label in the tree?
  3. Given a tree, how can we find the sum of all labels (assuming all labels are numbers)?
  4. Given a tree, how can we make a copy of it with all the leaves pruned? The function is non-destructive.
  5. Given a tree and a label, how can we check if the label we want is in the tree?
  6. Given a tree, how can we check if there aren't any descendent nodes of a same label for any node in the tree?