#!/usr/bin/env python3 """Let Over Lambda - An Object System in Python.""" # Object-oriented baseline. class Counter0: """A counter for counting.""" def __init__(self, init=0): """Intantiate a new counter with optional initialization.""" self._x = init def inc(self, by=1): """Increment counter.""" self._x += by return self._x def dec(self, by=1): """Decrement counter.""" self._x -= by return self._x c0 = Counter0(40) c0.inc(3) assert c0.dec() == 42 # First 'Let Over Lambda' version. def Counter1(init=0): """Instantiate a new counter with optional initialization.""" x = init # Increment the counter. def inc(by=1): nonlocal x x += by return x # Decrement the counter. def dec(by=1): nonlocal x x -= by return x # Return the two 'methods' as values. return inc, dec c1_inc, c1_dec = Counter1(40) c1_inc(3) assert c1_dec() == 42 # Second 'Let Over Lambda' version, using a Dict. def Counter2(init=0): """Intantiate a new counter with optional initialization.""" x = init # Increment the counter. def inc(by=1): nonlocal x x += by return x # Decrement the counter. def dec(by=1): nonlocal x x -= by return x return { 'inc': inc, 'dec': dec, } c2 = Counter2(40) c2['inc'](3) assert c2['dec']() == 42 # Third 'Let Over Lambda' version, using a dispatch function. def Counter3(init=0): """Intantiate a new counter with optional initialization.""" x = init # Increment the counter. def inc(by=1): nonlocal x x += by return x # Decrement the counter. def dec(by=1): nonlocal x x -= by return x # Dispatch on supplied message, forwarding other arguments. def dispatch(msg, *args, **kwargs): match msg: case 'inc': return inc(*args, **kwargs) case 'dec': return dec(*args, **kwargs) return dispatch c3 = Counter3(40) c3('inc', 3) assert c3('dec') == 42 # Fourth version, using a SimpleNamespace. def Counter4(init=0): """Intantiate a new counter with optional initialization.""" from types import SimpleNamespace x = init # Increment the counter. def inc(by=1): nonlocal x x += by return x # Decrement the counter. def dec(by=1): nonlocal x x -= by return x # Return a SimpleNamespace, so methods can be called with '.'. return SimpleNamespace(**{ 'inc': inc, 'dec': dec, }) c4 = Counter4(40) c4.inc(3) assert c4.dec() == 42 # Fifth version, reducing boilerplate. def cls(): """Make a new 'class' instance.""" from types import SimpleNamespace, FunctionType import inspect frame = inspect.currentframe().f_back localz = frame.f_locals del frame return SimpleNamespace(**{ name: fn for (name, fn) in localz.items() if isinstance(fn, FunctionType) }) def Counter5(init=0): """Intantiate a new counter with optional initialization.""" x = init # Increment the counter. def inc(by=1): nonlocal x x += by return x # Decrement the counter. def dec(by=1): nonlocal x x -= by return x # Return a class from local functions. return cls() c5 = Counter5(40) c5.inc(3) assert c5.dec() == 42 # Sixth version, using decorators. def cls6(constructor): """Decorator to make classes from closures.""" from types import SimpleNamespace from collections.abc import Iterable def make_class(*args, **kwargs): methods = constructor(*args, **kwargs) # Might be a single value. if not isinstance(methods, Iterable): methods = (methods,) return SimpleNamespace(**{ f.__name__: f for f in methods }) return make_class @cls6 def Counter6(init=0): """Intantiate a new counter with optional initialization.""" x = init # Increment the counter. def inc(by=1): nonlocal x x += by return x # Decrement the counter. def dec(by=1): nonlocal x x -= by return x # Return a class from local functions. return inc, dec c6 = Counter6(40) c6.inc(3) assert c6.dec() == 42 # Run all doctests. if __name__ == "__main__": import doctest doctest.testmod()