5

Basically I want to do something like this: How can I hook a function in a python module?

but I want to call the old function after my own code.

like

import whatever

oldfunc = whatever.this_is_a_function

def this_is_a_function(parameter):
    #my own code here
    # and call original function back
    oldfunc(parameter)

whatever.this_is_a_function = this_is_a_function

Is this possible?

I tried copy.copy, copy.deepcopy original function but it didn't work.

4
  • 1
    It depends on what whatever is. What is it in this case? Is it your own module? Commented Mar 2, 2016 at 21:24
  • no it's not my own module, it is a module registered from C++ code by application I don't have source code for(I know I can hook function in C++ but I'm interested if it's possible straight from python) Commented Mar 2, 2016 at 21:25
  • The code you've posted looks fine, aside from perhaps return oldfunc(parameter) instead of just oldfunc(parameter). Are you having a problem with it? Commented Mar 2, 2016 at 21:34
  • Yes, there is a lbrary called wphooks for this purpose. See priority arg for actions Commented Dec 12 at 8:17

4 Answers 4

13

Something like this? It avoids using globals, which is generally a good thing.

import whatever
import functools

def prefix_function(function, prefunction):
    @functools.wraps(function)
    def run(*args, **kwargs):
        prefunction(*args, **kwargs)
        return function(*args, **kwargs)
    return run

def this_is_a_function(parameter):
    pass # Your own code here that will be run before

whatever.this_is_a_function = prefix_function(
    whatever.this_is_a_function, this_is_a_function)

prefix_function is a function that takes two functions: function and prefunction. It returns a function that takes any parameters, and calls prefunction followed by function with the same parameters. The prefix_function function works for any callable, so you only need to program the prefixing code once for any other hooking you might need to do.

@functools.wraps makes it so that the docstring and name of the returned wrapper function is the same.

If you need this_is_a_function to call the old whatever.this_is_a_function with arguments different than what was passed to it, you could do something like this:

import whatever
import functools

def wrap_function(oldfunction, newfunction):
    @functools.wraps(oldfunction)
    def run(*args, **kwargs):
        return newfunction(oldfunction, *args, **kwargs)
    return run

def this_is_a_function(oldfunc, parameter):
    # Do some processing or something to customize the parameters to pass
    newparams = parameter * 2  # Example of a change to newparams
    return oldfunc(newparams)

whatever.this_is_a_function = wrap_function(
        whatever.this_is_a_function, this_is_a_function)

There is a problem that if whatever is a pure C module, it's typically impossible (or very difficult) to change its internals in the first place.

Sign up to request clarification or add additional context in comments.

Comments

3

So, here's an example of monkey-patching the time function from the time module.

import time

old_time = time.time

def time():
    print('It is today... but more specifically the time is:')
    return old_time()

time.time = time

print time.time()
# Output:
# It is today... but more specifically the time is:
# 1456954003.2

However, if you are trying to do this to C code, you will most likely get an error like cannot overwrite attribute. In that case, you probably want to subclass the C module.

You may want to take a look at this question.

Comments

3

This is the perfect time to tout my super-simplistic Hooker

def hook(hookfunc, oldfunc):
    def foo(*args, **kwargs):
        hookfunc(*args, **kwargs)
        return oldfunc(*args, **kwargs)
    return foo

Incredibly simple. It will return a function that first runs the desired hook function (with the same parameters, mind you) and will then run the original function that you are hooking and return that original value. This also works to overwrite a class method. Say we have static method in a class.

class Foo:
    @staticmethod
    def bar(data):
        for datum in data:
            print(datum, end="") # assuming python3 for this
        print()

But we want to print the length of the data before we print out its elements

def myNewFunction(data):
    print("The length is {}.".format(len(data)))

And now we simple hook the function

Foo.bar(["a", "b", "c"])
# => a b c
Foo.bar = hook(Foo.bar, myNewFunction)
Foo.bar(["x", "y", "z"])
# => The length is 3.
# => x y z 

7 Comments

Why use staticmethod instead of just plain functions?
For the Hooker class, an instance variable is entirely unnecessary. I added an init function so it CAN be used, i.e. h = Hooker(), but it isn't necessary. If I don't use @staticmethod, then an instance would have had to have been declared.
No, no. Why does Hooker exist at all? Why is hook a static method of a Hooker class instead of just a module-level function?
@Goodies I suppose I meant "why encapsulate in a class at all"? Considering this is a utility function, I feel this would make most sense as a plain function in its own module, much like functools.wraps.
Oh, I see. Well, personally, I just have this sitting in my libs. From any file I can import Hooker. The file is called Hooker.py and there are no classes in it. yes, it is unnecessary.
|
1

Actually, you can replace the target function's func_code. The example below

# a normal function
def old_func():
    print "i am old"

# a class method
class A(object):
    def old_method(self):
        print "i am old_method"

# a closure function
def make_closure(freevar1, freevar2):
    def wrapper():
        print "i am old_clofunc, freevars:", freevar1, freevar2
    return wrapper
old_clofunc = make_closure('fv1', 'fv2')

# ===============================================

# the new function
def new_func(*args):
    print "i am new, args:", args
# the new closure function
def make_closure2(freevar1, freevar2):
    def wrapper():
        print "i am new_clofunc, freevars:", freevar1, freevar2
    return wrapper
new_clofunc = make_closure2('fv1', 'fv2')

# ===============================================

# hook normal function
old_func.func_code = new_func.func_code
# hook class method
A.old_method.im_func.func_code = new_func.func_code
# hook closure function
# Note: the closure function's `co_freevars` count should be equal
old_clofunc.func_code = new_clofunc.func_code

# ===============================================

# call the old
old_func()
A().old_method()
old_clofunc()

output:

i am new, args: ()
i am new, args: (<__main__.A object at 0x0000000004A5AC50>,)
i am new_clofunc, freevars: fv1 fv2

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.