Last active
June 15, 2021 08:28
-
-
Save internetimagery/05082fac28bc17860ec23fa0d7172df7 to your computer and use it in GitHub Desktop.
Experiment in "underscore" placeholder lambda generation. Detecting when to finalize and when to build.
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
""" | |
Yet another placeholder underscore lambda replacement. | |
This is a proof of concept trying to tackle the difficult issue of detecting when the function is still being built, and when it is being used. | |
>>> list(map(_.upper(), "abc")) | |
>>> # ["A", "B", "C"] | |
""" | |
import operator | |
import sys | |
from dis import disco, opmap | |
from re import finditer | |
from io import StringIO | |
from weakref import ref | |
from functools import partial | |
from itertools import chain | |
from collections import namedtuple | |
from inspect import currentframe | |
# Limit the search space. These declare end of an expression. | |
OP_EXPRESSION_STOP = frozenset( | |
( | |
"STORE_NAME", | |
"STORE_FAST", | |
"STORE_ATTR", | |
"STORE_SUBSCR", | |
) | |
) | |
# How the opcodes affect the stack | |
OP_STACK_MANIP = { | |
"POP_TOP": lambda i: -1, | |
"DUP_TOP": lambda i: 1, | |
"DUP_TOP_TWO": lambda i: 2, | |
"LOAD_FAST": lambda i: 1, | |
"LOAD_CONST": lambda i: 1, | |
"LOAD_NAME": lambda i: 1, | |
"LOAD_GLOBAL": lambda i: 1, | |
} | |
OP_STACK_MANIP.update( | |
(op, lambda i: -1) | |
for op in opmap | |
if op.startswith(("BINARY_", "INPLACE_", "STORE_")) | |
) | |
OP_STACK_MANIP.update( | |
(op, lambda i: -i[2]) | |
for op in opmap | |
if op.startswith("CALL_") | |
) | |
OP_STACK_MANIP.update( | |
(op, lambda i: -i[2] + 1) | |
for op in opmap | |
if op.startswith("BUILD_") | |
) | |
class __(type): | |
""" Cause the class to instantiate when used """ | |
def __getattr__(cls, name): | |
return getattr(cls(currentframe().f_back, 0), name) | |
def __getitem__(cls, index): | |
return cls(currentframe().f_back, 1)[index] | |
def __abs__(cls): | |
return abs(cls(currentframe().f_back, 0)) | |
def __add__(cls, other): | |
return cls(currentframe().f_back, 1) + other | |
def __radd__(cls, other): | |
return other + cls(currentframe().f_back, 1) | |
def __sub__(cls, other): | |
return cls(currentframe().f_back, 1) - other | |
def __rsub__(cls, other): | |
return other - cls(currentframe().f_back, 1) | |
def __mul__(cls, other): | |
return cls(currentframe().f_back, 1) * other | |
def __rmul__(cls, other): | |
return other * cls(currentframe().f_back, 1) | |
def __truediv__(cls, other): | |
return cls(currentframe().f_back, 1) / other | |
__div__ = __truediv__ # py2 | |
def __rtruediv__(cls, other): | |
return other / cls(currentframe().f_back, 1) | |
__rdiv__ = __rtruediv__ # py2 | |
def __mod__(cls, other): | |
return cls(currentframe().f_back, 1) % other | |
def __rmod__(cls, other): | |
return other % cls(currentframe().f_back, 1) | |
def __pow__(cls, other): | |
return cls(currentframe().f_back, 1) ** other | |
def __rpow__(cls, other): | |
return other ** cls(currentframe().f_back, 1) | |
def __pos__(cls): | |
return +cls(currentframe().f_back, 0) | |
def __neg__(cls): | |
return -cls(currentframe().f_back, 0) | |
# COMPARISON | |
def __lt__(cls, other): | |
return cls(currentframe().f_back, 1) < other | |
def __le__(cls, other): | |
return cls(currentframe().f_back, 1) <= other | |
def __eq__(cls, other): | |
return cls(currentframe().f_back, 1) == other | |
def __ne__(cls, other): | |
return cls(currentframe().f_back, 1) != other | |
def __ge__(cls, other): | |
return cls(currentframe().f_back, 1) >= other | |
def __gt__(cls, other): | |
return cls(currentframe().f_back, 1) > other | |
# CANNOT SUPPORT THESE :( | |
def __bool__(cls): | |
# Only allowed to return bool, so can't use this. :( | |
raise TypeError("bool not supported :(") | |
__nonzero__ = __bool__ # py2 | |
def __len__(cls): | |
# Only allowed to return int, so can't use this. :( | |
raise TypeError("len not supported :(") | |
class _(object, metaclass=__): | |
""" | |
Generic placeholder for building simple lambdas. | |
fn = _["key"].upper() | |
assert fn({"key":"lower"}) == "LOWER" | |
""" | |
def __init__(self, frame, stack): | |
self.__commands = [] | |
self.__name = [] | |
self.__code = ref(frame.f_code) | |
self.__end = 0 | |
self._finalized = False | |
lasti = frame.f_lasti | |
for instruction in _get_instructions(frame.f_code): | |
if instruction[0] < lasti: | |
continue | |
if instruction[1] in OP_EXPRESSION_STOP: | |
break | |
manip = OP_STACK_MANIP.get(instruction[1]) | |
if manip: | |
stack += manip(instruction) | |
if stack < 0: | |
break | |
self.__end = instruction[0] | |
def __abs__(self): | |
self.__commands.append(operator.abs) | |
self.__name.insert(0, f"abs(") | |
self.__name.append(f")") | |
return self | |
def __add__(self, other): | |
self.__commands.append(partial(operator.add, other)) | |
self.__name.append(f" + {other!r}") | |
return self | |
def __radd__(self, other): | |
self.__commands.append(partial(operator.add, other)) | |
self.__name.insert(0, f"{other!r} + ") | |
return self | |
def __sub__(self, other): | |
self.__commands.append(lambda x: x - other) | |
self.__name.append(f" - {other!r}") | |
return self | |
def __rsub__(self, other): | |
self.__commands.append(partial(operator.sub, other)) | |
self.__name.insert(0, f"{other!r} - ") | |
return self | |
def __mul__(self, other): | |
self.__commands.append(partial(operator.mul, other)) | |
self.__name.append(f" * {other!r}") | |
return self | |
def __rmul__(self, other): | |
self.__commands.append(partial(operator.mul, other)) | |
self.__name.insert(0, f"{other!r} * ") | |
return self | |
def __truediv__(self, other): | |
self.__commands.append(lambda x: x / other) | |
self.__name.append(f" / {other!r}") | |
return self | |
def __rtruediv__(self, other): | |
self.__commands.append(partial(operator.truediv, other)) | |
self.__name.insert(0, f"{other!r} / ") | |
return self | |
def __mod__(self, other): | |
self.__commands.append(lambda x: x % other) | |
self.__name.append(f" % {other!r}") | |
return self | |
def __rmod__(self, other): | |
self.__commands.append(partial(operator.mod, other)) | |
self.__name.insert(0, f"{other!r} % ") | |
return self | |
def __pow__(self, other): | |
self.__commands.append(lambda x: x ** other) | |
self.__name.append(f" ** {other!r}") | |
return self | |
def __rpow__(self, other): | |
self.__commands.append(partial(operator.pow, other)) | |
self.__name.insert(0, f"{other!r} ** ") | |
return self | |
def __pos__(self): | |
self.__commands.append(operator.pos) | |
self.__name.insert(0, f"+") | |
return self | |
def __neg__(self): | |
self.__commands.append(operator.neg) | |
self.__name.insert(0, f"-") | |
return self | |
def __lt__(self, other): | |
self.__commands.append(partial(operator.gt, other)) | |
self.__name.append(f" < {other!r}") | |
return self | |
def __le__(self, other): | |
self.__commands.append(partial(operator.ge, other)) | |
self.__name.append(f" <= {other!r}") | |
return self | |
def __eq__(self, other): | |
self.__commands.append(partial(operator.eq, other)) | |
self.__name.append(f" == {other!r}") | |
return self | |
def __ne__(self, other): | |
self.__commands.append(partial(operator.ne, other)) | |
self.__name.append(f" != {other!r}") | |
return self | |
def __ge__(self, other): | |
self.__commands.append(partial(operator.le, other)) | |
self.__name.append(f" >= {other!r}") | |
return self | |
def __gt__(self, other): | |
self.__commands.append(partial(operator.lt, other)) | |
self.__name.append(f" > {other!r}") | |
return self | |
def __getattr__(self, name): | |
if self._finalized: | |
raise AttributeError(f"Finalized function has no attribute {name}") | |
self.__commands.append(operator.attrgetter(name)) | |
self.__name.append(f".{name}") | |
return self | |
def __getitem__(self, index): | |
if self._finalized: | |
raise TypeError("Finalized object is not subscriptable") | |
self.__commands.append(operator.itemgetter(index)) | |
self.__name.append(f"[{index!r}]") | |
return self | |
def __call__(self, *args, **kwargs): | |
if self._finalized: | |
return self.__run(*args, **kwargs) | |
frame = currentframe().f_back | |
if self.__code() is not frame.f_code or frame.f_lasti > self.__end: | |
self._finalized = True | |
return self.__run(*args, **kwargs) | |
# Optimize with a method caller if we previously got an attribute to run it | |
if self.__commands and isinstance(self.__commands[-1], operator.attrgetter): | |
command = operator.methodcaller(self.__name[-1][1:], *args, **kwargs) | |
self.__commands.pop() | |
else: | |
command = partial(_call, args, kwargs) | |
arg_visual = (repr(a) for a in args) | |
kwarg_visual = ("{}={!r}".format(*i) for i in kwargs.items()) | |
self.__commands.append(command) | |
self.__name.append("({})".format(", ".join(chain(arg_visual, kwarg_visual)))) | |
return self | |
def __run(self, *values): | |
if not values: | |
raise TypeError("Not enough arguments supplied") | |
value = values[0] | |
for command in self.__commands: | |
value = command(value) | |
if isinstance(value, type(self)): | |
return value(*values[1:]) | |
return value | |
def __repr__(self): | |
return "< _{} >".format("".join(self.__name)) | |
def _call(args, kwargs, func): | |
""" Store arguments till function is ready, then run it """ | |
return func(*args, **kwargs) | |
def _get_instructions(code): | |
""" Parse dis.disco output """ | |
buffer_ = StringIO() | |
stdout, sys.stdout = sys.stdout, buffer_ | |
try: | |
disco(code) | |
finally: | |
sys.stdout = stdout | |
for match in finditer(r"(\d+) ([A-Z_]+)( +\d+)?", buffer_.getvalue()): | |
yield int(match.group(1)), match.group(2), int(match.group(3) or 0) | |
if __name__ == "__main__": | |
fn = _.get("hello").upper() # STORE_NAME | |
assert fn({"hello": "there"}) == "THERE" | |
assert str(fn) == "< _.get('hello').upper() >" | |
def test(): | |
return _.get("hello").upper() # Different frame | |
assert test()({"hello": "there"}) == "THERE" | |
def test(): | |
fn = _.get("hello").upper() # STORE_FAST | |
assert fn({"hello": "there"}) == "THERE" | |
test() | |
class T: pass | |
t = T() | |
t.fn = _.get("hello").upper() # type: ignore # STORE_ATTR | |
assert t.fn({"hello": "there"}) == "THERE" # type: ignore | |
d = {} | |
d["fn"] = _.get("hello").upper() # STORE_SUBSCR | |
assert d["fn"]({"hello": "there"}) == "THERE" | |
assert tuple(map(_.upper(), ["lower"])) == ("LOWER",) # Used in expression | |
assert tuple(map(_.index(int("1")), [[1,2]])) == (0,) # Function call in method | |
d = {"key": 1} | |
assert tuple(map(_.index(d.get("key")), [[1,2]])) == (0,) # Function method call in method | |
d = {"key": lambda: 0} | |
assert tuple(map(_["key"](), [d])) == (0,) # Function tail function call | |
d = {"key": lambda _: 0} | |
assert tuple(map(_["key"](str(1)), [d])) == (0,) # Function call in tail function call | |
assert tuple(map(_[1:3], [[1,2,3,4]])) == ([2,3],) # Slice in item | |
d = {"one": {"two": "three"}} | |
assert tuple(map(_["one"]["two"], [d])) == ("three",) # nested item calls | |
assert tuple(map(_.get("one")["two"], [d])) == ("three",) # nested function to item | |
d = {"key": lambda: lambda: lambda: 3} | |
assert tuple(map(_["key"]()()(), [d])) == (3,) # Super nested | |
fn = _ + 10 | |
assert fn(3) == 13 | |
fn = 10 + _ | |
assert fn(3) == 13 | |
fn = _ * 2 | |
assert fn(10) == 20 | |
fn = 2 * _ | |
assert fn("-") == "--" | |
assert tuple(map((_ * 2).upper(), ["abc"])) == ("ABCABC",) | |
fn = _ - 5 | |
assert fn(10) == 5 | |
assert tuple(map(5 - _ + 10, [20])) == (-5,) | |
fn = _ + _ | |
assert fn(5, 5) == 10 | |
assert tuple(map(10 > _, [2, 4, 6, 8, 10, 12])) == (True, True, True, True, False, False) | |
assert tuple(map(abs(_), [-1, 2])) == (1,2) | |
assert tuple(map(-_ + 5, [10, 5])) == (-5, 0) | |
assert tuple(map(+_ + 5, [10, 5])) == (15, 10) | |
assert tuple(map(_ / 1, [2, 4])) == (2.0, 4.0) | |
fn = _ + 3 | |
assert fn(5) == 5 + 3 | |
fn = 3 + _ | |
assert fn(5) == 3 + 5 | |
fn = _ - 3 | |
assert fn(5) == 5 - 3 | |
fn = 3 - _ | |
assert fn(5) == 3 - 5 | |
fn = _ / 3 | |
assert fn(5) == 5 / 3 | |
fn = 3 / _ | |
assert fn(5) == 3 / 5 | |
fn = _ * 3 | |
assert fn(5) == 5 * 3 | |
fn = 3 * _ | |
assert fn(5) == 3 * 5 | |
fn = _ ** 3 | |
assert fn(5) == 5 ** 3 | |
fn = 3 ** _ | |
assert fn(5) == 3 ** 5 | |
fn = _ % 3 | |
assert fn(5) == 5 % 3 | |
fn = 3 % _ | |
assert fn(5) == 3 % 5 | |
fn = _ > 3 | |
assert fn(5) == (5 > 3) | |
fn = 3 > _ | |
assert fn(5) == (3 > 5) | |
fn = _ >= 3 | |
assert fn(5) == (5 >= 3) | |
fn = 3 >= _ | |
assert fn(5) == (3 >= 5) | |
fn = _ < 3 | |
assert fn(5) == (5 < 3) | |
fn = 3 < _ | |
assert fn(5) == (3 < 5) | |
fn = _ <= 3 | |
assert fn(5) == (5 <= 3) | |
fn = 3 <= _ | |
assert fn(5) == (3 <= 5) | |
fn = _ == 3 | |
assert fn(5) == (5 == 3) | |
fn = 3 != _ | |
assert fn(5) == (3 != 5) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment