Mailing List Archive

on implementing a toy oop-system
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"])
--
https://mail.python.org/mailman/listinfo/python-list
Re: on implementing a toy oop-system [ In reply to ]
ram@zedat.fu-berlin.de (Stefan Ram) writes:

> Meredith Montgomery <mmontgomery@levado.to> writes:
>>Is that at all possible somehow? Alternatively, how would you do your
>>toy oop-system?
>
> Maybe something along those lines:
>
> from functools import partial
>
> def counter_create( object ):
> object[ "n" ]= 0
> def counter_increment( object ):
> object[ "n" ]+= 1
> def counter_value( object ):
> return object[ "n" ]
>
> counter_class =( counter_create, counter_increment, counter_value )
>
> def inherit_from( class_, target ):
> class_[ 0 ]( target )
> for method in class_[ 1: ]:
> target[ method.__name__ ]= partial( method, target )
>
> car = dict()
>
> inherit_from( counter_class, car )
>
> print( car[ "counter_value" ]() )
> car[ "counter_increment" ]()
> print( car[ "counter_value" ]() )
>
> . The "create" part is simplified. I just wanted to show how
> to make methods like "counter_increment" act on the object
> that inherited them using "partial".

That's simple and interesting. I'll study your more elaborate approach
next, but I like what I see already. Thank you so much.
--
https://mail.python.org/mailman/listinfo/python-list
Re: on implementing a toy oop-system [ In reply to ]
ram@zedat.fu-berlin.de (Stefan Ram) writes:

> Meredith Montgomery <mmontgomery@levado.to> writes:
>>Is that at all possible somehow? Alternatively, how would you do your
>>toy oop-system?
>
> Maybe something along those lines:
>
> from functools import partial
>
> def counter_create( object ):
> object[ "n" ]= 0
> def counter_increment( object ):
> object[ "n" ]+= 1
> def counter_value( object ):
> return object[ "n" ]
>
> counter_class =( counter_create, counter_increment, counter_value )
>
> def inherit_from( class_, target ):
> class_[ 0 ]( target )
> for method in class_[ 1: ]:
> target[ method.__name__ ]= partial( method, target )
>
> car = dict()
>
> inherit_from( counter_class, car )
>
> print( car[ "counter_value" ]() )
> car[ "counter_increment" ]()
> print( car[ "counter_value" ]() )
>
> . The "create" part is simplified. I just wanted to show how
> to make methods like "counter_increment" act on the object
> that inherited them using "partial".

I really liked this idea. I organized it my way. Have a look. (Thank
you for the lecture!)

--8<---------------cut here---------------start------------->8---
from functools import partial

def Counter(name = None):
o = {"name": name if name else "untitled", "n": 0}
def inc(o):
o["n"] += 1
return o
o["inc"] = inc
def get(o):
return o["n"]
o["get"] = get
return o

def Car(maker):
o = {"maker": maker, "state": "off"}
inherit_from(Counter, o)
def on(o):
if o["is_on"]():
raise ValueError("oh, no: car is already on")
o["inc"]()
print(f"{o['maker']}: bruum!")
o["state"] = "on"
return o
o["on"] = partial(on, o)
def off(o):
if o["is_off"]():
raise ValueError("oh, no: car is already off")
print(f"{o['maker']}: spat!")
o["state"] = "off"
return o
o["off"] = partial(off, o)
def is_on(o):
return o["state"] == "on"
o["is_on"] = partial(is_on, o)
def is_off(o):
return o["state"] == "off"
o["is_off"] = partial(is_off, o)
return o

def main():
car1 = Car("Ford")
car2 = Car("VW")
for i in range(5):
car1["on"](); car1["off"]()
for i in range(3):
car2["on"](); car2["off"]()
print(f"car turned on = {car1['get']()} ({car1['maker']})")
print(f"car turned on = {car2['get']()} ({car2['maker']})")

## (*) How to inherit the methods from a class
##
def inherit_from(C, target):
o = C()
for k, v in o.items():
if callable(v):
target[k] = partial(v, target)
else:
target[k] = v
--8<---------------cut here---------------end--------------->8---
--
https://mail.python.org/mailman/listinfo/python-list
Re: on implementing a toy oop-system [ In reply to ]
On Sat, 24 Sept 2022 at 07:52, Meredith Montgomery
<mmontgomery@levado.to> wrote:
>
> def Counter(name = None):
> o = {"name": name if name else "untitled", "n": 0}
> def inc(o):
> o["n"] += 1
> return o
> o["inc"] = inc
> def get(o):
> return o["n"]
> o["get"] = get
> return o
>

Want a neat demo of how classes and closures are practically the same thing?

def Counter(name=None):
if not name: name = "untitled"
n = 0
def inc():
nonlocal n; n += 1
def get():
return n
return locals()

Aside from using a nonlocal declaration rather than "self.n", this is
extremely similar to classes, yet there are no classes involved.

A class statement creates a namespace. The locals() function returns
the function's namespace.

Each call to Counter() creates a new closure context, just like each
call to a constructor creates a new object.

There's very little difference, at a fundamental level :)

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
Re: on implementing a toy oop-system [ In reply to ]
Chris Angelico <rosuav@gmail.com> writes:

> On Sat, 24 Sept 2022 at 07:52, Meredith Montgomery
> <mmontgomery@levado.to> wrote:
>>
>> def Counter(name = None):
>> o = {"name": name if name else "untitled", "n": 0}
>> def inc(o):
>> o["n"] += 1
>> return o
>> o["inc"] = inc
>> def get(o):
>> return o["n"]
>> o["get"] = get
>> return o
>>
>
> Want a neat demo of how classes and closures are practically the same thing?
>
> def Counter(name=None):
> if not name: name = "untitled"
> n = 0
> def inc():
> nonlocal n; n += 1
> def get():
> return n
> return locals()
>
> Aside from using a nonlocal declaration rather than "self.n", this is
> extremely similar to classes, yet there are no classes involved.
>
> A class statement creates a namespace. The locals() function returns
> the function's namespace.
>
> Each call to Counter() creates a new closure context, just like each
> call to a constructor creates a new object.
>
> There's very little difference, at a fundamental level :)

I started out this way, but I had to change direction to implement
inheritance: the difficulty with closures seems to be lexical scoping,
which makes it hard (or impossible) for me to move these closures to
another ``object''. For instance, the nonlocal /n/ in /inc/ above is
forever bound to that closure; there seems to be no way to make /inc/
update some /n/ in another ``object'', which is needed in my conception
of inheritance. I think Python would have to let me duplicate closures.
(Thanks for showing me /locals()/.)
--
https://mail.python.org/mailman/listinfo/python-list
Re: on implementing a toy oop-system [ In reply to ]
Meredith Montgomery <mmontgomery@levado.to> writes:

> ram@zedat.fu-berlin.de (Stefan Ram) writes:
>
>> Meredith Montgomery <mmontgomery@levado.to> writes:
>>>Is that at all possible somehow? Alternatively, how would you do your
>>>toy oop-system?
>>
>> Maybe something along those lines:
>>
>> from functools import partial
>>
>> def counter_create( object ):
>> object[ "n" ]= 0
>> def counter_increment( object ):
>> object[ "n" ]+= 1
>> def counter_value( object ):
>> return object[ "n" ]
>>
>> counter_class =( counter_create, counter_increment, counter_value )
>>
>> def inherit_from( class_, target ):
>> class_[ 0 ]( target )
>> for method in class_[ 1: ]:
>> target[ method.__name__ ]= partial( method, target )
>>
>> car = dict()
>>
>> inherit_from( counter_class, car )
>>
>> print( car[ "counter_value" ]() )
>> car[ "counter_increment" ]()
>> print( car[ "counter_value" ]() )
>>
>> . The "create" part is simplified. I just wanted to show how
>> to make methods like "counter_increment" act on the object
>> that inherited them using "partial".
>
> I really liked this idea. I organized it my way. Have a look. (Thank
> you for the lecture!)

But it lacks consistency.

> from functools import partial
>
> def Counter(name = None):
> o = {"name": name if name else "untitled", "n": 0}
> def inc(o):
> o["n"] += 1
> return o
> o["inc"] = inc
> def get(o):
> return o["n"]
> o["get"] = get
> return o

This parent class is not defined in the same way as the child class
below. The class below uses partial to fix the object in the method,
but the parent one does not. We need consistency.

But if we curry the parent class's methods (that is, if we apply partial
on it to fix the object in its first argument), we will curry them a
second time in inherit_from. That won't work. I can't see an elegant
solution there, so what I'm going to do is to keep a copy of the
uncurried original method.

The code below works, but you can see it's kinda ugly. I wish I could
uncurry a procedure, but I don't think this is possible. (Is it?)

# -*- mode: python; python-indent-offset: 2 -*-
def Counter(name = None):
self = {"name": name if name else "untitled", "n": 0}
def inc(self):
self["n"] += 1
return self
self["inc_uncurried"] = inc
self["inc"] = curry(inc, self)
def get(self):
return self["n"]
self["get_uncurried"] = get
self["get"] = curry(get, self)
return self

def Car(maker):
self = {"maker": maker, "state": "off"}
inherit_from(Counter, self)
def on(self):
if self["is_on"]():
raise ValueError("oh, no: car is already on")
self["inc"]()
print(f"{self['maker']}: bruum!")
self["state"] = "on"
return self
self["on_uncurried"] = on
self["on"] = curry(on, self)
def off(self):
if self["is_off"]():
raise ValueError("oh, no: car is already off")
print(f"{self['maker']}: spat!")
self["state"] = "off"
return self
self["off_uncurried"] = off
self["off"] = curry(off, self)
def is_on(self):
return self["state"] == "on"
self["is_on_uncurried"] = is_on
self["is_on"] = curry(is_on, self)
def is_off(self):
return self["state"] == "off"
self["is_off_uncurried"] = is_off
self["is_off"] = curry(is_off, self)
return self

def main():
car1 = Car("Ford")
car2 = Car("VW")
for i in range(5):
car1["on"](); car1["off"]()
for i in range(3):
car2["on"](); car2["off"]()
print(f"car turned on = {car1['get']()} ({car1['maker']})")
print(f"car turned on = {car2['get']()} ({car2['maker']})")

>>> main()
Ford: bruum!
Ford: spat!
Ford: bruum!
Ford: spat!
Ford: bruum!
Ford: spat!
Ford: bruum!
Ford: spat!
Ford: bruum!
Ford: spat!
VW: bruum!
VW: spat!
VW: bruum!
VW: spat!
VW: bruum!
VW: spat!
car turned on = 5 (Ford)
car turned on = 3 (VW)
--
https://mail.python.org/mailman/listinfo/python-list
Re: on implementing a toy oop-system [ In reply to ]
ram@zedat.fu-berlin.de (Stefan Ram) writes:

> Meredith Montgomery <mmontgomery@levado.to> writes:
>>The code below works, but you can see it's kinda ugly. I wish I could
>>uncurry a procedure, but I don't think this is possible. (Is it?)
>
> from functools import partial
> from operator import add
> add5 = partial( add, 5 )
> print( add5( 2 ))
> # might be dependent on implementation details of "functools":
> uncurried = add5.func
> print( uncurried( 14, 7 ))

It works on my system here. Thank you so much for your help. (I've
been learning a lot with you!)

> However, to evaluate a method call such as "o.m( a, a1, ... )",
> currying does not necessarily have to be used. One can as well
> determine the function to be used for "m" from the type of "o"
> and then call that function with arguments "o", "a", "a1", ...

Was that your last approach? I only glanced at it so far. But I
remember seeing you describing an object and listing out which methods
were defined for it and then there was one procedure that would take the
role of invoking the methods (with the proper arguments).
--
https://mail.python.org/mailman/listinfo/python-list
Re: on implementing a toy oop-system [ In reply to ]
ram@zedat.fu-berlin.de (Stefan Ram) writes:

[...]

>>>However, to evaluate a method call such as "o.m( a, a1, ... )",
>>>currying does not necessarily have to be used. One can as well
>>>determine the function to be used for "m" from the type of "o"
>>>and then call that function with arguments "o", "a", "a1", ...
>>Was that your last approach?
>
> Yes, I think so.
>
> (There are also languages with "multi-methods", where upon
> a function call "m( o, o1, ... )" the decision which function
> to call depends on all the types of all the arguments.)

I think Clojure is one such. I've read Part 1 of ``Clojure in Action''
by Amit Rathore, 2012, Manning, ISBN 9781935182597. I liked it.
--
https://mail.python.org/mailman/listinfo/python-list