Does an equivalent of override exist for nested functions?

后端 未结 2 1007
渐次进展
渐次进展 2021-01-02 18:56

If I have this function, what should I do to replace the inner function with my own custom version?

def foo():
    def bar():
        # I want to change this         


        
相关标签:
2条回答
  • 2021-01-02 19:12

    Here's one way of doing it, creating a new foo that "does the right thing" by hacking the function internals. ( As mentioned by @DSM ). Unfortunately we cant just jump into the foo function and mess with its internals, as they're mostly marked read only, so what we have to do is modify a copy we construct by hand.

    # 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()
    

    I'm pretty sure its not going to catch all cases. But it works for the example (for me on an old python 2.5.1 )

    Ugly bits that could do with some tidy up are:

    1. The huge argument list being passed to CodeType
    2. The ugly tuple constructed from co_consts overriding only one member. All the info is in co_consts to determine which to replace - so a smarter function could do this. I dug into the internals by hand using print( foo.func_code.co_consts ).

    You can find some information about the CodeType and FunctionType by using the interpreter command help( types.CodeType ).

    UPDATE: I thought this was too ugly so I built a helper function to make it prettier. With the helper you can write:

    # 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()
    

    Here's the implementation of 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 讨论(0)
  • 2021-01-02 19:26

    You can pass it in as an optional parameter

    def foo(bar=None):
        def _bar():
            # I want to change this
            pass
        if bar is None:
            bar = _bar
    
    0 讨论(0)
提交回复
热议问题