Skip to content

Instantly share code, notes, and snippets.

@obriencj
Last active September 14, 2017 18:09
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/9f6126f273b63065457aceb6a5d4611d to your computer and use it in GitHub Desktop.
Save obriencj/9f6126f273b63065457aceb6a5d4611d to your computer and use it in GitHub Desktop.
local bindings from a map
#! /usr/bin/env python3
# This is a Proof-Of-Concept, and only works in Python3.
# A Python2 port is almost certainly possible, haven't even tried.
from inspect import currentframe
from dis import get_instructions
def bindings(source_map):
"""
Find just the values for the bindings you were going to ask for.
"""
# find our calling frame, and the index of the op that called us
caller = currentframe().f_back
code = caller.f_code
index = caller.f_lasti
# disassemble the instructions for the calling frame, and advance
# past our calling op's index
iterins = get_instructions(code)
for instr in iterins:
if instr.offset > index:
break
if instr.opname != "UNPACK_SEQUENCE":
# someone invoked us without being the right-hand side of an
# unpack assignment, do let's be a noop
return source_map
# this is the number of assignments being unpacked, we'll get that
# many STORE_ ops from the bytecode
count = instr.argval
# each STORE_ op has an argval which is the name it would assign
# to. This is just a convenience that the dis module fills in!
dest_keys = (next(iterins).argval for _ in range(0, count))
# finally, just return a generator that'll provide the values from
# the source_map that match to the bindings
return (source_map[dest] for dest in dest_keys)
def test():
data = {"tacos": 900, "beer": 700, "a": 100, "b": 200, "c": 300, }
a, b, c = bindings(data)
print("a:", a)
print("b:", b)
print("c:", c)
if __name__ == "__main__":
test()
@obriencj
Copy link
Author

obriencj commented Sep 14, 2017

Summarizing things you'd need to do to make this more robust:

  • check that the instructions after UNPACK_SEQUENCE are all STORE_FAST, STORE_DEREF, STORE_GLOBAL, or STORE_NAME. If they aren't, it would indicate some nested unpacking or a *arg function application. In both of those cases, just return the original map
  • this is targeted at Python3. The Python2 dis module kinda sucks, and there's no Instruction data type. You'd have to copy/paste most of dis.dis and have it accumulate and yield the ops and their arguments. Still well within reason!
  • collapse it all into one function
  • write a bunch of unittests and check across a wide range of pythons (2.5+, 3.3+)

@obriencj
Copy link
Author

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