Self function
Gabriel Genellina
gagsl-py2 at yahoo.com.ar
Fri May 8 13:28:50 EDT 2009
En Wed, 06 May 2009 23:32:23 -0300, Luis Alberto Zarrabeitia Gomez
<kyrie at uh.cu> escribió:
>
> Quoting Steven D'Aprano <steven at 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
More information about the Python-list
mailing list