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)