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

Reply via email to