Kannan Ekanath
Kannan Ekanath

Reputation: 17621

Python assign a function to another function

I am trying to assign a function to another function the left hand side of the assignment is available to me as a String. For example the body of the method I am looking for is

def change_function_defintion(name_of_function = 'module1.function1'
       , function_object):
    # Can I do eval(name_of_function) = function_object ? will that work?
    pass

Questions:

  1. How do I achieve this? Obviously if I call the above method and then call the module.function1 I expect the new function to be picked up.
  2. I am doing this in the context of unit testing i.e, Mock several functions, run the test and then basically "unmock" them. Are there any problems with the said approach?

Upvotes: 3

Views: 5370

Answers (6)

kiriloff
kiriloff

Reputation: 26333

Python function decorator

First, the notion you are talking about is the notion of function decorator. A function decorator is applied to a function definition by placing it on the line before that function definition begins (symbol @). It is a tool to modify the behavior of a function, or do operate composition of functions. Here is an example

class entryExit(object):

    def __init__(self, f):
        self.f = f

    def __call__(self):
        print "Entering", self.f.__name__
        self.f()
        print "Exited", self.f.__name__

@entryExit # decorator
def func1(): # decorated function
    print "inside func1()"

@entryExit
def func2():
    print "inside func2()"

I i run

func1()
func2()

i get

Entering func1
inside func1()
Exited func1
Entering func2
inside func2()
Exited func2

Python unittest.mock.patch()

patch acts as a function decorator, class decorator or a context manager. Inside the body of the function or with statement, the target is patched with a new object. When the function/with statement exits the patch is undone.

Patch lets you modify the function behavior within the with statement.

Here is an example where patch() is used as a context manager with a with statement.

>>> with patch.object(ProductionClass, 'method', return_value=None) 
  as mock_method:
...     thing = ProductionClass()
...     thing.method(1, 2, 3)
...
>>> mock_method.assert_called_once_with(1, 2, 3)

Upvotes: 1

Adrian Ratnapala
Adrian Ratnapala

Reputation: 5713

I think the following will do what you want (but you might want more robust parsing):

def set_named_fun(funname, fun) :
    import sys
    modname, funname =  funname.rsplit('.')
    mod = sys.modules[modname]
    setattr(mod, funname, fun)

The assumptions here are:

  • the module owning the function is already imported, and
  • you need to refer to the target function as a fully qualified name

The two assumptions are slightly in tension. There must be a great many cases where you can simply do:

import legend
legend.monkey = lambda : "great sage, equal of heaven"

Upvotes: 1

pcalcao
pcalcao

Reputation: 15990

My approach:

import importlib

def change_function_defintion(name_of_function = 'module1.function1'
   , function_object):
   my_mod = importlib.import_module('module1')
   setattr(my_mod, function1, function_object)

Now the longer rant:

That approach will probably work, if module1 is already imported in the local namespace, for instance, you can do something like:

>>> a = eval('str')
>>> a
<type 'str'>
>>> a(123)
'123'

In the context of mocking for unit tests, there might be a better way of doing so.

You can check here: http://pycheesecake.org/wiki/PythonTestingToolsTaxonomy#MockTestingTools for some libraries that will allow you to have more control around mocking objects in your unit tests.

EDIT:

You can do something like this, to dynamically import modules:

>>> import importlib
>>> my_mod = importlib.import_module('mymodule1')

Then, you can access the available functions inside the module, or get them via eval/getattr:

my_function = getattr(my_mod,'somefunction')

Of, if you want to swap that function to something else:

my_mod.functionName = newFunction

Upvotes: 1

Jean-Paul Calderone
Jean-Paul Calderone

Reputation: 48335

There are some problems with mocking and you might consider a different testing approach if possible:

Upvotes: 1

Matti John
Matti John

Reputation: 20527

I think it would be better to use a mocking library like Mock. Using patch you can change the function's behaviour within the scope of a context manager or a function and have it change back to normal afterwards. For example:

from mock import patch

with patch('module1.function1') as function1:
    function1.side_effect = function_object
    # Do stuff

if function1 is called inside the with block it will be replaced with function_object.

Similarly, patching within a function:

@patch('module1.function1')
def my_test(function1):
    function1.side_effect = function_object
    # Do stuff

Upvotes: 5

pranshus
pranshus

Reputation: 187

Can use "getattr" to get the function using the string name of the function (A function is an object). Then you can change the name and call / call something else (original named function) in the new named call.

Upvotes: 0

Related Questions