Last active
January 17, 2021 06:35
-
-
Save ssanderson/ac00083249756f8e860f8b006b33ef95 to your computer and use it in GitHub Desktop.
A Class is a Poor Man's Loop
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
""" | |
$ python loop.py | |
Body: 0 | |
Body: 1 | |
Body: 2 | |
Body: 3 | |
Body: 4 | |
Skipping 5 | |
Body: 6 | |
Body: 7 | |
Body: 8 | |
Body: 9 | |
""" | |
import builtins | |
import sys | |
import operator | |
class Break(Exception): | |
pass | |
class Continue(Exception): | |
pass | |
def break_(): | |
raise Break() | |
def continue_(): | |
raise Continue() | |
real_build_class = builtins.__build_class__ | |
def my_build_class(*args, **kwargs): | |
try: | |
return real_build_class(*args, **kwargs) | |
except Break: | |
return None | |
builtins.__build_class__ = my_build_class | |
class LoopVariable: | |
def __init__(self, frame, name, value): | |
self.frame = frame | |
self.name = name | |
self.value = value | |
self.condition = None | |
self.mutate = None | |
@property | |
def ready(self): | |
return self.condition is not None and self.mutate is not None | |
def __pos__(self): | |
return IncrementingLoopVariable(self) | |
def _cmp(f): | |
def method(self, value): | |
if self.condition is None: | |
def condition(): | |
return f(self.value, value) | |
if not condition(): | |
raise Break() | |
self.condition = condition | |
else: | |
raise AssertionError("Multiple comparisons to loop variable.") | |
return True | |
return method | |
__lt__ = _cmp(operator.lt) | |
__le__ = _cmp(operator.le) | |
__gt__ = _cmp(operator.gt) | |
__ge__ = _cmp(operator.ge) | |
__eq__ = _cmp(operator.eq) | |
__ne__ = _cmp(operator.ne) | |
class IncrementingLoopVariable: | |
def __init__(self, variable): | |
self.variable = variable | |
def __pos__(self): | |
if self.variable.mutate is None: | |
variable = self.variable | |
def mutate(): | |
variable.value += 1 | |
self.variable.mutate = mutate | |
else: | |
raise AssertionError("mutated twice") | |
return self | |
class LoopBodyMapping(dict): | |
def __init__(self): | |
self.variable = None | |
self.limit = None | |
self.frame = None | |
super().__setitem__('break_', break_) | |
super().__setitem__('continue_', continue_) | |
@property | |
def ready(self): | |
return ( | |
self.variable is not None | |
and self.variable.condition is not None | |
and self.variable.mutate is not None | |
) | |
def __setitem__(self, key, value): | |
if key in ('__module__', '__qualname__'): | |
return | |
if self.variable is None: | |
self.frame = sys._getframe(1) | |
self.variable = LoopVariable(self.frame, key, value) | |
return | |
elif key == self.variable.name: | |
return # Ignore later assignments. | |
super().__setitem__(key, value) | |
def __getitem__(self, key): | |
if key == '__name__': | |
return "For" | |
if self.variable is None: | |
raise AssertionError(key) | |
if key != self.variable.name: | |
return super().__getitem__(key) | |
if not self.variable.ready: | |
assert key == self.variable.name | |
return self.variable | |
else: | |
return self.variable.value | |
class LoopMeta(type): | |
def __prepare__(name, *args, **kwargs): | |
return LoopBodyMapping() | |
def __new__(mcls, name, bases, clsdict): | |
if name == 'Loop': | |
return super().__new__(mcls, name, bases, clsdict) | |
var = clsdict.variable | |
var.mutate() | |
frame = clsdict.frame | |
while var.condition(): | |
try: | |
exec(frame.f_code, globals(), clsdict) | |
except Continue: | |
pass | |
var.mutate() | |
class Loop(metaclass=LoopMeta): | |
pass | |
class For(Loop): | |
x = 0; x < 10; ++x | |
if x == 5: | |
print("Skipping 5") | |
continue_() | |
print("Body:", x) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment