2012-08-11 15 views
7

Si tengo esta función, ¿qué debo hacer para reemplazar la función interna con mi propia versión personalizada?¿Existe un equivalente de anulación para las funciones anidadas?

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 

Usar clases esto se haría fácilmente anulando el método. Sin embargo, no puedo encontrar la manera de hacerlo con las 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.

+0

Cuando se usa 'bar' sean? ¿Se usa en las funciones posteriores (como 'baz'?) Usted * querría * reemplazarlo en esos casos, ¿verdad? –

+0

Exactamente. ¿Alguna forma de hacer esto? – Paolo

+0

Puede acceder a varios detalles internos de functione, porque son solo objetos. Use la función dir y la referencia del lenguaje para obtener más información. – Marcin

Respuesta

10

Ésta es una forma de hacerlo, la creación de un nuevo foo que "hace lo correcto" por el pirateo de las partes internas de función. (Según lo mencionado por @DSM). Lamentablemente, no podemos saltar a la función foo y meternos con sus partes internas, ya que en su mayoría están marcadas como de solo lectura, por lo 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 captar todos los casos. Pero funciona para el ejemplo (para mí en un viejo Python 2.5.1)

bits de feos que podría hacer con un poco de poner en orden son:

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

Usted puede encontrar información acerca de la CodeTypeFunctionType y utilizando el intérprete de comandos help(types.CodeType).

ACTUALIZACIÓN: Pensé que esto era demasiado feo, así que construí una función auxiliar para hacerlo más bonito. Con el ayudante puede 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, {}) 
+0

Tener ambos enfoques de codificación para esta respuesta proporciona el doble de conocimiento. – MikeiLL

3

Puede pasar en como parámetro opcional

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

No estoy seguro de entender. ¿Su respuesta no implica cambiar la función dada? Tal vez no he sido claro, pero no puedo cambiar el 'foo' original. Por cierto, voy a editar mi pregunta un poco. – Paolo

+4

Creo que OP está buscando algún tipo de opción de monopatch, ya que 'foo' proviene de un módulo importado dado que no puedo modificar. – PaulMcG

Cuestiones relacionadas