En Wed, 06 May 2009 23:32:23 -0300, Luis Alberto Zarrabeitia Gomez
<ky...@uh.cu> escribió:
Quoting Steven D'Aprano <ste...@remove.this.cybersource.com.au>:
But regardless, everyone is missing the most important point: why are
you
copying and pasting code in the first place? That is surely very close
to
the top of the list of Worst Ever Anti-Patterns, and it should be
avoided
whenever possible.
[btw, I took this long before jumping into this thread for this precise
reason... Copy/paste is an evil anti-pattern.
In Python, we can avoid much copy-and-paste coding with decorators.
Given
one function, we can create as many variants as we want, differing in
pre-
processing of arguments and post-processing of results, by using
decorators. This is a powerful ability to have, but it's crippled for
many recursive functions, because recursive functions in Python don't
actually call themselves, they call whatever happens to be bound to
their
name at runtime.
A bit offtopic: a while ago I think I saw a recipe for a decorator that,
via
bytecode hacks, would bind otherwise global names to the local namespace
of the
function. Can anyone remember it/point me to it? An @bind decorator that
would
'localize' all the global names, including the still unexistent but
already know
function name, would guarantee that at least recursion calls the same
function
instead of "whatever happens to be bound to their name at runtime". If
it wasn't
a hack, anyway.
I could not find such thing, but here it is something similar (mostly a
proof-of-concept).
Basically, modifies the bytecode so global references become local ones,
and then injects a new argument whose default value is the desired value.
It can be used to make all references to the function name to refer to the
function itself; also, the __function__ variant can be implemented.
<code>
from opcode import HAVE_ARGUMENT, opmap
def iter_code(co):
"""Iterate over a code object."""
code = co.co_code
n = len(code)
i = 0
while i < n:
op = ord(code[i])
if op >= HAVE_ARGUMENT:
oparg = ord(code[i+1]) + ord(code[i+2])*256
yield code[i:i+3], op, oparg
i += 3
else:
yield code[i], op, None
i += 1
def replace_bytecode(code, iname_global, iname_local):
LOAD_GLOBAL = opmap['LOAD_GLOBAL']
LOAD_FAST = opmap['LOAD_FAST']
STORE_GLOBAL = opmap['STORE_GLOBAL']
STORE_FAST = opmap['STORE_FAST']
for codestr, op, oparg in iter_code(code):
if op==LOAD_GLOBAL and oparg==iname_global:
codestr = chr(LOAD_FAST) + chr(iname_local % 256) + chr(iname_local
// 256)
elif op==STORE_GLOBAL and oparg==iname_global:
codestr = chr(STORE_FAST) + chr(iname_local % 256) + chr(iname_local
// 256)
yield(codestr)
class GlobalToLocalWarning(RuntimeWarning): pass
def global_to_local(function, refname, value):
"""
Make all references to `refname` local instead of global.
Actually, transform
def function(a,b,c):
into
def function(a,b,c,refname=value)
"""
code = function.func_code
if refname not in code.co_names:
import warnings
warnings.warn(GlobalToLocalWarning('no reference to "%s" found in %s'
% (refname, function.func_name)), stacklevel=2)
return function
# insert refname just after the last argument
varnames = list(code.co_varnames)
varnames.insert(code.co_argcount, refname)
# new bytecode using LOAD_FAST instead of LOAD_GLOBAL
iname_global = code.co_names.index(refname) # LOAD_GLOBAL/STORE_GLOBAL
operand
iname_local = code.co_argcount # LOAD_FAST/STORE_FAST operand
rawcode = ''.join(replace_bytecode(code, iname_global, iname_local))
function.func_code = type(code)(
code.co_argcount + 1,
code.co_nlocals + 1,
code.co_stacksize,
code.co_flags,
rawcode,
code.co_consts,
code.co_names,
tuple(varnames),
code.co_filename,
code.co_name,
code.co_firstlineno,
code.co_lnotab
)
# the desired value is the default value of the new, fake argument
if function.func_defaults is None:
function.func_defaults = (value,)
else:
function.func_defaults += (value,)
return function
# decorator: function name refers to itself
def recursive3(function):
return global_to_local(function, function.func_name, function)
@recursive3
def fibo(n):
if n<=1: return 1
return fibo(n-1) + fibo(n-2)
# those "fibo" won't be global anymore
# decorator: name '__function__' refers to function
def recursive4(function):
return global_to_local(function, '__function__', function)
def factorial(n):
@recursive4
def _factorial(n, acc):
if n<=1: return acc
return __function__(n-1, n*acc)
return _factorial(n, 1)
print fibo, fibo.__name__, fibo.func_name, fibo.func_code.co_name
assert fibo(5)==8
F10=factorial(10)
assert F10==2*3*4*5*6*7*8*9*10
foo = fibo
bar = factorial
del fibo, factorial
assert foo(5)==8
assert bar(10)==F10
import inspect
assert inspect.getargspec(foo).defaults[0] is foo
</code>
--
Gabriel Genellina
--
http://mail.python.org/mailman/listinfo/python-list