Last active
July 28, 2021 05:02
-
-
Save zbentley/4d37b2123458c6d9da2abc991ad3ca09 to your computer and use it in GitHub Desktop.
The worst object system in the world
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Are you tired of Python being slow? Wish it was faster in irrelevant ways at the cost of being massively slower | |
and more fragile in every relevant way? Tired of how costly it is to look up attributes on classes? Wish you could | |
"stop" a class when you're done with it, and cause all future interactions with it to raise baffling exceptions? Do | |
you hate that attribute assignment is a statement? Then you might like this alternative* object system I wrote! | |
Don't have any of those problems? Well, that's fine, because nobody asked for this anyway. | |
It has: | |
- Access without dots. Dots are so .... Java. | |
- Assignment is an expression! Just like you've always wanted! | |
- Constructors, but no destructors. And on some older versions of Python, refcount-zero may not actually destroy objects | |
because of reference loops. Garbage collection is for wimps anyway. | |
- Methods. | |
- Fields. | |
- Classmethods. | |
- Initializers. | |
- *All class state is stored in the runtime frame of a single function that never returns*. You know. For performance. | |
- Multiple inheritance, but *without a deterministic MRO.* This discourages the use of inheritance abuse to reconcile | |
overridden fields, and nudges people towards pure mixin-based composition. Because layer-cake objects are so passé. | |
- All object fields must be hashable, to enforce immutability. Attempting to interact with an object with mutable fields | |
will cause the object to enter an internally broken state after which all subsequent behavior is undefined (seriously | |
undefined--like, it might raise exceptions. It might reset your object back to the initial state. You don't know. So | |
don't do anything mutable.) | |
- No type system underneath, all classes fail isinstance checks on everything (well, almost everything). Objects are | |
just floating, unique, programmable closure scopes, as god intended. | |
- Seriously, why would you use this ever. Run away. | |
*alternative as in "alternative facts": incorrect in every way. | |
""" | |
from collections import defaultdict | |
import random | |
import inspect | |
from contextlib import contextmanager | |
def indirect(send): | |
def inner(instr, *a, **b): | |
if instr == 'new': | |
return send((instr, '', a, b))() | |
subj, *a = a | |
return send((instr, subj, a, b))() | |
return inner | |
def sasi_class_gen(): | |
vars = defaultdict(list) | |
def getvar(v): | |
return random.choice(vars[v]) | |
next_yield = lambda: 'not started' | |
instr, subj, args, kwargs = '', '', [], {} | |
while True: | |
try: | |
if instr == 'setvar': | |
vars[subj].append(args[0]) | |
next_yield = lambda: "don't you wish this was an expression in real python?" | |
elif instr == 'getvar': | |
next_yield = lambda: getvar(subj) | |
elif instr == 'new': | |
inst = sasi_class_gen() | |
inst.send(None) | |
call = indirect(inst.send) | |
for k, v in vars.items(): | |
call('setvar', k, getvar(k)) | |
call('setvar', '__self__', call) | |
if '__init__' in vars: | |
getvar('__init__')(call, *args, **kwargs) | |
next_yield = lambda: call | |
elif instr == 'call': | |
try: | |
v = getvar(subj) | |
if getattr(v, '__classmethod__', False): | |
next_yield = lambda: v(getvar('__class__'), *args, **kwargs) | |
else: | |
if not '__self__' in vars: | |
raise ValueError('Cannot call unbound method') | |
next_yield = lambda: v(getvar('__self__'), *args, **kwargs) | |
except KeyError: | |
raise ValueError(f'No method {subj} found') | |
elif instr == 'subclass_onto': | |
for k, v in vars.items(): | |
args[0]('setvar', k, getvar(k)) | |
finally: | |
instr, subj, args, kwargs = yield next_yield | |
@contextmanager | |
def Class(*superclasses): | |
gen = sasi_class_gen() | |
gen.send(None) | |
call = indirect(gen.send) | |
call('setvar', '__class__', indirect) | |
old = inspect.currentframe().f_back.f_back.f_locals.copy() | |
yield call # TODO extract name | |
new = inspect.currentframe().f_back.f_back.f_locals | |
for superclass in superclasses: | |
superclass('subclass_onto', '', call) | |
for k, v in new.items(): | |
if callable(v): | |
if k not in old or old[k] != v: | |
call('setvar', k, v) | |
def class_method(func): | |
func.__classmethod__ = True | |
return func | |
##### BEGIN USAGE | |
with Class() as MyClass: | |
def __init__(self, v1, v2): | |
self('setvar', 'v1', v1) | |
self('setvar', 'v2', v2) | |
def mymethod(self): | |
print("I'm a method", self('getvar', 'v1')) | |
@class_method | |
def myclassmethod(cls): | |
print("I'm a classmethod") | |
with Class(MyClass) as SubClass: | |
def mymethod(self): | |
print("Subclass method") | |
inst = MyClass('new', 'a', 'b') | |
inst2 = MyClass('new', 'c', 'd') | |
print(inst('getvar', 'v1')) | |
print(inst2('getvar', 'v1')) | |
inst('call', 'mymethod') | |
inst('setvar', 'v1', 'e') | |
print(inst('getvar', 'v1')) | |
inst2 = SubClass('new', 1, 2) | |
for _ in range(10): | |
inst2('call', 'mymethod') | |
inst2('call', 'myclassmethod') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment