OK, here is the new version. You're the beta tester. The module and the decorator are now named usevarnames
# python 2 or 3
from __future__ import print_function
"""module usevarnames - decorator for magic assignment expression
"""
import sys
__version__ = '0.5.0'
if sys.version_info >=(3 ,):
byte_ord = lambda x :x
else :
byte_ord = ord
import collections
import functools
import opcode
CALL, UNPACK, STOREF, STOREN = (opcode.opmap[s] for s in
("CALL_FUNCTION", "UNPACK_SEQUENCE", "STORE_FAST", "STORE_NAME"))
class VarnamesError (Exception ): pass
def assignment_varnames (code, lasti):
"""Generate variable names extracted from a statement of the form
x, y, z = function(...)
in a code objet @code where @lasti is the index of the
CPython bytecode instruction where the function is called.
"""
errmsg ="simple assignment syntax 'x, y, z = ...' expected"
co = code.co_code
i =lasti
if byte_ord(co[i]) != CALL: raise VarnamesError(errmsg)
i += 3
if byte_ord(co[i]) == UNPACK:
nvars = byte_ord(co[i+1]) + byte_ord(co[i+2]) * 256
i += 3
else :nvars = 1
for j in range(nvars):
k = byte_ord(co[i])
oparg = byte_ord(co[i+1]) + byte_ord(co[i+2]) * 256
if k == STOREF: yield code.co_varnames[oparg]
elif k == STOREN : yield code.co_names[oparg]
else: raise VarnamesError(errmsg)
i += 3
def usevarnames(func):
"""usevarnames(function) -> decorated function
This decorator restricts the usage of a function
to simple assignment statements such as
x, y, z = function(...)
A counterpart is that the function's body can
use the function varnames() to obtain the assigned names.
It means that within the body of the function, the
expression
varnames()
will return the tuple ('x', 'y', 'z') of the variable
names being assigned.
The function must return a number of values corresponding
to the number of variables being assigned. If there are
two variables or more, the function can return any iterable
with this number of arguments.
"""
@functools.wraps(func)
def wrapper (*args, **kwargs):
f = sys._getframe(1)
try:
code ,lasti = f.f_code, f.f_lasti
finally :
del f
names = tuple(assignment_varnames(code, lasti))
_STACK.appendleft(names)
try:
return func(*args, **kwargs)
finally:
_STACK.popleft()
return wrapper
_STACK = collections.deque()
def varnames():
return _STACK[0]
if __name__ == "__main__":
@usevarnames
def attrof(obj):
attrs = varnames()
vals = tuple(getattr(obj, a) for a in attrs)
if len(vals) > 1: return vals
elif vals: return vals[0]
import os, telnetlib
Telnet = attrof(telnetlib)
print(Telnet)
mkdir, unlink, chdir = attrof(os)
print(mkdir, unlink, chdir)
print(_STACK)