Skip to content

Instantly share code, notes, and snippets.

@obriencj
Created December 11, 2017 20:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save obriencj/b012a1ac1a76d0deff9627ca77453b6a to your computer and use it in GitHub Desktop.
Save obriencj/b012a1ac1a76d0deff9627ca77453b6a to your computer and use it in GitHub Desktop.
class values(object):
def __init__(self, *args, **kwds):
self.__args = args
self.__iter__ = args.__iter__
self.__kwds = kwds
self.keys = kwds.keys
def __getitem__(self, key):
if isinstance(key, (slice, int)):
return self.__args[key]
else:
return self.__kwds[key]
def __repr__(self):
return "values(*%r, **%r)" % (self.__args, self.__kwds)
def __call__(self, function):
return function(*self.__args, **self.__kwds)
def unpack(self, positionals, variadic=None, kwonly=(),
keywords=(), defaults={}, kwvariadic=None):
lpos = len(positionals)
largs = len(self.__args)
kwvar = dict(self.__kwds)
if lpos == largs:
args = list(self.__args)
var = () if variadic else None
elif lpos < largs:
if variadic:
args = list(self.__args[:lpos])
var = self.__args[lpos:]
else:
raise TypeError("too many positional arguments")
else:
args = list(self.__args)
var = () if variadic else None
try:
for a in positionals[largs:]:
args.append(kwvar.pop(a))
except KeyError:
raise TypeError("missing required argument %s" % a)
kwds = {}
try:
for a in kwonly:
kwds[a] = kwvar.pop(a)
except KeyError:
raise TypeError("missing required keyword-only argument %s" % a)
for a in keywords:
kwds[a] = kwvar.pop(a, defaults[a])
if kwvar and not kwvariadic:
raise TypeError("unexpected arguments, %r" % list(kwvar.keys()))
return args, var, kwds, (kwvar if kwvariadic else None)
def unpack_fun(self, function):
code = function.__code__
ac = code.co_argcount + code.co_kwonlyargcount
if code.co_flags & 4:
ac += 1
if code.co_flags & 8:
ac += 1
positionals = list(code.co_varnames[:ac])
kwvariadic = positionals.pop() if code.co_flags & 8 else None
variadic = positionals.pop() if code.co_flags & 4 else None
kwoac = code.co_kwonlyargcount
if kwoac:
kwonly = positionals[-kwoac:]
positionals = positionals[:-kwoac]
else:
kwonly = ()
defaults = function.__kwdefaults__
if defaults:
kwac = len(defaults)
if kwonly:
keywords = kwonly[-kwac:]
kwonly = kwonly[:-kwac]
else:
keywords = positionals[-kwac:]
positionals = positionals[:-kwac]
else:
defaults = ()
keywords = ()
return self.unpack(positionals, variadic, kwonly,
keywords, defaults, kwvariadic)
v = values(1, 2, 3, foo=77, bar=88)
print("v is", repr(v))
def test_1(a, b, c, foo, **kwds):
return [a, b, c, foo], None, {}, kwds
print()
print("v(test_1) :", v(test_1))
print("test_1(*v, **v) :", test_1(*v, **v))
print("v.unpack_fun(test_1) :", v.unpack_fun(test_1))
def test_2(a, *rest, foo, bar, baz=None):
return [a], rest, dict(foo=foo, bar=bar, baz=baz), None
print()
print("v(test_2) :", v(test_2))
print("test_2(*v, **v) :", test_2(*v, **v))
print("v.unpack_fun(test_2) :", v.unpack_fun(test_2))
def test_3(a, b, c, *, foo, bar=100, baz=200, **kwds):
return [a, b, c], None, dict(foo=foo, bar=bar, baz=baz), kwds
print()
print("v(test_3) :", v(test_3))
print("test_3(*v, **v) :", test_3(*v, **v))
print("v.unpack_fun(test_3) :", v.unpack_fun(test_3))
# The end.
@obriencj
Copy link
Author

General idea is thus:

A values object represents the data given to a function application. It is the *args and **kwds data combined. It can be used as both for a function application (eg. my_function(*my_val, **my_val)) or in reverse to apply itself (eg. my_val(my_function)).

It also has the ability to convert itself into the four groups of bindings that a given function application would create,

  • the positional formal values
  • the variadic positional values
  • the keyword mapping
  • the variadic keywords mapping

These four groups depend greatly on the function's argspec, as you can see from the three sample functions test_1, test_2, and test_3.

Not shown is that a function with an incompatible argspec will cause an exception to be raised.

@obriencj
Copy link
Author

This logic will be borrowed for sibilant. The tail-call-recusion optimization step needs to be able to take the positional arguments for the tail call and map those back to the fast vars for the function itself, including default values. The logic for this is buried deep within Python's ceval code, hence needing to duplicate its behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment