[zopeorg-checkins] CVS: Products/PythonMethod/zbytecodehacks - ChangeLog:1.1 Makefile:1.1 README:1.1 TODO:1.1 VSExec.py:1.1 __init__.py:1.1 attr_freeze.py:1.1 closure.py:1.1 code_editor.py:1.1 common.py:1.1 cyclehandle.py:1.1 dbc.py:1.1 find_function_call.py:1.1 iif.py:1.1 inline.py:1.1 label.py:1.1 macro.py:1.1 macros.py:1.1 opbases.py:1.1 ops.py:1.1 rationalize.py:1.1 tailr.py:1.1 version:1.1 xapply.py:1.1
Sidnei da Silva
sidnei at x3ng.com.br
Fri May 30 11:17:50 EDT 2003
Update of /cvs-zopeorg/Products/PythonMethod/zbytecodehacks
In directory cvs.zope.org:/tmp/cvs-serv19195/PythonMethod/zbytecodehacks
Added Files:
ChangeLog Makefile README TODO VSExec.py __init__.py
attr_freeze.py closure.py code_editor.py common.py
cyclehandle.py dbc.py find_function_call.py iif.py inline.py
label.py macro.py macros.py opbases.py ops.py rationalize.py
tailr.py version xapply.py
Log Message:
Adding products needed for migration of NZO
=== Added File Products/PythonMethod/zbytecodehacks/ChangeLog ===
1999-12-11 Evan Simpson <evan at 4-am.com>
* Tupleizing and UntupleFunction now handle nested function definitions
* Subdirectories reverted to v0.5 bytecodehacks, since the only change
I've ever made was to accidentally change line endings in them to CRLF
1999-09-09 Evan Simpson <evan at 4-am.com>
* Tupleizing a function now omits globals
* New UntupleFunction re-applies globals, with automatic initialization
of variables to None, since there's no way to check if they exist.
It also includes $functions, and checks __builtins__ handling.
* Moved all bytecode manipulation into Munge_window class, which uses
op.execute to maintain information about who did what to the stack.
* Added Munger which re-enables creation of dictionary literals.
* Made all Mungers load frequently used functions from the global dict
into the local instead of storing them in co_consts.
* Simplified GuardedOps and turned off test Guard.
* Wrote lots of docstring.
1999-08-28 Evan Simpson <evan at 4-am.com>
* Ripped out Fleshy acquisition-style class and added
CycleHandle in an attempt to improve speed.
* code_editor.py: Added "as_tuple" to Function and EditableCode
to provide (hopefully) pickleable representations. Their __init__s
now accept these tuples.
* Added VSExec.py (the point of all this), which provides facilities
for compiling arbitrary blocks of code with heavy restrictions.
1999-06-11 Michael Hudson <mwh21 at cam.ac.uk>
* a monumental amount has changed. I haven't been keeping the
ChangeLog up to date, sorry.
1999-05-16 Michael Hudson <mwh21 at cam.ac.uk>
* doc/bch.tex: documented macro and macros.
* macros.py: added basic library of macros.
* setq2.py: does same job as setq.py, but uses the new macro
package.
* macro.py It's a macro packages of sorts. Needs documentation.
1999-05-15 Michael Hudson <mwh21 at cam.ac.uk>
* inline.py: Substantially rewritten to use find_function_call,
and to support keyword arguments. No varags yet.
* setq.py Added changes written by Christian Tismer (now converts
globals to locals)
1999-05-13 Michael Hudson <mwh21 at cam.ac.uk>
* Release 0.11 - cleaned up production of documentation following
advice from the documentation master, Fred L. Drake.
1999-05-12 Michael Hudson <mwh21 at cam.ac.uk>
* Release 0.10.
* doc/ There's documentation (gasp)
1999-05-10 Michael Hudson <mwh21 at cam.ac.uk>
* inline.py: Python now has inline functions! Bet you never
expected that.
* It's all changing again! Much polish, some docstrings,
everything rewritten to use code_editor that wasn't already, many
style fixes.
1999-05-06 Michael Hudson <mwh21 at cam.ac.uk>
* attr_freeze.py: implement an attribute freezer that works.
* xapply2.py: implement xapply for functions (again!) using the
new code editing framework from code_editor.py.
* code_editor.py: That's more like it!
1999-05-04 Michael Hudson <mwh21 at cam.ac.uk>
* attr_freeze.py: implements a (buggy) attempt at freezing
attribute references.
* read_code.py,opbases.py,ops.py,write_ops.py,
common.py,__init__.py: Much stuff added/changed. It not pretty or
internally consistent yet. I might bash on it some more some
time. I'm afraid I don't feel like explaining myself properly yet
either.
1999-05-02 Michael Hudson <mwh21 at cam.ac.uk>
* README,ChangeLog: Added.
* xapply.py: Added, following prompting by Christian Tismer.
* closure.py: Made improvements suggested by Tim Peters.
=== Added File Products/PythonMethod/zbytecodehacks/Makefile ===
SUBDIRS=tests code_gen doc
clean: clean-local clean-recursive
release: version src-release doc-release
clean-local:
$(RM) *.pyc *~ *.pyo
clean-recursive:
for i in $(SUBDIRS); do \
(cd $$i && make clean); \
done
src-release: clean
cd .. && ./mkdist.sh
doc-release: clean
cd doc && make release
=== Added File Products/PythonMethod/zbytecodehacks/README ===
Welcome to the bytecodehacks!
There's docmentation in doc/; To build it you need an unpacked python
source distribution somewhere, and for html output you also need
latex2html.
Build docs like this:
$(path to Python source)/Doc/tools/mkhowto --$(format) (--a4) bch.tex
You can get built html docs at
ftp://starship.python.net/pub/crew/mwh/bytecodehacks-doc-$(VERSION).tar.gz.
The bytecodehacks rewrite the bytecode of functions to do unlikely
things in Python.
The package (and that's how it's distributed) splits into two parts -
the byte code editing routines and the "bytecodehacks" that are
usuable without a degree in python arcanery, although one might help
understand some of the consequences.
Some highlights:
bytecodehacks.closure - bind global references to constants
bytecodehacks.xapply - a sort-of lazy apply
bytecodehacks.setq - this one should interest people!
bytecodehacks.inline - Python gets inline functions
bytecodehacks.macro - Python gets semantic (hygenic) macros!
Please note that these modules are not really bullet-proof, more a
proof-of-concept than anything else.
The are also public domain; do what you like with them. If you find
bugs, or more imaginative uses for these techniques, I'd surely like
to know!
Thanks for taking an interest.
=== Added File Products/PythonMethod/zbytecodehacks/TODO ===
polish attr_freeze.
=== Added File Products/PythonMethod/zbytecodehacks/VSExec.py ===
'''Safe execution of arbitrary code through bytecode munging
CodeBlock is a base class for bytecode munging of code strings. Derived
classes should override 'forbid' and 'Mungers'. The class constructor
takes a function name, a parameter string, and a function body string. It
combines the name, parameters, and body into a function (indented one
space), compiles it, and then examines its bytecode.
Any bytecode whose opcode is a key in 'forbid' with a true value may not
appear in the compiled code.
Each object in 'Mungers' is called with the code window object (at start)
and the variable name information for the function. Mungers can use this
opportunity to examine the code and variables, and perform setup operations.
The value returned from the call is discarded if false, otherwise it is
assumed to be a collection of bytecode-specific functions.
The code window object is passed over the bytecodes, maintaining a stack
of "responsible" opcodes. At any given position, the size of the stack
should be the same as the size of the run-time stack would be when that
part of the code is executing, and the top of the stack would have been
put there by the "responsible" opcode.
At each position, the mungers are examined to see if they have a function
corresponding to the current opcode. If so, the function is called with
the code window object. The function can then use the code window object to
examine or change the bytecode.
Once all processing is done, and if there have not been any errors, the
compiled, edited function is converted into a pickleable tuple, which is
stored as attribute 't' of the resulting instance. In order for this to
work, mungers must not place unpickleable objects into the list of constants.
In order for unpickling to be robust, they should not place any function
objects there either. If a function must be provided for use in the
bytecode, it should be loaded from the global dictionary, preferably with an
'illegal' name.
'''
from string import replace, join
import ops, code_editor
from closure import bind
class Warning(Exception): pass
class ForbiddenOp(Exception): pass
general_special_globals = {}
class Munge_window:
def __init__(self, code, use_stack):
self.code = code
self.opn = -1
self.use_stack = use_stack
if use_stack:
self.stack = []
self.last_empty_stack = None
def insert_code(self, rcode, at = None):
opn = self.opn
# rcode is passed as a (callables, args) pair
rcode = map(apply, rcode[0], rcode[1])
if at is None:
at = opn
self.code[at:at]=rcode
self.opn = opn + len(rcode)
return rcode
def set_code(self, rct = 1, rcode = ()):
'Replace "rct" bytecodes at current position with "rcode"'
opn = self.opn
# rcode is passed as a (callables, args) pair
if rcode:
rcode = map(apply, rcode[0], rcode[1])
self.code[opn:opn+rct] = rcode
if self.use_stack:
stack = self.stack
for op in rcode:
try:
op.execute(stack)
except:
del stack[:]
if not stack:
self.last_empty_stack = op
self.opn = opn + len(rcode) - 1
def advance(self):
self.opn = opn = self.opn + 1
code = self.code
if opn < len(code):
self.op = code.opcodes[opn]
self.opname = self.op.__class__.__name__
return 1
elif opn == len(code):
# Hack!
self.op = None
self.opname = 'finalize'
return 1
def do_op(self):
if self.use_stack and self.op:
op = self.op
stack = self.stack
try:
op.execute(stack)
except:
del stack[:]
if not stack:
self.last_empty_stack = op
def after_code_for(self, stack_idx):
stack = self.stack
if stack_idx < 0 and len(stack) + stack_idx < 0:
whichop = self.last_empty_stack
if whichop is None:
return 0
else:
whichop = stack[stack_idx]
return self.code.index(whichop)+1
def assert_stack_size(self, size, ctxt):
if self.use_stack and len(self.stack) != size:
raise Warning, ('PythonMethod Bug: %s objects were on the stack '
'%s when only %s should be.' % (len(self.stack), ctxt, size))
class CodeBlock:
'''Compile a string containing Python code, with restrictions'''
forbid = {}
Mungers = ()
globals = {'__builtins__': None}
forbidden = "Forbidden operation %s at line %d"
def __init__(self, name, paramstr, src):
self.name = name
self.src = src
self.paramstr = paramstr
self.f = None
# Exec 'src' as a function body, indented one space, with empty globals
# Waaa: triple-quoted strings will get indented too
defns = {'__builtins__': None}
defblk = 'def %s(%s):\n %s\n' % (name, self.paramstr
, replace(self.src, '\n', '\n '))
exec defblk in defns
block = defns[name]
# Crack open the resulting function and munge it.
f = code_editor.Function(block)
f.func_globals = self.globals
self.warnings, self.errors = [], []
self.munge_data = {}
self.munge(f.func_code)
if not self.errors: self.t = f.as_tuple()
def munge(self, fc, depth=0):
# Recurse into nested functions first
for subcode in fc.subcodes:
self.munge(subcode, depth+1)
# Make the current recursion depth accessible
self.depth = depth
code = fc.co_code
warnings, errors = self.warnings, self.errors
# Initialize the Munge objects
mungers = []
window = Munge_window(code, 1)
margs = (self, window, fc)
for M in self.Mungers:
try:
mungers.append(apply(M, margs))
except Exception, e:
errors.append(e.__class__.__name__ + ', ' + str(e))
# Try to collect all initialization errors before failing
if errors:
return
# Mungers which only perform an initial pass should return false
mungers = filter(None, mungers)
line = 0 ; forbid = self.forbid
while window.advance():
op, opname = window.op, window.opname
if isinstance(op, ops.SET_LINENO):
line = op.arg
window.do_op()
elif op and forbid.get(op.op, 0):
errors.append(self.forbidden % (opname, line))
window.do_op() #???
else:
for m in mungers:
handler = getattr(m, opname, None)
if handler:
try:
# Return true to prevent further munging
handled = handler(window)
except ForbiddenOp:
errors.append(self.forbidden % (opname, line))
except Exception, e:
raise
errors.append(e.__class__.__name__ + ', ' + str(e))
else:
if not handled:
continue
break
else:
window.do_op()
def __call__(self, *args, **kargs):
F = code_editor.Function(self.t)
F.func_globals = self.globals
self.__call__ = f = F.make_function()
return apply(f, args, kargs)
def _print_handler(printlist, *txt):
add = printlist.append
if len(txt):
if printlist and printlist[-1:] != ['\n']:
add(' ')
add(str(txt[0]))
else:
add('\n')
def _join_printed(printlist):
return join(printlist, '')
_join_printed = bind(_join_printed, join=join, map=map, str=str)
general_special_globals['$print_handler'] = _print_handler
general_special_globals['$join_printed'] = _join_printed
general_special_globals['$printed'] = None
class Printing:
'''Intercept print statements
Print statements are either converted to no-ops, or replaced with
calls to a handler. The default handler _print_handler appends the
intended output to a list. 'printed' is made into a reserved name
which can only be used to read the result of _join_printed.
_print_handler and _join_printed should be provided in the global
dictionary as $print_handler and $join_printed'''
lfnames = (('$printed',), ('$print_handler',), ('$join_printed',))
print_prep = ( (ops.LOAD_FAST, ops.LOAD_FAST), (lfnames[1], lfnames[0]) )
get_printed = ( (ops.LOAD_FAST, ops.LOAD_FAST, ops.CALL_FUNCTION),
(lfnames[2], lfnames[0], (1,)) )
init_printed = ( (ops.BUILD_LIST, ops.STORE_GLOBAL),
((0,), lfnames[0]) )
make_local = (ops.LOAD_GLOBAL, ops.STORE_FAST)
call_print1 = ( (ops.CALL_FUNCTION, ops.POP_TOP), ((1,), ()) )
call_print2 = ( (ops.CALL_FUNCTION, ops.POP_TOP), ((2,), ()) )
def __init__(self, cb, w, fc):
if 'printed' in fc.co_varnames:
raise SyntaxError, '"printed" is a reserved word.'
self.warnings, self.depth = cb.warnings, cb.depth
self.md = cb.munge_data['Printing'] = cb.munge_data.get('Printing', {})
self.names_used = names_used = {}
names = fc.co_names
if 'printed' in names:
names_used[0] = names.index('printed')
names_used[2] = 1
else:
self.LOAD_GLOBAL = None
def finalize(self, w):
names_used = self.names_used
if names_used:
# Load special names used by the function (and $printed always)
ln = self.lfnames
names_used[0] = 1
for i in names_used.keys():
w.insert_code( (self.make_local, (ln[i], ln[i])), 0)
# Inform higher-level mungers of name usage
self.md.update(names_used)
if self.md and self.depth == 0:
# Initialize the $printed variable in the base function
w.insert_code(self.init_printed, 0)
if not self.md.has_key(2):
self.warnings.append(
"Prints, but never reads 'printed' variable.")
def PRINT_ITEM(self, w):
# Load the printing function before the code for the operand.
w.insert_code(self.print_prep, w.after_code_for(-2))
w.assert_stack_size(1, "at a 'print' statement")
# Instead of printing, call our function and discard the result.
w.set_code(1, self.call_print2)
self.names_used[1] = 1
return 1
def PRINT_NEWLINE(self, w):
w.assert_stack_size(0, "at a 'print' statement")
w.insert_code(self.print_prep)
w.set_code(1, self.call_print1)
self.names_used[1] = 1
return 1
def LOAD_GLOBAL(self, w):
if w.op.arg==self.names_used[0]:
# Construct the print result instead of getting non-existent var.
w.set_code(1, self.get_printed)
return 1
def PublicNames(cb, w, fc):
'''Restrict access to all but public names
Forbid use of any multi-character name starting with _
'''
protected = []
for name in fc.co_names:
if name[:1]=='_' and len(name)>1:
protected.append(name)
if protected:
raise SyntaxError, ('Names starting with "_" are not allowed (%s).'
% join(protected, ', '))
class AllowMapBuild:
'''Allow literal mappings
Construction of literal dictionaries requires STORE_SUBSCR, which is
usually forbidden. Allow it by checking the stack.'''
def __init__(self, cb, w, fc):
pass
def STORE_SUBSCR(self, w):
if isinstance(w.stack[-2], ops.BUILD_MAP):
return
raise ForbiddenOp
def _get_call(w):
load_guard = ((ops.LOAD_FAST, ops.LOAD_ATTR), (('$guard',), (guard,)))
# Load the binary guard function before its parameters are computed.
iops = w.insert_code(load_guard, w.after_code_for(-3))
# Fix the execution stack to refer to the loaded function.
if w.use_stack: w.stack[-2:-2] = iops[1:]
# Call guard function instead of performing binary op
w.set_code(1, cf2)
return 1
def _SLICE(w):
load_guard = ((ops.LOAD_FAST, ops.LOAD_ATTR, ops.ROT_TWO),
(('$guard',), ('getslice',), ()))
# Load the Slice guard and switch it with the argument.
w.insert_code(load_guard, w.opn+1)
# Call the slice guard after performing the slice.
w.insert_code(cf1, w.opn+4)
return 1
class _GuardedOps:
def __call__(self, cb, w, fc):
localize_guard = ( (ops.LOAD_GLOBAL, ops.STORE_FAST), (('$guard',),('$guard',)) )
# Insert setup code; no need to fix stack
w.insert_code(localize_guard, 0)
return self
def GuardedOps(guards):
'''Allow operations to be Guarded, by replacing them with guard calls.
Construct a munging object which will replace specific opcodes with
calls to methods of a guard object. The guard object must appear in
the global dictionary as $guard.'''
gops = _GuardedOps()
opmap = (('mul', 'BINARY_MULTIPLY')
,('div', 'BINARY_DIVIDE')
,('power', 'BINARY_POWER')
,('getattr', 'LOAD_ATTR')
,('getitem', 'BINARY_SUBSCR'))
cf1 = ((ops.CALL_FUNCTION,), ((1,),))
cf2 = ((ops.CALL_FUNCTION,), ((2,),))
for guard, defname in opmap:
if hasattr(guards, guard):
setattr(gops, defname, bind(_get_call, guard=guard, cf2=cf2))
if hasattr(guards, 'getslice'):
gops.SLICE_3 = gops.SLICE_2 = gops.SLICE_1 = gops.SLICE_0 = bind(_SLICE, cf1=cf1)
return gops
class SafeBlock(CodeBlock):
forbid = {ops.STORE_FAST.op: 0, ops.STORE_GLOBAL.op: 0, ops.STORE_SUBSCR.op: 0}
for opclass in ops._bytecodes.values():
if forbid.get(opclass.op, 1):
opname = opclass.__name__
if (opname[:6]=='STORE_' or opname[:7]=='DELETE_' or opname[:5]=='EXEC_'):
forbid[opclass.op] = 1
Mungers = [Printing, PublicNames, AllowMapBuild]
def UntupleFunction(t, special_globals, **globals):
import new
globals.update(general_special_globals)
globals.update(special_globals)
globals['global_exists'] = defined = globals.has_key
if not defined('__builtins__'):
globals['__builtins__'] = {}
t = list(t)
# Handle nested functions and lambdas
t_code = t[2]
if len(t_code) == 13:
sub_codes = [t_code]
funstack = [sub_codes]
while funstack:
if len(t_code) == 13:
# This has nested code objects, so make it mutable
sub_codes[0] = t_parent = t_code = list(t_code)
# Put the list of nested codes on the stack for processing
sub_codes = list(t_code.pop())
funstack.append(sub_codes)
else:
# This code tuple is fully processed, so untuple it
func_code = apply(new.code, tuple(t_code))
# Find the first placeholder () in the parent's constants
t_consts = list(t_parent[5])
# Replace the placeholder with the code object
t_consts[t_consts.index(())] = func_code
t_parent[5] = tuple(t_consts)
# Clear it from the stack
del sub_codes[0]
# Get the next code tuple to process
if not sub_codes:
# Back up one level
funstack.pop()
sub_codes = funstack[-1]
if len(funstack) > 1:
t_parent = funstack[-2][0]
else:
funstack = None
t_code = sub_codes[0]
f = new.function(apply(new.code, tuple(t_code)), globals, t[0])
f.func_defaults = t[3] and tuple(t[3])
f.func_doc = t[1]
return f
if 0:
#demonstrator Guard
class Guard:
def mul(self, a, b):
if a*b>10:
return 'ha!'
else:
return a*b
def getslice(self, s):
if len(s)>2:
raise "Too long a slice!"
return s
def test(p, c):
sb = SafeBlock('f', p, c)
print sb.errors, sb.warnings
f = code_editor.Function(sb.t)
for c in f.func_code.co_code:
print c
for subcode in f.func_code.subcodes:
for c in subcode.co_code:
print ' ', c
return sb
if __name__ == '__main__':
sb = test('x', '''\
print x
def plus1(x):
print x+1
plus1(x)
return printed''')
f = UntupleFunction(sb.t, {})
#from dis import dis
#dis(f)
print f(2),
print f(3),
=== Added File Products/PythonMethod/zbytecodehacks/__init__.py ===
__all__=[
'closure',
'xapply',
'common',
'inline',
'code_editor',
'opbases',
'ops',
'attr_freeze',
'code_gen']
=== Added File Products/PythonMethod/zbytecodehacks/attr_freeze.py ===
from code_editor import Function
from ops import LOAD_GLOBAL, LOAD_ATTR, LOAD_CONST
def freeze_one_attr(cs,code,attr,value):
looking_for=0
is_global=1
inserted=0
i = 0
while i < len(cs):
op=cs[i]
if is_global:
if op.__class__ is LOAD_GLOBAL:
if code.co_names[op.arg]==attr[looking_for]:
looking_for=looking_for+1
is_global=0
else:
if op.__class__ is LOAD_ATTR \
and code.co_names[op.arg]==attr[looking_for]:
looking_for=looking_for+1
if looking_for == len(attr):
inserted=1
newop=LOAD_CONST(len(code.co_consts))
cs[i-len(attr)+1:i+1]=[newop]
i=i-len(attr)
looking_for=0
is_global=1
else:
looking_for=0
is_global=1
i=i+1
if inserted:
code.co_consts.append(value)
return cs
class Ref:
def __init__(self,name=()):
self.name=name
def __getattr__(self,attr):
return Ref(self.name+(attr,))
def __call__(self):
return self.name
def __repr__(self):
return `self.name`
def freeze_attrs(func,*vars):
func=Function(func)
code=func.func_code
cs=code.co_code
if len(vars)%2 <> 0:
raise TypeError, "wrong number of arguments"
for i in range(0,len(vars),2):
freeze_one_attr(cs,code,vars[i](),vars[i+1])
return func.make_function()
=== Added File Products/PythonMethod/zbytecodehacks/closure.py ===
"""\
closure
implements a form of closures by abusing the co_consts field of a code
object.
exports: bind, bind_locals, bind_now
and contains two examples: make_adder, make_balance
"""
from code_editor import Function
from ops import *
def scan_for_STORE(func,name):
for i in func.func_code.co_code:
if i.__class__ in [STORE_FAST,STORE_NAME,STORE_GLOBAL] \
and i.name == name:
return 1
return 0
def bind(function,newname=None,**vars):
"""\
bind(function[,newname],var1=value1,var2=value2,...) -> function
returns a new function (optionally renamed) where every reference to
one of var1, var2, etc is replaced by a reference to the respective
valueN."""
func = Function(function)
code = func.func_code
cs = func.func_code.co_code
name2index = {}
mutated = {}
for name in vars.keys():
mutated[name] = scan_for_STORE(func,name)
if 0 in code.co_consts:
zeroIndex = code.co_consts.index(0)
else:
zeroIndex = len(code.co_consts)
code.co_consts.append(0)
i = 0
while i < len(cs):
op = cs[i]
i = i + 1
# should LOAD_NAME be here??? tricky, I'd say
if op.__class__ in [LOAD_GLOBAL,LOAD_NAME,LOAD_FAST]:
if not vars.has_key(op.name):
continue
if mutated[name]:
if not name2index.has_key(op.name):
name2index[op.name]=len(code.co_consts)
code.co_consts.append([vars[op.name]])
cs[i-1:i] = [LOAD_CONST(name2index[op.name]),
LOAD_CONST(zeroIndex),
BINARY_SUBSCR()]
i = i + 2
else:
if not name2index.has_key(op.name):
name2index[op.name]=len(code.co_consts)
code.co_consts.append(vars[op.name])
cs[i-1] = LOAD_CONST(name2index[op.name])
elif op.__class__ in [STORE_FAST,STORE_NAME,STORE_GLOBAL]:
if not vars.has_key(op.name):
continue
if not mutated[name]:
continue # shouldn't be reached
cs[i-1:i] = [LOAD_CONST(name2index[op.name]),
LOAD_CONST(zeroIndex),
STORE_SUBSCR()]
i = i + 2
if newname is not None:
func.func_name = newname
return func.make_function()
bind=Function(bind)
bind.func_code.co_varnames[0]='$function'
bind.func_code.co_varnames[1]='$newname'
bind=bind.make_function()
def bind_locals(func):
"""bind_locals(func) -> function
returns a new function where every global variable reference in func
is replaced, if possible, by a reference to a local variable in the
callers context."""
try:
raise ""
except:
import sys
frame = sys.exc_traceback.tb_frame.f_back
name = func.func_name+'+'
l = apply(bind,(func,name),frame.f_locals)
frame = None
return l
def bind_now(func):
"""bind_now(func) -> function
returns a new function where every global variable reference in func
is replaced, if possible, by a reference to a variable in the callers
context."""
try:
raise ""
except:
import sys
frame = sys.exc_traceback.tb_frame.f_back
l = apply(bind,(func,),frame.f_locals)
g = apply(bind,(l,),frame.f_globals)
frame = None
return g
## examples
def make_adder(n):
"""make_adder(n) -> function
return a monadic function that adds n to its argument."""
def adder(x):
return x+n
return bind_locals(adder)
def make_balance(initial_amount):
"""make_balance(initial_amount) -> function
demonstrates an object with state, sicp style."""
def withdraw(amount):
if current[0]<amount:
raise "debt!"
else:
current[0]=current[0]-amount
return current[0]
return bind(withdraw,current=[initial_amount])
=== Added File Products/PythonMethod/zbytecodehacks/code_editor.py ===
# the third attempt; maybe it'll work sometime.
# interface I want:
# mc=MutableCode(<code object>)
# behaves like list of opcodes, eg
# len(mc) => number of bytecodes in code
# mc[i] returns some representation of the ith opcode
# mc.assemble() => codestring, or maybe code object.
import types,StringIO,struct,new
import ops
from cyclehandle import CycleHandle
class CodeString(CycleHandle):
def __init__(self,cs=None,bytecodes=None):
self._set_workers(CodeStringWorker(cs, bytecodes))
class CodeStringWorker:
def __init__(self,cs,bytecodes):
self.labels=[]
self.byte2op={}
self.opcodes=[]
if bytecodes is None:
bytecodes = ops._bytecodes
if type(cs) is type(""):
self.disassemble_no_code(cs,bytecodes)
else:
self.disassemble(cs,bytecodes)
def disassemble(self,code,bytecodes):
self.labels = []
self.byte2op = {}
self.opcodes = []
self.code = code
cs=StringIO.StringIO(code.co_code)
i, op, n = 0, 0, len(code.co_code)
while i < n:
self.byte2op[i]=op
byte=cs.read(1)
self.opcodes.append(bytecodes[byte](cs,self))
i = cs.tell()
op = op + 1
del self.code
for label in self.labels:
label.resolve(self)
def disassemble_no_code(self,codestring,bytecodes):
self.labels = []
self.byte2op = {}
self.opcodes = []
cs=StringIO.StringIO(codestring)
i, op, n = 0, 0, len(codestring)
while i < n:
self.byte2op[i]=op
byte=cs.read(1)
self.opcodes.append(bytecodes[byte](cs,self))
i = cs.tell()
op = op + 1
for label in self.labels:
label.resolve(self)
def add_label(self,label):
self.labels.append(label)
def find_labels(self,index):
labelled=self.opcodes[index]
pointees=[]
for l in self.labels:
if l.op == labelled:
pointees.append(l)
return pointees
def __getitem__(self,index):
return self.opcodes[index]
def __setitem__(self,index,value):
# find labels that point to the removed opcode
pointees=self.find_labels(index)
if self.opcodes[index].is_jump():
self.labels.remove(self.opcodes[index].label)
self.opcodes[index]=value
for l in pointees:
l.op=value
if value.is_jump():
self.labels.append(value.label)
def __delitem__(self,index):
# labels that pointed to the deleted item get attached to the
# following insn (unless it's the last insn - in which case I
# don't know what you're playing at, but I'll just point the
# label at what becomes the last insn)
pointees=self.find_labels(index)
if index + 1 == len(self.opcodes):
replacement = self.opcodes[index]
else:
replacement = self.opcodes[index + 1]
for l in pointees:
l.op=replacement
going=self.opcodes[index]
if going.is_jump():
self.labels.remove(going.label)
del self.opcodes[index]
def __getslice__(self,lo,hi):
return self.opcodes[lo:hi]
def __setslice__(self,lo,hi,values):
# things that point into the block being stomped on get
# attached to the start of the new block (if there are labels
# pointing into the block, rather than at its start, a warning
# is printed, 'cause that's a bit dodgy)
pointees=[]
opcodes = self.opcodes
indices=range(len(opcodes))[lo:hi]
for i in indices:
if opcodes[i].is_jump():
self.labels.remove(opcodes[i].label)
p=self.find_labels(i)
if p and i <> lo:
print "What're you playing at?"
pointees.extend(p)
codes = []
for value in values:
if value.is_jump():
self.labels.append(value.label)
codes.append(value)
opcodes[lo:hi]=codes
replacement = opcodes[min(lo, len(opcodes)-1)]
for l in pointees:
l.op = replacement
def __delslice__(self,lo,hi):
self.__setslice__(lo, hi, [])
def __len__(self):
return len(self.opcodes)
def append(self,value):
self.opcodes.append(value)
if value.is_jump():
self.labels.append(value.label)
def insert(self,index,value):
self.opcodes.insert(index,value)
if value.is_jump():
self.labels.append(value.label)
def remove(self,x):
del self[self.opcodes.index(x)]
def index(self,x):
return self.opcodes.index(x)
def assemble(self):
out=StringIO.StringIO()
for i in self:
i.byte=out.tell()
out.write(i.assemble(self))
for l in self.labels:
l.write_refs(out)
out.seek(0)
return out.read()
def set_code(self, code):
self.code = code
class EditableCode(CycleHandle):
def __init__(self,code=None):
self._set_workers(EditableCodeWorker(code))
class EditableCodeWorker:
# bits for co_flags
CO_OPTIMIZED = 0x0001
CO_NEWLOCALS = 0x0002
CO_VARARGS = 0x0004
CO_VARKEYWORDS = 0x0008
AUTO_RATIONALIZE = 0
def __init__(self,code=None):
if code is None:
self.init_defaults()
elif type(code) in (type(()), type([])):
self.init_tuple(code)
else:
self.init_code(code)
self.co_code.set_code(self)
def name_index(self,name):
if name not in self.co_names:
self.co_names.append(name)
return self.co_names.index(name)
def local_index(self,name):
if name not in self.co_varnames:
self.co_varnames.append(name)
return self.co_varnames.index(name)
def rationalize(self):
from rationalize import rationalize
rationalize(self)
def init_defaults(self):
self.co_argcount = 0
self.co_stacksize = 0 # ???
self.co_flags = 0 # ???
self.co_consts = []
self.co_names = []
self.co_varnames = []
self.co_filename = '<edited code>'
self.co_name = 'no name'
self.co_firstlineno = 0
self.co_lnotab = '' # ???
self.co_code = CodeString()
self.subcodes = []
def init_code(self,code):
self.co_argcount = code.co_argcount
self.co_stacksize = code.co_stacksize
self.co_flags = code.co_flags
self.co_consts = consts = list(code.co_consts)
self.co_names = list(code.co_names)
self.co_varnames = list(code.co_varnames)
self.co_filename = code.co_filename
self.co_name = code.co_name
self.co_firstlineno = code.co_firstlineno
self.co_lnotab = code.co_lnotab
self.co_code = CodeString(code)
self.subcodes = subcodes = []
from types import CodeType
for i in range(len(consts)):
if type(consts[i]) == CodeType:
subcodes.append(EditableCode(consts[i]))
consts[i] = ()
def init_tuple(self,tup):
self.subcodes = []
if len(tup)==13:
self.subcodes = map(EditableCode, tup[-1])
tup = tup[:-1]
( self.co_argcount, ignored, self.co_stacksize, self.co_flags
, self.co_code, co_consts, co_names, co_varnames, self.co_filename
, self.co_name, self.co_firstlineno, self.co_lnotab
) = tup
self.co_consts = list(co_consts)
self.co_names = list(co_names)
self.co_varnames = list(co_varnames)
self.co_code = CodeString(self)
def make_code(self):
if self.AUTO_RATIONALIZE:
self.rationalize()
else:
# hack to deal with un-arg-ed names
for op in self.co_code:
if (op.has_name() or op.has_local()) and not hasattr(op, "arg"):
if op.has_name():
op.arg = self.name_index(op.name)
else:
op.arg = self.local_index(op.name)
return apply(new.code, self.as_tuple()[:12])
def set_function(self, function):
self.function = function
def as_tuple(self):
# the assembling might change co_names or co_varnames - so
# make sure it's done *before* we start gathering them.
bytecode = self.co_code.assemble()
subcodes = []
for subcode in self.subcodes:
subcodes.append(subcode.as_tuple())
return (
self.co_argcount,
len(self.co_varnames),
self.co_stacksize,
self.co_flags,
bytecode,
tuple(self.co_consts),
tuple(self.co_names),
tuple(self.co_varnames),
self.co_filename,
self.co_name,
self.co_firstlineno,
self.co_lnotab,
tuple(subcodes))
class Function(CycleHandle):
def __init__(self,func=None):
self._set_workers(FunctionWorker(func))
class FunctionWorker:
def __init__(self,func):
if func is None:
self.init_defaults()
elif type(func) in (type(()), type([])):
self.init_tuple(func)
else:
self.init_func(func)
self.func_code.set_function(self)
def init_defaults(self):
self.__name = "no name"
self.__doc = None
self.func_code = EditableCode()
self.func_defaults = []
self.func_globals = {} # ???
def init_func(self,func):
self.__name = func.func_name
self.__doc = func.func_doc
self.func_code = EditableCode(func.func_code)
self.func_defaults = func.func_defaults
self.func_globals = func.func_globals
def init_tuple(self,tup):
( self.__name, self.__doc, func_code, self.func_defaults
, self.func_globals
) = tup
self.func_code = EditableCode(func_code)
def __getattr__(self,attr):
# for a function 'f.__name__ is f.func_name'
# so lets hack around to support that...
if attr in ['__name__','func_name']:
return self.__name
if attr in ['__doc__','func_doc']:
return self.__doc
raise AttributeError, attr
def __setattr__(self,attr,value):
if attr in ['__name__','func_name']:
self.__name = value
elif attr in ['__doc__','func_doc']:
self.__doc = value
else:
self.__dict__[attr]=value
def make_function(self):
newfunc = new.function(
self.func_code.make_code(),
self.func_globals,
self.__name)
if not self.func_defaults:
defs=None
else:
defs=tuple(self.func_defaults)
newfunc.func_defaults = defs
newfunc.func_doc = self.__doc
return newfunc
def __call__(self,*args,**kw):
return apply(self.make_function(),args,kw)
def set_method(self, method):
self.method = method
def as_tuple(self):
if not self.func_defaults:
defs=None
else:
defs=tuple(self.func_defaults)
return (self.__name, self.__doc, self.func_code.as_tuple(), defs, {})
class InstanceMethod(CycleHandle):
def __init__(self,meth=None):
self._set_workers(InstanceMethodWorker(meth))
class InstanceMethodWorker:
def __init__(self,meth):
if meth is None:
self.init_defaults()
else:
self.init_meth(meth)
self.im_func.set_method(self)
def init_defaults(self):
self.im_class = None
self.im_func = Function()
self.im_self = None
def init_meth(self,meth):
self.im_class = meth.im_class
self.im_func = Function(meth.im_func)
self.im_self = meth.im_self
def make_instance_method(self):
return new.instancemethod(
self.im_func.make_function(),
self.im_self,self.im_class)
class FunctionOrMethod:
def __init__(self,functionormethod):
if type(functionormethod) is types.FunctionType:
self.is_method = 0
self.function = Function(functionormethod)
elif type(functionormethod) is types.UnboundMethodType:
self.is_method = 1
self.method = InstanceMethod(functionormethod)
self.function = self.method.im_func
def make_function_or_method(self):
if self.is_method:
return self.method.make_instance_method()
else:
return self.function.make_function()
=== Added File Products/PythonMethod/zbytecodehacks/common.py ===
import new
def copy_code_with_changes(codeobject,
argcount=None,
nlocals=None,
stacksize=None,
flags=None,
code=None,
consts=None,
names=None,
varnames=None,
filename=None,
name=None,
firstlineno=None,
lnotab=None):
if argcount is None: argcount = codeobject.co_argcount
if nlocals is None: nlocals = codeobject.co_nlocals
if stacksize is None: stacksize = codeobject.co_stacksize
if flags is None: flags = codeobject.co_flags
if code is None: code = codeobject.co_code
if consts is None: consts = codeobject.co_consts
if names is None: names = codeobject.co_names
if varnames is None: varnames = codeobject.co_varnames
if filename is None: filename = codeobject.co_filename
if name is None: name = codeobject.co_name
if firstlineno is None: firstlineno = codeobject.co_firstlineno
if lnotab is None: lnotab = codeobject.co_lnotab
return new.code(argcount,
nlocals,
stacksize,
flags,
code,
consts,
names,
varnames,
filename,
name,
firstlineno,
lnotab)
code_attrs=['argcount',
'nlocals',
'stacksize',
'flags',
'code',
'consts',
'names',
'varnames',
'filename',
'name',
'firstlineno',
'lnotab']
=== Added File Products/PythonMethod/zbytecodehacks/cyclehandle.py ===
class CycleHandle:
'''CycleHandles are proxies for cycle roots
A CycleHandle subclass should create one or more workers, and pass them
to _set_workers. These workers can then participate in cycles, as long
as deleting all of the worker's attributes will break the cycle. When a
CycleHandle instance goes away, it deletes all attributes of all of
its workers. You could also explicitly call drop_workers.
For example,
>>> class Ham:
... def __del__(self):
... print 'A ham has died!'
...
>>> ct = CycleHandle()
>>> ct._set_workers(Ham(), Ham())
>>> ct._workers[0].ham2 = ct._workers[1]
>>> ct._workers[1].ham1 = ct._workers[0]
>>> del ct
A ham has died!
A ham has died!
'''
_workers = ()
def _set_workers(self, *workers):
self.__dict__['_workers'] = workers
def _not_mutable(self, *x):
raise TypeError, 'CycleHandle is not mutable'
_not_mutable_defs = ('__delslice__', '__setslice__', '__delitem__'
, '__setitem__', '__delattr__', '__setattr__')
def __getattr__(self, attr):
for worker in self._workers:
if hasattr(worker, attr):
return getattr(worker, attr)
if attr in self._not_mutable_defs:
return self._not_mutable
raise AttributeError, attr
def _drop_workers(self):
for worker in self._workers:
worker.__dict__.clear()
self.__dict__['_workers'] = ()
def __del__(self, drop_workers=_drop_workers):
drop_workers(self)
def _test():
import doctest, cyclehandle
return doctest.testmod(cyclehandle)
if __name__ == "__main__":
_test()
=== Added File Products/PythonMethod/zbytecodehacks/dbc.py ===
from code_editor import Function
from ops import *
import dis,new,string
PRECONDITIONS = 1
POSTCONDITIONS = 2
INVARIANTS = 4
EVERYTHING = PRECONDITIONS|POSTCONDITIONS|INVARIANTS
if __debug__:
__strength__ = PRECONDITIONS|POSTCONDITIONS
else:
__strength__ = 0
# TODO: docs, sort out inheritance.
if __debug__:
def add_contracts(target_class,contract_class,strength=None):
if strength is None:
strength = __strength__
newmethods = {}
contractmethods = contract_class.__dict__
if strength & INVARIANTS:
inv = contractmethods.get("class_invariants",None)
for name, meth in target_class.__dict__.items():
if strength & PRECONDITIONS:
pre = contractmethods.get("pre_"+name,None)
if pre is not None:
meth = add_precondition(meth,pre)
if strength & POSTCONDITIONS:
post = contractmethods.get("post_"+name,None)
if post is not None:
meth = add_postcondition(meth,post)
if (strength & INVARIANTS) and inv \
and type(meth) is type(add_contracts):
if name <> '__init__':
meth = add_precondition(meth,inv)
meth = add_postcondition(meth,inv)
newmethods[name] = meth
return new.classobj(target_class.__name__,
target_class.__bases__,
newmethods)
def add_precondition(meth,cond):
meth = Function(meth)
cond = Function(cond)
mcs = meth.func_code.co_code
ccs = cond.func_code.co_code
nlocals = len(meth.func_code.co_varnames)
nconsts = len(meth.func_code.co_consts)
nnames = len(meth.func_code.co_names)
nargs = meth.func_code.co_argcount
retops = []
for op in ccs:
if op.__class__ is RETURN_VALUE:
# RETURN_VALUEs have to be replaced by JUMP_FORWARDs
newop = JUMP_FORWARD()
ccs[ccs.index(op)] = newop
retops.append(newop)
elif op.op in dis.hasname:
op.arg = op.arg + nnames
elif op.op in dis.haslocal:
if op.arg >= nargs:
op.arg = op.arg + nlocals
elif op.op in dis.hasconst:
op.arg = op.arg + nconsts
new = POP_TOP()
mcs.insert(0,new)
mcs[0:0] = ccs.opcodes
for op in retops:
op.label.op = new
meth.func_code.co_consts.extend(cond.func_code.co_consts)
meth.func_code.co_varnames.extend(cond.func_code.co_varnames)
meth.func_code.co_names.extend(cond.func_code.co_names)
return meth.make_function()
def add_postcondition(meth,cond):
""" a bit of a monster! """
meth = Function(meth)
cond = Function(cond)
mcode = meth.func_code
ccode = cond.func_code
mcs = mcode.co_code
ccs = ccode.co_code
nlocals = len(mcode.co_varnames)
nconsts = len(mcode.co_consts)
nnames = len(mcode.co_names)
nargs = ccode.co_argcount
cretops = []
Result_index = len(meth.func_code.co_varnames)
mcode.co_varnames.append('Result')
old_refs = find_old_refs(cond)
for op in ccs:
if op.__class__ is RETURN_VALUE:
newop = JUMP_FORWARD()
ccs[ccs.index(op)] = newop
cretops.append(newop)
elif op.op in dis.hasname:
if cond.func_code.co_names[op.arg] == 'Result' \
and op.__class__ is LOAD_GLOBAL:
ccs[ccs.index(op)] = LOAD_FAST(Result_index)
else:
op.arg = op.arg + nnames
elif op.op in dis.haslocal:
if op.arg >= nargs:
op.arg = op.arg + nlocals + 1 # + 1 for Result
elif op.op in dis.hasconst:
op.arg = op.arg + nconsts
# lets generate the prologue code to save values for `Old'
# references and point the LOAD_FASTs inserted by
# find_old_refs to the right locations.
prologue = []
for ref, load_op in old_refs:
if ref[0] in mcode.co_varnames:
prologue.append(LOAD_FAST(mcode.co_varnames.index(ref[0])))
else:
prologue.append(LOAD_GLOBAL(mcode.name_index(ref[0])))
for name in ref[1:]:
prologue.append(LOAD_ATTR(mcode.name_index(name)))
lname = string.join(ref,'.')
lindex = len(mcode.co_varnames)
mcode.co_varnames.append(lname)
prologue.append(STORE_FAST(lindex))
load_op.arg = lindex
mcs[0:0] = prologue
mretops = []
for op in mcs:
if op.__class__ is RETURN_VALUE:
newop = JUMP_FORWARD()
mcs[mcs.index(op)] = newop
mretops.append(newop)
n = len(mcs)
# insert the condition code
mcs[n:n] = ccs.opcodes
# store the returned value in Result
store_result = STORE_FAST(Result_index)
mcs.insert(n, store_result)
# target the returns in the method to this store
for op in mretops:
op.label.op = store_result
# the post condition will leave a value on the stack; lose it.
# could just strip off the LOAD_CONST & RETURN_VALLUE at the
# end of the function and scan for RETURN_VALUES in the
# postcondition as a postcondition shouldn't be returning
# things (certainly not other than None).
new = POP_TOP()
mcs.append(new)
# redirect returns in the condition to the POP_TOP just
# inserted...
for op in cretops:
op.label.op = new
# actually return Result...
mcs.append(LOAD_FAST(Result_index))
mcs.append(RETURN_VALUE())
# and add the new constants and names (to avoid core dumps!)
mcode.co_consts .extend(ccode.co_consts )
mcode.co_varnames.extend(ccode.co_varnames)
mcode.co_names .extend(ccode.co_names )
return meth.make_function()
def find_old_refs(func):
chaining = 0
refs = []
ref = []
code = func.func_code
cs = code.co_code
i = 0
while i < len(cs):
op=cs[i]
if not chaining:
if op.__class__ is LOAD_GLOBAL:
if code.co_names[op.arg]=='Old':
chaining=1
else:
if op.__class__ is LOAD_ATTR:
ref.append(code.co_names[op.arg])
else:
newop = LOAD_FAST(0)
cs[i-len(ref)-1:i] = [newop]
i = i - len(ref)
refs.append((ref,newop))
ref = []
chaining = 0
i=i+1
return refs
else: # if not __debug__
def add_contracts(target_class,contracts_class):
return target_class
# example
class Uncontracted:
def __init__(self,x,y):
self.x=x
self.y=y
def do(self):
# self.x = self.x + 1 # sneaky!
return self.x/self.y
class Contracts:
def pre___init__(self,x,y):
assert y <> 0
def post_do(self):
assert Old.self.x == self.x
assert Old.self.y == self.y
assert Result > 0, "Result was %s"%`Result`
def class_invariants(self):
assert self.x > 0
Contracted = add_contracts(Uncontracted,Contracts)
=== Added File Products/PythonMethod/zbytecodehacks/find_function_call.py ===
from code_editor import Function
from ops import *
def find_function_call(infunc,calledfuncname, allowkeywords=0, startindex=0):
i = startindex
code = infunc.func_code
cs = code.co_code
def match(op,name = calledfuncname):
return getattr(op,'name',None) == name
while i < len(cs):
op = code.co_code[i]
if match(op):
try:
if allowkeywords:
return simulate_stack_with_keywords(code,i)
else:
return simulate_stack(code,i)
except:
i = i + 1
i = i + 1
if allowkeywords:
return None,0
else:
return None
def call_stack_length_usage(arg):
num_keyword_args=arg>>8
num_regular_args=arg&0xFF
return 2*num_keyword_args + num_regular_args
def simulate_stack(code,index_start):
stack = []
cs = code.co_code
i, n = index_start, len(cs)
while i < n:
op = cs[i]
if op.__class__ is CALL_FUNCTION and op.arg+1==len(stack):
stack.append(op)
return stack
elif op.is_jump():
i = cs.index(op.label.op)+1
else:
op.execute(stack)
i = i + 1
raise "no call found!"
def simulate_stack_with_keywords(code,index_start):
stack = []
cs = code.co_code
i, n = index_start, len(cs)
while i < n:
op = cs[i]
if op.__class__ is CALL_FUNCTION \
and call_stack_length_usage(op.arg)+1==len(stack):
stack.append(op)
return stack, op.arg>>8
elif op.is_jump():
i = cs.index(op.label.op)+1
else:
op.execute(stack)
i = i + 1
raise "no call found!"
=== Added File Products/PythonMethod/zbytecodehacks/iif.py ===
from code_editor import Function
from ops import *
from find_function_call import find_function_call
def iifize(func):
func = Function(func)
cs = func.func_code.co_code
while 1:
stack = find_function_call(func,"iif")
if stack is None:
break
load, test, consequent, alternative, call = stack
cs.remove(load)
jump1 = JUMP_IF_FALSE(alternative)
cs.insert(cs.index(test)+1,jump1)
jump2 = JUMP_FORWARD(call)
cs.insert(cs.index(consequent)+1,jump2)
cs.remove(call)
cs = None
return func.make_function()
=== Added File Products/PythonMethod/zbytecodehacks/inline.py ===
import dis
from code_editor import Function
from find_function_call import find_function_call
from ops import \
LOAD_GLOBAL, RETURN_VALUE, SET_LINENO, CALL_FUNCTION, \
JUMP_FORWARD, STORE_FAST
INLINE_MAX_DEPTH = 100
def inline(func, **funcs):
func = Function(func)
code = func.func_code
for name, function in funcs.items():
count = inline1(func, name, function)
if count <> 0:
fcode=function.func_code
code.co_consts=code.co_consts+list(fcode.co_consts)
code.co_varnames=code.co_varnames+list(fcode.co_varnames)
code.co_names=code.co_names+list(fcode.co_names)
code.co_stacksize=code.co_stacksize+fcode.co_stacksize
return func.make_function()
def munge_code(function,code):
f = Function(function)
fcs = f.func_code.co_code
i, n = 0, len(fcs)
retops = []
while i < n:
op = fcs[i]
if op.__class__ is RETURN_VALUE:
# RETURN_VALUEs have to be replaced by JUMP_FORWARDs
newop = JUMP_FORWARD()
fcs[i] = newop
retops.append(newop)
elif op.op in dis.hasname:
op.arg = op.arg + len(code.co_names)
elif op.op in dis.haslocal:
op.arg = op.arg + len(code.co_varnames)
elif op.op in dis.hasconst:
op.arg = op.arg + len(code.co_consts)
# should we hack out SET_LINENOs? doesn't seem worth it.
i = i + 1
return fcs.opcodes, retops
def inline1(func,funcname,function):
code = func.func_code
cs = code.co_code
count = 0
defaults_added = 0
while count < INLINE_MAX_DEPTH:
stack, numkeywords = find_function_call(func,funcname,allowkeywords=1)
if stack is None:
return count
count = count + 1
load_func, posargs, kwargs, function_call = \
stack[0], stack[1:-2*numkeywords-1], stack[-2*numkeywords-1:-1], stack[-1]
kw={}
for i in range(0,len(kwargs),2):
name = code.co_consts[kwargs[i].arg]
valuesrc = kwargs[i+1]
kw[name] = valuesrc
varnames = list(function.func_code.co_varnames)
for i in kw.keys():
if i in varnames:
if varnames.index(i) < len(posargs):
raise TypeError, "keyword parameter redefined"
else:
raise TypeError, "unexpected keyword argument: %s"%i
# no varargs yet!
# flags = function.func_code.co_flags
# varargs = flags & (1<<2)
# varkeys = flags & (1<<3)
args_got = len(kw) + len(posargs)
args_expected = function.func_code.co_argcount
if args_got > args_expected:
raise TypeError,"too many arguments; expected %d, got %d"%(ac,len(lf) + len(posargs))
elif args_got < args_expected:
# default args?
raise TypeError,"not enough arguments; expected %d, got %d"%(ac,len(lf) + len(posargs))
cs.remove(load_func)
local_index = len(code.co_varnames)
for insn in posargs:
new = STORE_FAST(local_index)
cs.insert(cs.index(insn)+1,new)
labels = cs.find_labels(cs.index(new)+1)
for label in labels:
label.op = new
local_index = local_index + 1
for name, insn in kw.items():
new = STORE_FAST(varnames.index(name) + len(code.co_varnames))
cs.insert(cs.index(insn)+1,new)
labels = cs.find_labels(cs.index(new)+1)
for label in labels:
label.op = new
newops, retops = munge_code(function,code)
call_index = cs.index(function_call)
nextop = cs[call_index + 1]
cs[call_index:call_index + 1] = newops
for op in retops:
op.label.op = nextop
raise RuntimeError, "are we trying to inline a recursive function here?"
=== Added File Products/PythonMethod/zbytecodehacks/label.py ===
import struct
class Label:
def __init__(self,byte=None):
self.byte=byte
self.__op=None
self.absrefs=[]
self.relrefs=[]
def resolve(self,code):
self.__op=code.opcodes[code.byte2op[self.byte]]
def add_absref(self,byte):
# request that the absolute address of self.op be written to
# the argument of the opcode starting at byte in the
# codestring
self.absrefs.append(byte)
def add_relref(self,byte):
# request that the relative address of self.op be written to
# the argument of the opcode starting at byte in the
# codestring
self.relrefs.append(byte)
def __setattr__(self,attr,value):
if attr == 'op':
self.__op = value
else:
self.__dict__[attr] = value
def __getattr__(self,attr):
if attr == 'op':
return self.__op
else:
raise AttributeError, attr
def write_refs(self,cs):
address=self.__op.byte
for byte in self.absrefs:
cs.seek(byte+1)
cs.write(struct.pack('<h',address))
for byte in self.relrefs:
offset=address-byte-3
cs.seek(byte+1)
cs.write(struct.pack('<h',offset))
=== Added File Products/PythonMethod/zbytecodehacks/macro.py ===
import dis
from code_editor import Function
from find_function_call import find_function_call
from ops import *
MAX_MACRO_DEPTH = 100
_macros = {}
def add_macro(arg1,arg2=None):
if arg2 is None:
_macros[arg1.func_name] = arg1
else:
_macros[arg1]=arg2
def expand(func, macros = None):
func = Function(func)
code = func.func_code
if macros is None:
macros = _macros
insertions = {}
trips = 0
while trips < MAX_MACRO_DEPTH:
outercount = 0
for name,macro in macros.items():
count = expand1(func, name, macro)
outercount = outercount + count
if count <> 0 and not insertions.has_key(macro):
fcode=macro.func_code
code.co_consts=code.co_consts+list(fcode.co_consts)
code.co_varnames=code.co_varnames+list(fcode.co_varnames)
code.co_names=code.co_names+list(fcode.co_names)
code.co_stacksize=code.co_stacksize+fcode.co_stacksize
insertions[macro] = 0
if not outercount:
return func.make_function()
trips = trips + 1
raise RuntimeError, "unbounded recursion?!"
def expand_these(func,**macros):
return expand(func,macros)
def remove_epilogue(cs):
try:
last,butone,buttwo = cs[-3:]
except:
return
if last.__class__ is buttwo.__class__ is RETURN_VALUE:
if butone.__class__ is LOAD_CONST:
if cs.code.co_consts[butone.arg] is None:
if not (cs.find_labels(-1) or cs.find_labels(-2)):
del cs[-2:]
def munge_code(function,code,imported_locals):
f = Function(function)
fcs = f.func_code.co_code
if fcs[0].__class__ is SET_LINENO:
del fcs[1:1 + 2*len(imported_locals)]
else:
del fcs[0:2*len(imported_locals)]
# a nicety: let's see if the last couple of opcodes are necessary
# (Python _always_ adds a LOAD_CONST None, RETURN_VALUE to the end
# of a function, and I'd like to get rid of that if we can).
remove_epilogue(fcs)
i, n = 0, len(fcs)
retops = []
while i < n:
op = fcs[i]
if op.__class__ is RETURN_VALUE:
# RETURN_VALUEs have to be replaced by JUMP_FORWARDs
newop = JUMP_FORWARD()
fcs[i] = newop
retops.append(newop)
elif op.op in dis.hasname:
op.arg = op.arg + len(code.co_names)
elif op.op in dis.haslocal:
localname = f.func_code.co_varnames[op.arg]
op.arg = imported_locals.get(localname,op.arg + len(code.co_varnames))
elif op.op in dis.hasconst:
op.arg = op.arg + len(code.co_consts)
# should we hack out SET_LINENOs? doesn't seem worth it.
i = i + 1
return fcs.opcodes, retops
def expand1(func,name,macro):
code = func.func_code
cs = code.co_code
count = 0
macrocode = macro.func_code
while count < MAX_MACRO_DEPTH:
stack = find_function_call(func,name)
if stack is None:
return count
count = count + 1
load_func, args, function_call = \
stack[0], stack[1:-1], stack[-1]
args_got = len(args)
args_expected = macrocode.co_argcount
if args_got > args_expected:
raise TypeError,"too many arguments; expected %d, got %d"%(args_expected,args_got)
elif args_got < args_expected:
# default args?
raise TypeError,"not enough arguments; expected %d, got %d"%(args_expected,args_got)
cs.remove(load_func)
arg_names = macrocode.co_varnames[:macrocode.co_argcount]
import_args = []
normal_args = []
for i in range(len(arg_names)):
if arg_names[i][0] == '.':
import_args.append(args[i])
else:
normal_args.append(args[i])
imported_locals = {}
for insn in import_args:
cs.remove(insn)
if insn.__class__ is LOAD_GLOBAL:
name = code.co_names[insn.arg]
var = global_to_local(code, name)
elif insn.__class__ is not LOAD_FAST:
raise TypeError, "imported arg must be local"
else:
var = insn.arg
argindex = macrocode.co_argcount + import_args.index(insn)
argname = macrocode.co_varnames[argindex]
imported_locals[argname] = var
local_index = len(code.co_varnames)
for insn in normal_args:
new = STORE_FAST(local_index + args.index(insn))
cs.insert(cs.index(insn)+1,new)
labels = cs.find_labels(cs.index(new)+1)
for label in labels:
label.op = new
newops, retops = munge_code(macro,code,imported_locals)
call_index = cs.index(function_call)
nextop = cs[call_index + 1]
cs[call_index:call_index + 1] = newops
for op in retops:
if cs.index(nextop) - cs.index(op) == 1:
cs.remove(op)
else:
op.label.op = nextop
raise RuntimeError, "are we trying to expand a recursive macro here?"
def global_to_local(code, name):
"""\
internal function to make a global variable into
a local one, for the case that setq is the first
reference to a variable.
Modifies a code object in-place.
Return value is index into variable table
"""
cs = code.co_code
index = len(code.co_varnames)
code.co_varnames.append(name)
for i in range(len(cs)):
op = cs[i]
if op.__class__ not in [LOAD_GLOBAL,STORE_GLOBAL]:
continue
thisname = code.co_names[op.arg]
if thisname <> name:
continue
if op.__class__ is LOAD_GLOBAL:
cs[i] = LOAD_FAST(index)
else:
cs[i] = STORE_FAST(index)
return index
=== Added File Products/PythonMethod/zbytecodehacks/macros.py ===
from macro import add_macro
def main():
def setq((x),v):
x = v
return v
add_macro(setq)
def pre_incr((x)):
x = x + 1
return x
add_macro(pre_incr)
def post_incr((x)):
t = x
x = x + 1
return t
add_macro(post_incr)
def pre_decr((x)):
x = x - 1
return x
add_macro(pre_decr)
def post_decr((x)):
t = x
x = x + 1
return t
add_macro(post_decr)
def add_set((x),v):
x = x + v
return x
add_macro(add_set)
def sub_set((x),v):
x = x - v
return x
add_macro(sub_set)
def mul_set((x),v):
x = x * v
return x
add_macro(mul_set)
def div_set((x),v):
x = x / v
return x
add_macro(div_set)
def mod_set((x),v):
x = x % v
return x
add_macro(mod_set)
main()
def test():
from macro import expand
def f(x):
i = 0
while pre_incr(i) < len(x):
if setq(c, x[i]) == 3:
print c, 42
x = expand(f)
return x
x(range(10))
=== Added File Products/PythonMethod/zbytecodehacks/opbases.py ===
import struct,dis,new
from label import Label
class ByteCode:
pass
class GenericOneByteCode(ByteCode):
def __init__(self,cs,code):
pass
def __repr__(self):
return self.__class__.__name__
def assemble(self,code):
return self.opc
def is_jump(self):
return 0
def has_name(self):
return 0
def has_name_or_local(self):
return self.has_name() or self.has_local()
def has_local(self):
return 0
class GenericThreeByteCode(GenericOneByteCode):
def __init__(self,cs,code):
GenericOneByteCode.__init__(self,cs,code)
arg=cs.read(2)
self.arg=struct.unpack('<h',arg)[0]
def __repr__(self):
return "%s %d"%(self.__class__.__name__, self.arg)
def assemble(self,code):
return self.opc+struct.pack('<h',self.arg)
def user_init(self,arg):
self.arg = arg
class Jump(GenericThreeByteCode):
def __repr__(self):
return "%s %s"%(self.__class__.__name__, `self.label`)
def is_jump(self):
return 1
def user_init(self,arg):
self.label.op = arg
class JRel(Jump):
def __init__(self,cs,code):
GenericThreeByteCode.__init__(self,cs,code)
self.label = Label(cs.tell()+self.arg)
code.add_label(self.label)
def assemble(self,code):
self.label.add_relref(self.byte)
return self.opc+'\000\000'
class JAbs(Jump):
def __init__(self,cs,code):
GenericThreeByteCode.__init__(self,cs,code)
self.label = Label(self.arg)
code.add_label(self.label)
def assemble(self,code):
self.label.add_absref(self.byte)
return self.opc+'\000\000'
class _NamedOpcode(GenericThreeByteCode):
def user_init(self,arg):
if type(arg) == type(1):
self.arg = arg
else:
self.name = arg
def __repr__(self):
if hasattr(self,"name"):
return "%s : %s"%(self.__class__.__name__,self.name)
else:
return "%s : %d"%(self.__class__.__name__,self.arg)
class NameOpcode(_NamedOpcode):
def __init__(self,cs,code):
GenericThreeByteCode.__init__(self,cs,code)
self.name = code.code.co_names[self.arg]
def has_name(self):
return 1
def assemble(self,code):
if hasattr(self,"name") and not hasattr(self,"arg"):
self.arg = code.code.name_index(self.name)
return GenericThreeByteCode.assemble(self,code)
class LocalOpcode(_NamedOpcode):
def __init__(self,cs,code):
GenericThreeByteCode.__init__(self,cs,code)
self.name = code.code.co_varnames[self.arg]
def has_local(self):
return 1
def assemble(self,code):
if hasattr(self,"name") and not hasattr(self,"arg"):
self.arg = code.code.local_index(self.name)
return GenericThreeByteCode.assemble(self,code)
=== Added File Products/PythonMethod/zbytecodehacks/ops.py === (986/1086 lines abridged)
# this file is autogenerated by running
# from bytecodehacks.code_gen import write_ops
# write_ops.Main()
import opbases
from label import Label
_opbases = opbases
_Label = Label
del Label
del opbases
_bytecodes={}
class STOP_CODE(_opbases.GenericOneByteCode):
op = 0
opc = '\000'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
pass
_bytecodes[STOP_CODE.opc]=STOP_CODE
class POP_TOP(_opbases.GenericOneByteCode):
op = 1
opc = '\001'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack.pop()
_bytecodes[POP_TOP.opc]=POP_TOP
class ROT_TWO(_opbases.GenericOneByteCode):
op = 2
opc = '\002'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-2:]=[stack[-1],stack[-2]]
_bytecodes[ROT_TWO.opc]=ROT_TWO
[-=- -=- -=- 986 lines omitted -=- -=- -=-]
self.user_init(csorarg)
def execute(self,stack):
raise "Exception!"
_bytecodes[RAISE_VARARGS.opc]=RAISE_VARARGS
class CALL_FUNCTION(_opbases.GenericThreeByteCode):
op = 131
opc = '\203'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.GenericThreeByteCode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
num_keyword_args=self.arg>>8
num_regular_args=self.arg&0xFF
stack[-2*num_keyword_args-num_regular_args-1:]=[self]
_bytecodes[CALL_FUNCTION.opc]=CALL_FUNCTION
class MAKE_FUNCTION(_opbases.GenericThreeByteCode):
op = 132
opc = '\204'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.GenericThreeByteCode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack[-self.arg-1:]=[self]
_bytecodes[MAKE_FUNCTION.opc]=MAKE_FUNCTION
class BUILD_SLICE(_opbases.GenericThreeByteCode):
op = 133
opc = '\205'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.GenericThreeByteCode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack[-self.arg:]=[self]
_bytecodes[BUILD_SLICE.opc]=BUILD_SLICE
=== Added File Products/PythonMethod/zbytecodehacks/rationalize.py ===
import code_editor
from ops import *
import operator
CONDJUMP = [ JUMP_IF_TRUE, JUMP_IF_FALSE ]
UNCONDJUMP = [ JUMP_FORWARD, JUMP_ABSOLUTE ]
UNCOND = UNCONDJUMP + [ BREAK_LOOP, STOP_CODE, RETURN_VALUE, \
RAISE_VARARGS ]
PYBLOCK = [ SETUP_LOOP, SETUP_EXCEPT, SETUP_FINALLY ]
PYENDBLOCK = [ POP_BLOCK ]
binaryops = {
'BINARY_ADD': operator.add,
'BINARY_SUBTRACT': operator.sub,
'BINARY_MULTIPLY': operator.mul,
'BINARY_DIVIDE': operator.div,
'BINARY_MODULO': operator.mod,
'BINARY_POWER': pow,
'BINARY_LSHIFT': operator.lshift,
'BINARY_RSHIFT': operator.rshift,
'BINARY_AND': operator.and_,
'BINARY_OR': operator.or_,
'BINARY_XOR': operator.xor
}
unaryops = {
'UNARY_POS': operator.pos,
'UNARY_NEG': operator.neg,
'UNARY_NOT': operator.not_
}
def rationalize(code):
calculateConstants(code)
strip_setlineno(code)
simplifyjumps(code)
removeconstjump(code)
simplifyjumps(code)
eliminateUnusedNames(code)
eliminateUnusedLocals(code)
def calculateConstants(co):
"""Precalculate results of operations involving constants."""
cs = co.co_code
cc = co.co_consts
stack = []
i = 0
while i < len(cs):
op = cs[i]
if binaryops.has_key(op.__class__.__name__):
if stack[-1].__class__ is stack[-2].__class__ is LOAD_CONST:
arg1 = cc[stack[-2].arg]
arg2 = cc[stack[-1].arg]
result = binaryops[op.__class__.__name__](arg1,arg2)
if result in cc:
arg = cc.index(result)
else:
arg = len(cc)
cc.append(result)
cs.remove(stack[-2])
cs.remove(stack[-1])
i = i - 2
cs[i] = LOAD_CONST(arg)
stack.pop()
stack.pop()
stack.append(cs[i])
else:
op.execute(stack)
elif unaryops.has_key(op.__class__.__name__):
if stack[-1].__class__ is LOAD_CONST:
arg1 = cc[stack[-1].arg]
result = unaryops[op.__class__.__name__](arg1)
if result in cc:
arg = cc.index(result)
else:
arg = len(cc)
cc.append(result)
cs.remove(stack[-1])
i = i - 1
cs[i] = LOAD_CONST(arg)
stack.pop()
stack.append(cs[i])
else:
op.execute(stack)
else:
# this is almost certainly wrong
try:
op.execute(stack)
except: pass
i = i + 1
def strip_setlineno(co):
"""Take in an EditableCode object and strip the SET_LINENO bytecodes"""
i = 0
while i < len(co.co_code):
op = co.co_code[i]
if op.__class__ is SET_LINENO:
co.co_code.remove(op)
else:
i = i + 1
def simplifyjumps(co):
cs = co.co_code
i = 0
pyblockstack = [None]
loopstack = [None]
trystack = [None]
firstlook = 1
while i < len(cs):
op = cs[i]
# new pyblock?
if firstlook:
if op.__class__ in PYBLOCK:
pyblockstack.append(op)
if op.__class__ is SETUP_LOOP:
loopstack.append(op.label.op)
else:
trystack.append(op.label.op)
# end of pyblock?
elif op.__class__ == POP_BLOCK:
op2 = pyblockstack.pop()
if op2.__class__ == SETUP_LOOP:
loopstack.pop()
else:
trystack.pop()
# Is the code inaccessible
if i >= 1:
if cs[i-1].__class__ in UNCOND and not (cs.find_labels(i) or \
op.__class__ in PYENDBLOCK):
cs.remove(op)
firstlook = 1
continue
# are we jumping from the statement before?
if cs[i-1].__class__ in UNCONDJUMP:
if cs[i-1].label.op == op:
cs.remove(cs[i-1])
firstlook = 1
continue
# break before end of loop?
elif cs[i-1].__class__ == BREAK_LOOP:
if op.__class__ == POP_BLOCK:
cs.remove(cs[i-1])
firstlook = 1
continue
# Do we have an unconditional jump to an unconditional jump?
if op.__class__ in UNCONDJUMP:
if op.label.op.__class__ in UNCONDJUMP:
refop = op.label.op
if op.__class__ == JUMP_FORWARD:
newop = JUMP_ABSOLUTE()
newop.label.op = refop.label.op
cs[i] = newop
else:
op.label.op = refop.label.op
firstlook = 0
continue
# Do we have a conditional jump to a break?
if op.__class__ in CONDJUMP and loopstack[-1]:
destindex = cs.index(op.label.op)
preendindex = cs.index(loopstack[-1])-2
if cs[i+2].__class__ == BREAK_LOOP and cs[preendindex].__class__ \
== POP_TOP:
if op.__class__ == JUMP_IF_FALSE:
newop = JUMP_IF_TRUE()
else:
newop = JUMP_IF_FALSE()
newop.label.op = cs[preendindex]
cs[i] = newop
cs.remove(cs[i+1])
cs.remove(cs[i+1])
cs.remove(cs[i+1])
firstlook = 0
continue
elif cs[destindex+1].__class__ == BREAK_LOOP and \
cs[preendindex].__class__ == POP_TOP:
op.label.op = cs[preendindex]
cs.remove(cs[destindex])
cs.remove(cs[destindex])
cs.remove(cs[destindex])
firstlook = 0
continue
firstlook = 1
i = i+1
def removeconstjump(co):
cs = co.co_code
cc = co.co_consts
i = 0
while i < len(cs):
op = cs[i]
if op.__class__ in CONDJUMP and cs[i-1].__class__ == LOAD_CONST:
if (op.__class__ == JUMP_IF_FALSE and cc[cs[i-1].arg]) or \
(op.__class__ == JUMP_IF_TRUE and not cc[cs[i-1].arg]):
cs.remove(cs[i-1])
cs.remove(cs[i-1])
cs.remove(cs[i-1])
i = i-2
else:
cs.remove(cs[i-1])
cs.remove(cs[i])
newop = JUMP_FORWARD()
newop.label.op = cs[cs.index(op.label.op)+1]
cs[i-1] = newop
i = i-1
i = i+1
def eliminateUnusedNames(code):
used_names = {}
for op in code.co_code:
if op.has_name():
if hasattr(op,"arg"):
arg = op.arg
else:
arg = op.arg = code.name_index(op.name)
used_names[arg] = 1
used_names = used_names.keys()
used_names.sort()
name_mapping = {}
for i in range(len(used_names)):
name_mapping[used_names[i]]=i
newnames = []
for i in range(len(code.co_names)):
if i in used_names:
newnames.append(code.co_names[i])
code.co_names = newnames
for op in code.co_code:
if op.has_name():
op.arg = name_mapping[op.arg]
def eliminateUnusedLocals(code):
used_names = {}
for op in code.co_code:
if op.has_local():
if hasattr(op,"arg"):
arg = op.arg
else:
arg = op.arg = code.local_index(op.name)
used_names[arg] = 1
used_names = used_names.keys()
used_names.sort()
name_mapping = {}
for i in range(len(used_names)):
name_mapping[used_names[i]]=i
newnames = []
for i in range(len(code.co_varnames)):
if i in used_names:
newnames.append(code.co_varnames[i])
code.co_varnames = newnames
for op in code.co_code:
if op.has_local():
op.arg = name_mapping[op.arg]
=== Added File Products/PythonMethod/zbytecodehacks/tailr.py ===
from code_editor import Function
from find_function_call import find_function_call
from ops import *
def make_tail_recursive(func):
func = Function(func)
code = func.func_code
cs = code.co_code
index = 0
while 1:
stack = find_function_call(func,func.func_name,startindex=index)
if stack is None:
break
index = cs.index(stack[-1])
if cs[index + 1].__class__ is RETURN_VALUE:
cs.remove(stack[0])
newop = JUMP_ABSOLUTE()
cs[index - 1:index] = [newop]
newop.label.op = cs[0]
del stack[0],stack[-1]
nlocals = len(code.co_varnames)
code.co_varnames = code.co_varnames + code.co_varnames
for op in stack:
cs.insert(cs.index(op)+1,STORE_FAST(stack.index(op)+nlocals))
iindex = cs.index(newop)
for i in range(len(stack)):
cs.insert(iindex,STORE_FAST(i))
cs.insert(iindex,LOAD_FAST(i+nlocals))
index = iindex
return func.make_function()
def _facr(n,c,p):
if c <= n:
return _facr(n,c+1,c*p)
return p
def facr(n,_facr=_facr):
return _facr(n,1,1l)
_factr = make_tail_recursive(_facr)
def factr(n,_factr=_factr):
return _factr(n,1,1l)
def faci(n):
p = 1l; c = 1;
while c <= n:
p = c*p
c = c+1
return p
import time
def suite(n,c=10,T=time.time):
r = [0,0,0]
for i in range(c):
t=T(); facr(n); r[0] = T()-t + r[0]
t=T(); factr(n); r[1] = T()-t + r[1]
t=T(); faci(n); r[2] = T()-t + r[2]
print " recursive: 1.000000000000 (arbitrarily)"
print "tail recursive:",r[1]/r[0]
print " iterative:",r[2]/r[0]
=== Added File Products/PythonMethod/zbytecodehacks/version ===
0.52z
=== Added File Products/PythonMethod/zbytecodehacks/xapply.py ===
"""\
xapply
Inspired by Don Beaudry's functor module.
xapply exports one public function, the eponymous xapply. xapply can
be thought of as `lazy apply' or `partial argument resolution'.
It takes a function and part of it's argument list, and returns a
function with the first parameters filled in. An example:
def f(x,y):
return x+y
add1 = xapply(f,1)
add1(2) => 3
This xapply is not yet as general as that from the functor module, but
the functions return are as fast as normal function, i.e. twice as
fast as functors.
This may be generalised at some point in the future.
"""
import new,string,re,types
from ops import LOAD_FAST, LOAD_CONST
from code_editor import Function, InstanceMethod
def xapply_munge(code, args, except0=0):
nconsts = len(code.co_consts)
nvars = len(args)
code.co_consts.extend(list(args))
if except0:
var2constlim = nvars+1
var2constoff = nconsts-1
else:
var2constlim = nvars
var2constoff = nconsts
cs = code.co_code
for i in range(len(cs)):
op = cs[i]
if op.__class__ is LOAD_FAST:
if op.arg == 0 and except0:
continue
if op.arg < var2constlim:
cs[i] = LOAD_CONST(op.arg + var2constoff)
else:
op.arg = op.arg - nvars
code.co_varnames = code.co_varnames[nvars:]
code.co_argcount = code.co_argcount - nvars
def xapply_func(func,args):
f = Function(func)
xapply_munge(f.func_code,args,0)
return f.make_function()
def xapply_meth(meth,args):
im = InstanceMethod(meth)
xapply_munge(im.im_func.func_code,args,1)
return im.make_instance_method()
def xapply(callable,*args):
""" xapply(callable,arg1,arg2,...) -> callable
if
f=xapply(callable,arg1,arg2,...,argn)
then
f(arg<n+1>,....argm)
is equivalent to
callable(arg1,...,argn,arg<n+1>,..argm)
callable currently must be a function or instance method, and keyword
arguments are currently not allowed.
"""
callable_type=type(callable)
if callable_type is types.FunctionType:
return xapply_func(callable,args)
elif callable_type is types.UnboundMethodType:
return xapply_meth(callable,args)
else:
raise "nope"
More information about the zopeorg-checkins
mailing list