¿Existe un equivalente de anulación para funciones anidadas?

Si tengo esta función, ¿qué debo hacer para reemplazar la función interna con mi propia versión personalizada?

def foo(): def bar(): # I want to change this pass # here starts a long list of functions I want to keep unchanged def baz(): pass 

Usando clases, esto se haría fácilmente sobrepasando el método. Sin embargo, no puedo averiguar cómo hacer eso con funciones anidadas. Cambiar foo para que sea una clase (o cualquier otra cosa) no es una opción porque proviene de un módulo importado dado que no puedo modificar.

Aquí hay una forma de hacerlo, creando un nuevo foo que “hace lo correcto” pirateando las funciones internas. (Como lo menciona @DSM). Desafortunadamente, no podemos simplemente saltar a la función foo y desordenar con sus elementos internos, ya que en su mayoría están marcados como de solo lectura, así que lo que tenemos que hacer es modificar una copia que construimos a mano.

 # Here's the original function def foo(): def bar(): print(" In bar orig") def baz(): print(" Calling bar from baz") bar() print("Foo calling bar:") bar() print("Foo calling baz:") baz() # Here's using it foo() # Now lets override the bar function import types # This is our replacement function def my_bar(): print(" Woo hoo I'm the bar override") # This creates a new code object used by our new foo function # based on the old foo functions code object. foocode = types.CodeType( foo.func_code.co_argcount, foo.func_code.co_nlocals, foo.func_code.co_stacksize, foo.func_code.co_flags, foo.func_code.co_code, # This tuple is a new version of foo.func_code.co_consts # NOTE: Don't get this wrong or you will crash python. ( foo.func_code.co_consts[0], my_bar.func_code, foo.func_code.co_consts[2], foo.func_code.co_consts[3], foo.func_code.co_consts[4] ), foo.func_code.co_names, foo.func_code.co_varnames, foo.func_code.co_filename, foo.func_code.co_name, foo.func_code.co_firstlineno, foo.func_code.co_lnotab, foo.func_code.co_freevars, foo.func_code.co_cellvars ) # This is the new function we're replacing foo with # using our new code. foo = types.FunctionType( foocode , {}) # Now use it foo() 

Estoy bastante seguro de que no va a detectar todos los casos. Pero funciona para el ejemplo (para mí en un antiguo python 2.5.1)

Los bits feos que podrían hacer con un poco de ordenación son:

  1. La enorme lista de argumentos que se pasa a CodeType
  2. La tupla fea construida a partir de co_consts anulando solo un miembro. Toda la información está en co_consts para determinar cuál reemplazar, por lo que una función más inteligente podría hacer esto. Hice cavas en el interior a mano usando print( foo.func_code.co_consts ) .

Puede encontrar información sobre CodeType y FunctionType utilizando la help( types.CodeType ) comando del intérprete help( types.CodeType ) .

ACTUALIZACIÓN: pensé que esto era demasiado feo, así que construí una función de ayuda para hacerlo más bonito. Con el ayudante puedes escribir:

 # Use our function to get a new version of foo with "bar" replaced by mybar foo = monkey_patch_fn( foo, "bar", my_bar ) # Check it works foo() 

Aquí está la implementación de monkey_patch_fn :

 # Returns a copy of original_fn with its internal function # called name replaced with new_fn. def monkey_patch_fn( original_fn, name, new_fn ): #Little helper function to pick out the correct constant def fix_consts(x): if x==None: return None try: if x.co_name == name: return new_fn.func_code except AttributeError, e: pass return x original_code = original_fn.func_code new_consts = tuple( map( fix_consts, original_code.co_consts ) ) code_type_args = [ "co_argcount", "co_nlocals", "co_stacksize", "co_flags", "co_code", "co_consts", "co_names", "co_varnames", "co_filename", "co_name", "co_firstlineno", "co_lnotab", "co_freevars", "co_cellvars" ] new_code = types.CodeType( *[ ( getattr(original_code,x) if x!="co_consts" else new_consts ) for x in code_type_args ] ) return types.FunctionType( new_code, {} ) 

Puedes pasarlo como parámetro opcional.

 def foo(bar=None): def _bar(): # I want to change this pass if bar is None: bar = _bar