Skip to content

Instantly share code, notes, and snippets.

@nosamanuel
Created February 9, 2014 00:04
Show Gist options
  • Save nosamanuel/8892238 to your computer and use it in GitHub Desktop.
Save nosamanuel/8892238 to your computer and use it in GitHub Desktop.
Destructuring bind in Python
import ast
import inspect
class destructure(object):
def __init__(self, target):
self.target = target
def __enter__(self):
# 1. Get the stack frame that called `destructure`
frame = inspect.currentframe()
calling_frame = frame.f_back
# 2. Get the source and line number that contains the call
if not calling_frame.f_back:
# XXX: This is an edge case because calls should always come
# from inside other functions, but...
# If there is no frame outside the calling frame then the
# call is from the top level of a module and we need to
# re-read the source to determine the calling statement.
info = inspect.getframeinfo(calling_frame)
source = open(info.filename).read()
line = calling_frame.f_lineno
else:
# In this case the source will be the the source of the
# calling function and we have to recalculate the line
# number as the offset from the start of the function body.
source = inspect.getsource(calling_frame)
line = calling_frame.f_lineno - calling_frame.f_code.co_firstlineno
line += 1 # Line numbers are 1-indexed
# 3. Parse that code into an AST
tree = ast.parse(source)
# 4. Search the AST for the calling statement
def search(node, line):
if isinstance(node, ast.With):
start_line, end_line = node.lineno, node.optional_vars.lineno
if start_line <= line <= end_line:
return node
elif not hasattr(node, 'body'):
return
# Search the parse tree recursively
for child in node.body:
node = search(child, line)
if node:
return node
statement = search(tree, line)
# 5. Yield attributes named by variables in the as-clause
names = [var.id for var in statement.optional_vars.elts]
if hasattr(self.target, '__getitem__'):
return (self.target[name] for name in names)
else:
return (getattr(self.target, name) for name in names)
def __exit__(self, type, value, traceback):
pass
if __name__ == '__main__':
user = {'name': 'noah', 'username': 'noah', 'email': '[email protected]'}
with destructure(user) as (name, email):
print u'<{} {}>'.format(name, email)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment