Konrad
Konrad

Reputation: 7208

Hook python module function

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.

Upvotes: 5

Views: 31242

Answers (4)

Alyssa Haroldsen
Alyssa Haroldsen

Reputation: 3731

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.

Upvotes: 13

Baloo
Baloo

Reputation: 11

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

Upvotes: 1

Goodies
Goodies

Reputation: 4681

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 

Upvotes: 3

Jared Goguen
Jared Goguen

Reputation: 9010

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.

Upvotes: 3

Related Questions