on implementing a toy oop-system

Meredith Montgomery mmontgomery at levado.to
Sun Sep 4 17:05:31 EDT 2022


Just for investigation sake, I'm trying to simulate OO-inheritance.  

(*) What did I do?  

I decided that objects would be dictionaries and methods would be
procedures stored in the object.  A class is just a procedure that
creates such object-dictionary.  So far so good.  Trouble arrived when I
decided to have inheritance.  The problem might related to the
lexical-nature of closures, but I'm not sure.

(*) A problem 

Say I have a Counter-creating object procedure and a Car-creating object
procedure.  Cars want to track how many times they park, so they inherit
a counter.  

--8<---------------cut here---------------start------------->8---
def Car(maker = None, color = None):
  o = inherit(Object(), Counter())
  o["maker"] = maker
  o["color"] = color
  o["speed"] = 0
--8<---------------cut here---------------end--------------->8---

What Object() does is just to return a dictionary.  What inherit() does
is just to merge the two dictionaries into a single one (to be used by
the Car procedure).

This didn't come out as I expected, though.  Let me show you
step-by-step where the problem is.  First, here's a counter in action.

>>> c1 = Counter()
>>> c1["inc"]()["inc"]()
{'id': <function Object.<locals>.id at 0x0000016547C648B0>, 'n': 2, [...]}
>>> c1["n"]
2

That's good and expected: I incremented it twice.  But look what happens
with a Car's counter.

>>> c = Car()
>>> c = Car()
>>> c
{'id': <function Object.<locals>.id at 0x0000016547C64B80>, 'n': 0,
  'inc': <function Counter.<locals>.inc at 0x0000016547C64C10>, 'dec':
  <function Counter.<locals>.dec at 0x0000016547C64CA0>, 'maker': None,
  'color': None, 'speed': 0, 'accelerate': <function
  Car.<locals>.accelerate at 0x0000016547C64AF0>, 'park': <function
  Car.<locals>.park at 0x0000016547C64D30>}

>>> c["inc"]()
{'id': <function Object.<locals>.id at 0x0000016547C64B80>, 'n': 1,
  'inc': <function Counter.<locals>.inc at 0x0000016547C64C10>, 'dec':
  <function Counter.<locals>.dec at 0x0000016547C64CA0>}

We can see something got incremented!  But... 

>>> c["n"]
0

Indeed, what got incremented is the dictionary attached to the /inc/
procedure of the Counter closure, so it's that dictionary that's being
mutated.  My /inherit/ procedure is not able to bring that procedure
into the Car dictionary.  

Is that at all possible somehow?  Alternatively, how would you do your
toy oop-system?

(*) Full code below

from random import random

def Object():
  myid = random()
  def id():
    return myid
  return {
    "id": id
  }

def inherit(o, parentObject):
  o.update(parentObject)
  return o

def Counter(begin_at = 0):
  o = Object()
  o["n"] = begin_at
  def inc():
    nonlocal o
    o["n"] += 1
    return o
  o["inc"] = inc
  def dec():
    nonlocal o
    o["n"] -= 1
    return o
  o["dec"] = dec
  return o

def Car(maker = None, color = None):
  o = inherit(Object(), Counter())
  o["maker"] = maker
  o["color"] = color
  o["speed"] = 0
  def accelerate(speed):
    nonlocal o
    print(f"Car-{o['id']()}: accelerating to {speed}...")
    o["speed"] = speed
    return o
  o["accelerate"] = accelerate
  def park():
    nonlocal o
    o["speed"] = 0
    o["parked"] = True
    o["inc"]()
    print(f"Car-{o['id']()}: parked! ({o['n']} times)")
    return o
  o["park"] = park
  return o

def tests():
  c1 = Counter()
  c2 = Counter(100)
  c1["inc"]()["inc"]()
  c2["dec"]()["dec"]()["dec"]()
  print("c1 is 2:", c1["n"])
  print("c2 is 97:", c2["n"])
  car1 = Car("VW", "Red")
  car1["accelerate"](100)
  print("speed is 100:", car1["speed"])
  car2 = Car("Ford", "Black")
  car2["accelerate"](120)["park"]()
  car2["accelerate"](50)["park"]()
  print("has parked 2 times:", car2["n"])


More information about the Python-list mailing list