Skip to main content

This is my blog, more about me at marianoguerra.github.io

🦋 @marianoguerra.org 🐘 @marianoguerra@hachyderm.io 🐦 @warianoguerra

generando funciones dinamicamente (a.k.a generando bytecode a mano)

Este desafio estuvo bueno, alguien en la lista de emesene dijo que para hacer la API de dbus exponiendo los eventos del protocolo (que tienen numero de argumentos variables) necesitaba generar funciones con cantidad de argumentos variables pero que la funcion tenga ese numero fijo, esto significa que si tengo que generar una funcion que recibe 4 argumentos tiene que ser:

def fun(arg0, arg1, arg2, arg3): pass

y no

def fun(*args): pass

ya que la API de dbus usa inspect para determinar el numero de argumentos posicionales

me tomo varias horas sacarlo, buscaba y buscaba y cada vez iba mas a bajo nivel. Hasta que llegue al mas bajo nivel que se puede llegar... generar bytecodes :)

y bue, pelee con eso un buen rato ya que si bien alguna vez jugue haciendo maquinas virtuales de juguete esta era la primera vez contra una en serio.

el resultado es el siguiente:


import dis
import types

def gen(num):
op = dis.opmap.__getitem__
ops = [op('LOAD_GLOBAL'), 0, 0]

for argnum in range(num):
ops.append(op('LOAD_FAST'))
ops.append(argnum)
ops.append(0)

ops.append(op('CALL_FUNCTION'))
ops.append(num)
ops.append(0)
ops.append(op('RETURN_VALUE'))

code_str = ''.join(chr(x) for x in ops)
return code_str

def gen_code(num, global_vars, name='func', filename='magic.py'):
code = gen(num)
varnames = ['arg' + str(x) for x in range(num)]
names = global_vars + varnames

names = tuple(names)
varnames = tuple(varnames)

return types.CodeType(num, num, num, 0, code, (), names, varnames, filename, name, 1, '')

def gen_fun(num, name, func):
code = gen_code(num, [func.__name__], name)
return types.FunctionType(code,{func.__name__: func})

if __name__ == '__main__':
def printer(*args):
print args

f4 = gen_fun(4, 'f4', printer)
dis.dis(f4.func_code)
f4(1,2,3,4)

f1 = gen_fun(1, 'f1', printer)
dis.dis(f1.func_code)
f1('only one arg')

try:
f1(1, 2)
except TypeError:
print 'ok, ok, only one argument'


lo que hace es generar una funcion que recibe N parametros y que lo unico que hace es llamar a otra funcion pasandole esos parametros, lo cual seria algo como:

def fun1(*args): print args

def fun_que_recibe_4_parametros(a,b,c,d): fun1(a,b,c,d)

con lo que si escribimos nuestra funcion en fun1 y creamos las funciones que reciben los distintos parametros con un for tenemos lo que necesitamos :)

la salida de la ejecusion de lo de arriba es:

1 0 LOAD_GLOBAL 0 (printer)
3 LOAD_FAST 0 (arg0)
6 LOAD_FAST 1 (arg1)
9 LOAD_FAST 2 (arg2)
12 LOAD_FAST 3 (arg3)
15 CALL_FUNCTION 4
18 RETURN_VALUE

(1, 2, 3, 4)

1 0 LOAD_GLOBAL 0 (printer)
3 LOAD_FAST 0 (arg0)
6 CALL_FUNCTION 1
9 RETURN_VALUE

('only one arg',)
ok, ok, only one argument


fuentes de inspiracion:

http://pyref.infogami.com/type-code
http://docs.python.org/library/dis.html
http://docs.python.org/library/types
http://docs.python.org/reference/datamodel.html#the-standard-type-hierarchy (la parte de callable types)